1use yuv::{BufferStoreMut, YuvPackedImageMut, YuvPlanarImage};
11
12use super::ring::{V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG};
13
14#[derive(Clone, Copy)]
16pub struct I420View<'a> {
17 pub width: usize,
18 pub height: usize,
19 pub y: &'a [u8],
20 pub u: &'a [u8],
21 pub v: &'a [u8],
22 pub y_stride: usize,
23 pub uv_stride: usize,
24 pub full_range: bool,
26}
27
28impl<'a> I420View<'a> {
29 pub fn chroma_width(&self) -> usize {
30 self.width.div_ceil(2)
31 }
32
33 pub fn chroma_height(&self) -> usize {
34 self.height.div_ceil(2)
35 }
36}
37
38#[derive(Clone, Copy, Debug)]
40pub struct DeviceFormat {
41 pub width: usize,
42 pub height: usize,
43 pub fourcc: u32,
44}
45
46impl DeviceFormat {
47 pub fn frame_bytes(&self) -> usize {
49 match self.fourcc {
50 V4L2_PIX_FMT_YUYV => self.width * 2 * self.height,
51 _ => self.width * self.height + 2 * (self.width.div_ceil(2) * self.height.div_ceil(2)),
52 }
53 }
54
55 pub fn full_range(&self) -> bool {
57 self.fourcc == V4L2_PIX_FMT_MJPEG
58 }
59}
60
61pub struct I420Buffer {
63 pub width: usize,
64 pub height: usize,
65 pub data: Vec<u8>,
66}
67
68impl I420Buffer {
69 pub fn new(width: usize, height: usize) -> Self {
70 let mut b = I420Buffer { width, height, data: Vec::new() };
71 b.resize(width, height);
72 b
73 }
74
75 pub fn resize(&mut self, width: usize, height: usize) {
76 self.width = width;
77 self.height = height;
78 self.data.resize(width * height + 2 * (width.div_ceil(2) * height.div_ceil(2)), 0);
79 }
80
81 pub fn y_len(&self) -> usize {
82 self.width * self.height
83 }
84
85 pub fn uv_len(&self) -> usize {
86 self.width.div_ceil(2) * self.height.div_ceil(2)
87 }
88
89 pub fn view(&self, full_range: bool) -> I420View<'_> {
90 let (y, rest) = self.data.split_at(self.y_len());
91 let (u, v) = rest.split_at(self.uv_len());
92 I420View {
93 width: self.width,
94 height: self.height,
95 y,
96 u,
97 v,
98 y_stride: self.width,
99 uv_stride: self.width.div_ceil(2),
100 full_range,
101 }
102 }
103
104 pub fn fill_black(&mut self, full_range: bool) {
106 let y_len = self.y_len();
107 self.data[..y_len].fill(if full_range { 0 } else { 16 });
108 self.data[y_len..].fill(128);
109 }
110}
111
112struct RangeLut {
114 luma: [u8; 256],
115 chroma: [u8; 256],
116 luma_up: [u8; 256],
117 chroma_up: [u8; 256],
118}
119
120fn range_lut() -> &'static RangeLut {
121 static LUT: std::sync::OnceLock<RangeLut> = std::sync::OnceLock::new();
122 LUT.get_or_init(|| {
123 let mut luma = [0u8; 256];
124 let mut chroma = [0u8; 256];
125 let mut luma_up = [0u8; 256];
126 let mut chroma_up = [0u8; 256];
127 for i in 0..256usize {
128 luma[i] = (16 + (i * 219 + 127) / 255) as u8;
129 let c = i as i32 - 128;
130 let scaled = if c >= 0 { (c * 224 + 127) / 255 } else { -((-c * 224 + 127) / 255) };
131 chroma[i] = (128 + scaled) as u8;
132 let ly = i as i32 - 16;
133 luma_up[i] = ((ly * 255 + 109) / 219).clamp(0, 255) as u8;
134 let up = if c >= 0 { (c * 255 + 112) / 224 } else { -((-c * 255 + 112) / 224) };
135 chroma_up[i] = (128 + up).clamp(0, 255) as u8;
136 }
137 RangeLut { luma, chroma, luma_up, chroma_up }
138 })
139}
140
141fn range_luts(src_full: bool, dev: &DeviceFormat) -> (Option<&'static [u8; 256]>, Option<&'static [u8; 256]>) {
145 let lut = range_lut();
146 match (src_full, dev.full_range()) {
147 (true, false) => (Some(&lut.luma), Some(&lut.chroma)),
148 (false, true) => (Some(&lut.luma_up), Some(&lut.chroma_up)),
149 _ => (None, None),
150 }
151}
152
153fn copy_plane(src: &[u8], src_stride: usize, width: usize, height: usize, dst: &mut [u8], dst_stride: usize, lut: Option<&[u8; 256]>) {
155 for row in 0..height {
156 let s = &src[row * src_stride..row * src_stride + width];
157 let d = &mut dst[row * dst_stride..row * dst_stride + width];
158 match lut {
159 Some(t) => {
160 for (o, i) in d.iter_mut().zip(s) {
161 *o = t[*i as usize];
162 }
163 }
164 None => d.copy_from_slice(s),
165 }
166 }
167}
168
169fn halve_plane(src: &[u8], sw: usize, sh: usize, sstride: usize, dst: &mut Vec<u8>) -> (usize, usize) {
172 let dw = (sw / 2).max(1);
173 let dh = (sh / 2).max(1);
174 dst.resize(dw * dh, 0);
175 for y in 0..dh {
176 let r0 = &src[(2 * y).min(sh - 1) * sstride..];
177 let r1 = &src[(2 * y + 1).min(sh - 1) * sstride..];
178 let out = &mut dst[y * dw..(y + 1) * dw];
179 for (x, o) in out.iter_mut().enumerate() {
180 let x0 = (2 * x).min(sw - 1);
181 let x1 = (2 * x + 1).min(sw - 1);
182 *o = ((r0[x0] as u32 + r0[x1] as u32 + r1[x0] as u32 + r1[x1] as u32 + 2) / 4) as u8;
183 }
184 }
185 (dw, dh)
186}
187
188fn bilinear_plane(src: &[u8], sw: usize, sh: usize, sstride: usize, dst: &mut [u8], dw: usize, dh: usize, dstride: usize) {
190 if sw == 0 || sh == 0 || dw == 0 || dh == 0 {
191 return;
192 }
193 if sw == dw && sh == dh {
194 copy_plane(src, sstride, sw, sh, dst, dstride, None);
195 return;
196 }
197 let xs: Vec<(usize, usize, u32)> = (0..dw)
198 .map(|x| {
199 let fx = ((x as u64 * 2 + 1) * sw as u64 * 65536 / (dw as u64 * 2)).saturating_sub(32768);
200 let x0 = (fx >> 16) as usize;
201 let frac = (fx & 0xFFFF) as u32;
202 let x0 = x0.min(sw - 1);
203 let x1 = (x0 + 1).min(sw - 1);
204 (x0, x1, frac)
205 })
206 .collect();
207 for y in 0..dh {
208 let fy = ((y as u64 * 2 + 1) * sh as u64 * 65536 / (dh as u64 * 2)).saturating_sub(32768);
209 let y0 = ((fy >> 16) as usize).min(sh - 1);
210 let y1 = (y0 + 1).min(sh - 1);
211 let wy = (fy & 0xFFFF) as u32;
212 let r0 = &src[y0 * sstride..y0 * sstride + sw];
213 let r1 = &src[y1 * sstride..y1 * sstride + sw];
214 let out = &mut dst[y * dstride..y * dstride + dw];
215 for (o, &(x0, x1, wx)) in out.iter_mut().zip(&xs) {
216 let top = r0[x0] as u32 * (65536 - wx) + r0[x1] as u32 * wx;
217 let bot = r1[x0] as u32 * (65536 - wx) + r1[x1] as u32 * wx;
218 let v = ((top >> 8) * (65536 - wy) + (bot >> 8) * wy + (1 << 23)) >> 24;
219 *o = v.min(255) as u8;
220 }
221 }
222}
223
224fn scale_plane(src: &[u8], sw: usize, sh: usize, sstride: usize, dst: &mut [u8], dw: usize, dh: usize, tmp: &mut [Vec<u8>; 2]) {
227 let mut cur: Option<(usize, usize, usize)> = None;
228 let mut which = 0;
229 let (mut w, mut h, mut stride) = (sw, sh, sstride);
230 while w >= 2 * dw && h >= 2 * dh && w > 1 && h > 1 {
231 let (nw, nh) = {
232 let (a, b) = tmp.split_at_mut(1);
233 let (out, input) = if which == 0 { (&mut b[0], &a[0]) } else { (&mut a[0], &b[0]) };
234 match cur {
235 None => halve_plane(src, w, h, stride, out),
236 Some(_) => halve_plane(input, w, h, stride, out),
237 }
238 };
239 which ^= 1;
240 w = nw;
241 h = nh;
242 stride = nw;
243 cur = Some((w, h, stride));
244 }
245 match cur {
246 None => bilinear_plane(src, sw, sh, sstride, dst, dw, dh, dw),
247 Some(_) => {
248 let input = if which == 0 { &tmp[0] } else { &tmp[1] };
249 bilinear_plane(input, w, h, stride, dst, dw, dh, dw);
250 }
251 }
252}
253
254pub struct Normalizer {
256 fitted: I420Buffer,
257 fitted_geometry: Option<(usize, usize, usize, usize)>,
258 scaled: [Vec<u8>; 3],
259 halves: [Vec<u8>; 2],
260}
261
262impl Normalizer {
263 pub fn new() -> Self {
264 Normalizer {
265 fitted: I420Buffer::new(2, 2),
266 fitted_geometry: None,
267 scaled: [Vec::new(), Vec::new(), Vec::new()],
268 halves: [Vec::new(), Vec::new()],
269 }
270 }
271
272 pub fn write_frame(&mut self, src: &I420View<'_>, dev: &DeviceFormat, out: &mut [u8]) -> usize {
274 let need = dev.frame_bytes();
275 if out.len() < need || src.width == 0 || src.height == 0 {
276 return 0;
277 }
278 if src.width == dev.width && src.height == dev.height {
279 emit(src, dev, out);
280 return need;
281 }
282 let (dw, dh, ox, oy) = fit(src.width, src.height, dev.width, dev.height);
283 if self.fitted.width != dev.width || self.fitted.height != dev.height {
284 self.fitted.resize(dev.width, dev.height);
285 self.fitted_geometry = None;
286 }
287 if self.fitted_geometry != Some((dw, dh, ox, oy)) {
288 self.fitted.fill_black(dev.full_range());
289 self.fitted_geometry = Some((dw, dh, ox, oy));
290 }
291 let cw = dw.div_ceil(2);
292 let ch = dh.div_ceil(2);
293 self.scaled[0].resize(dw * dh, 0);
294 self.scaled[1].resize(cw * ch, 0);
295 self.scaled[2].resize(cw * ch, 0);
296 scale_plane(src.y, src.width, src.height, src.y_stride, &mut self.scaled[0], dw, dh, &mut self.halves);
297 scale_plane(src.u, src.chroma_width(), src.chroma_height(), src.uv_stride, &mut self.scaled[1], cw, ch, &mut self.halves);
298 scale_plane(src.v, src.chroma_width(), src.chroma_height(), src.uv_stride, &mut self.scaled[2], cw, ch, &mut self.halves);
299 let (y_lut, c_lut) = range_luts(src.full_range, dev);
300 let fw = self.fitted.width;
301 let fcw = fw.div_ceil(2);
302 let y_len = self.fitted.y_len();
303 let uv_len = self.fitted.uv_len();
304 let (yp, rest) = self.fitted.data.split_at_mut(y_len);
305 let (up, vp) = rest.split_at_mut(uv_len);
306 copy_plane(&self.scaled[0], dw, dw, dh, &mut yp[oy * fw + ox..], fw, y_lut);
307 copy_plane(&self.scaled[1], cw, cw, ch, &mut up[(oy / 2) * fcw + ox / 2..], fcw, c_lut);
308 copy_plane(&self.scaled[2], cw, cw, ch, &mut vp[(oy / 2) * fcw + ox / 2..], fcw, c_lut);
309 let fitted = self.fitted.view(dev.full_range());
310 emit(&fitted, dev, out);
311 need
312 }
313}
314
315impl Default for Normalizer {
316 fn default() -> Self {
317 Self::new()
318 }
319}
320
321#[derive(Clone, Copy, PartialEq, Eq, Debug)]
327pub struct Orientation {
328 pub quarter_turns: u8,
330 pub hflip: bool,
332}
333
334impl Orientation {
335 pub const UPRIGHT: Orientation = Orientation { quarter_turns: 0, hflip: false };
336
337 pub fn is_upright(&self) -> bool {
338 self.quarter_turns % 4 == 0 && !self.hflip
339 }
340}
341
342const ORIENT_TILE: usize = 32;
345
346fn orient_plane(src: &[u8], stride: usize, w: usize, h: usize, o: Orientation, dst: &mut [u8]) {
354 let q = o.quarter_turns % 4;
355 let (dw, dh) = if q % 2 == 1 { (h, w) } else { (w, h) };
356 if q % 2 == 0 {
357 let reversed = (q == 2) != o.hflip;
358 for dy in 0..dh {
359 let sy = if q == 2 { h - 1 - dy } else { dy };
360 let row = &src[sy * stride..sy * stride + w];
361 let out = &mut dst[dy * dw..dy * dw + dw];
362 if reversed {
363 for (dx, o) in out.iter_mut().enumerate() {
364 *o = row[w - 1 - dx];
365 }
366 } else {
367 out.copy_from_slice(row);
368 }
369 }
370 return;
371 }
372 for ty in (0..dh).step_by(ORIENT_TILE) {
373 for tx in (0..dw).step_by(ORIENT_TILE) {
374 for dy in ty..(ty + ORIENT_TILE).min(dh) {
375 let row = &mut dst[dy * dw..(dy + 1) * dw];
376 for dx in tx..(tx + ORIENT_TILE).min(dw) {
377 let (sx, sy) = if q == 1 { (dy, h - 1 - dx) } else { (w - 1 - dy, dx) };
378 row[if o.hflip { dw - 1 - dx } else { dx }] = src[sy * stride + sx];
379 }
380 }
381 }
382 }
383}
384
385pub fn orient_i420(src: &I420View<'_>, o: Orientation, out: &mut I420Buffer) {
390 let q = o.quarter_turns % 4;
391 let (dw, dh) = if q % 2 == 1 { (src.height, src.width) } else { (src.width, src.height) };
392 out.resize(dw, dh);
393 let y_len = out.y_len();
394 let uv_len = out.uv_len();
395 let (yp, rest) = out.data.split_at_mut(y_len);
396 let (up, vp) = rest.split_at_mut(uv_len);
397 orient_plane(src.y, src.y_stride, src.width, src.height, o, yp);
398 orient_plane(src.u, src.uv_stride, src.chroma_width(), src.chroma_height(), o, up);
399 orient_plane(src.v, src.uv_stride, src.chroma_width(), src.chroma_height(), o, vp);
400}
401
402pub fn fit(sw: usize, sh: usize, dw: usize, dh: usize) -> (usize, usize, usize, usize) {
405 let (mut w, mut h) = if sw * dh > dw * sh {
406 (dw, (sh * dw / sw).max(2))
407 } else {
408 ((sw * dh / sh).max(2), dh)
409 };
410 w = (w.min(dw) / 2) * 2;
411 h = (h.min(dh) / 2) * 2;
412 let ox = ((dw - w) / 2 / 2) * 2;
413 let oy = ((dh - h) / 2 / 2) * 2;
414 (w, h, ox, oy)
415}
416
417fn emit(src: &I420View<'_>, dev: &DeviceFormat, out: &mut [u8]) {
419 let (y_lut, c_lut) = range_luts(src.full_range, dev);
420 let w = dev.width;
421 let h = dev.height;
422 let cw = w.div_ceil(2);
423 let ch = h.div_ceil(2);
424 match dev.fourcc {
425 V4L2_PIX_FMT_NV12 => {
426 let (yp, uvp) = out.split_at_mut(w * h);
427 copy_plane(src.y, src.y_stride, w, h, yp, w, y_lut);
428 for row in 0..ch {
429 let u = &src.u[row * src.uv_stride..row * src.uv_stride + cw];
430 let v = &src.v[row * src.uv_stride..row * src.uv_stride + cw];
431 let o = &mut uvp[row * cw * 2..(row + 1) * cw * 2];
432 for x in 0..cw {
433 let (uu, vv) = match c_lut {
434 Some(t) => (t[u[x] as usize], t[v[x] as usize]),
435 None => (u[x], v[x]),
436 };
437 o[2 * x] = uu;
438 o[2 * x + 1] = vv;
439 }
440 }
441 }
442 V4L2_PIX_FMT_YUYV => {
443 let planar = if src.full_range {
444 None
445 } else {
446 Some(YuvPlanarImage {
447 y_plane: src.y,
448 y_stride: src.y_stride as u32,
449 u_plane: src.u,
450 u_stride: src.uv_stride as u32,
451 v_plane: src.v,
452 v_stride: src.uv_stride as u32,
453 width: w as u32,
454 height: h as u32,
455 })
456 };
457 match planar {
458 Some(p) => {
459 let mut packed = YuvPackedImageMut {
460 yuy: BufferStoreMut::Borrowed(&mut out[..w * 2 * h]),
461 yuy_stride: (w * 2) as u32,
462 width: w as u32,
463 height: h as u32,
464 };
465 if yuv::yuv420_to_yuyv422(&mut packed, &p).is_ok() {
466 return;
467 }
468 yuyv_scalar(src, w, h, out, y_lut, c_lut);
469 }
470 None => yuyv_scalar(src, w, h, out, y_lut, c_lut),
471 }
472 }
473 _ => {
474 let (yp, rest) = out.split_at_mut(w * h);
475 let (up, vp) = rest.split_at_mut(cw * ch);
476 copy_plane(src.y, src.y_stride, w, h, yp, w, y_lut);
477 copy_plane(src.u, src.uv_stride, cw, ch, up, cw, c_lut);
478 copy_plane(src.v, src.uv_stride, cw, ch, vp, cw, c_lut);
479 }
480 }
481}
482
483fn yuyv_scalar(src: &I420View<'_>, w: usize, h: usize, out: &mut [u8], y_lut: Option<&[u8; 256]>, c_lut: Option<&[u8; 256]>) {
484 let cw = w.div_ceil(2);
485 for row in 0..h {
486 let y = &src.y[row * src.y_stride..row * src.y_stride + w];
487 let u = &src.u[(row / 2) * src.uv_stride..(row / 2) * src.uv_stride + cw];
488 let v = &src.v[(row / 2) * src.uv_stride..(row / 2) * src.uv_stride + cw];
489 let o = &mut out[row * w * 2..(row + 1) * w * 2];
490 for x in 0..w {
491 let yy = match y_lut { Some(t) => t[y[x] as usize], None => y[x] };
492 let c = if x % 2 == 0 { u[x / 2] } else { v[x / 2] };
493 let cc = match c_lut { Some(t) => t[c as usize], None => c };
494 o[2 * x] = yy;
495 o[2 * x + 1] = cc;
496 }
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::super::ring::V4L2_PIX_FMT_YUV420;
503 use super::*;
504
505 fn solid(width: usize, height: usize, y: u8, u: u8, v: u8) -> I420Buffer {
506 let mut b = I420Buffer::new(width, height);
507 let yl = b.y_len();
508 let ul = b.uv_len();
509 b.data[..yl].fill(y);
510 b.data[yl..yl + ul].fill(u);
511 b.data[yl + ul..].fill(v);
512 b
513 }
514
515 #[test]
516 fn mjpeg_device_takes_full_range_samples() {
517 let mjpeg = DeviceFormat { width: 4, height: 2, fourcc: V4L2_PIX_FMT_MJPEG };
518 let raw = DeviceFormat { width: 4, height: 2, fourcc: V4L2_PIX_FMT_YUV420 };
519 let mut limited = I420Buffer::new(4, 2);
520 limited.data[..8].fill(16);
521 limited.data[8..].fill(128);
522 let mut out = vec![0u8; mjpeg.frame_bytes()];
523 let mut n = Normalizer::new();
524 assert_eq!(n.write_frame(&limited.view(false), &mjpeg, &mut out), mjpeg.frame_bytes());
525 assert_eq!((out[0], out[8]), (0, 128));
526 limited.data[..8].fill(235);
527 n.write_frame(&limited.view(false), &mjpeg, &mut out);
528 assert_eq!(out[0], 255);
529 let mut full = I420Buffer::new(4, 2);
530 full.data[..8].fill(200);
531 full.data[8..].fill(60);
532 n.write_frame(&full.view(true), &mjpeg, &mut out);
533 assert_eq!((out[0], out[8]), (200, 60));
534 n.write_frame(&full.view(true), &raw, &mut out);
535 assert_eq!((out[0], out[8]), (range_lut().luma[200], range_lut().chroma[60]));
536 }
537
538 #[test]
539 fn fit_preserves_aspect_and_alignment() {
540 assert_eq!(fit(1280, 720, 1280, 720), (1280, 720, 0, 0));
541 assert_eq!(fit(640, 480, 1280, 720), (960, 720, 160, 0));
542 assert_eq!(fit(720, 1280, 1280, 720), (404, 720, 438, 0));
543 assert_eq!(fit(1920, 1080, 640, 480), (640, 360, 0, 60));
544 let (w, h, ox, oy) = fit(333, 777, 1280, 720);
545 assert!(w % 2 == 0 && h % 2 == 0 && ox % 2 == 0 && oy % 2 == 0);
546 assert!(w + ox <= 1280 && h + oy <= 720);
547 }
548
549 #[test]
550 fn same_size_i420_is_a_plain_copy() {
551 let src = solid(8, 4, 100, 60, 200);
552 let dev = DeviceFormat { width: 8, height: 4, fourcc: V4L2_PIX_FMT_YUV420 };
553 let mut out = vec![0u8; dev.frame_bytes() + 7];
554 let n = Normalizer::new().write_frame(&src.view(false), &dev, &mut out);
555 assert_eq!(n, 8 * 4 * 3 / 2);
556 assert_eq!(&out[..n], &src.data[..]);
557 }
558
559 #[test]
560 fn full_range_is_compressed_to_limited() {
561 let src = solid(4, 2, 255, 0, 255);
562 let dev = DeviceFormat { width: 4, height: 2, fourcc: V4L2_PIX_FMT_YUV420 };
563 let mut out = vec![0u8; dev.frame_bytes()];
564 Normalizer::new().write_frame(&src.view(true), &dev, &mut out);
565 assert_eq!(out[0], 235);
566 assert_eq!(out[8], 16);
567 assert_eq!(out[10], 240);
568 let black = solid(4, 2, 0, 128, 128);
569 Normalizer::new().write_frame(&black.view(true), &dev, &mut out);
570 assert_eq!((out[0], out[8], out[10]), (16, 128, 128));
571 }
572
573 #[test]
574 fn nv12_and_yuyv_interleave() {
575 let src = solid(4, 2, 50, 60, 70);
576 let nv12 = DeviceFormat { width: 4, height: 2, fourcc: V4L2_PIX_FMT_NV12 };
577 let mut out = vec![0u8; nv12.frame_bytes()];
578 assert_eq!(Normalizer::new().write_frame(&src.view(false), &nv12, &mut out), 12);
579 assert_eq!(&out[..8], &[50; 8]);
580 assert_eq!(&out[8..], &[60, 70, 60, 70]);
581 let yuyv = DeviceFormat { width: 4, height: 2, fourcc: V4L2_PIX_FMT_YUYV };
582 let mut out = vec![0u8; yuyv.frame_bytes()];
583 assert_eq!(Normalizer::new().write_frame(&src.view(false), &yuyv, &mut out), 16);
584 assert_eq!(&out[..8], &[50, 60, 50, 70, 50, 60, 50, 70]);
585 let mut out2 = vec![0u8; yuyv.frame_bytes()];
586 Normalizer::new().write_frame(&src.view(true), &yuyv, &mut out2);
587 assert_eq!(out2[0], range_lut().luma[50]);
588 assert_eq!(out2[1], range_lut().chroma[60]);
589 }
590
591 #[test]
592 fn letterbox_scales_and_pads_black() {
593 let src = solid(8, 8, 200, 90, 160);
594 let dev = DeviceFormat { width: 16, height: 8, fourcc: V4L2_PIX_FMT_YUV420 };
595 let mut out = vec![0u8; dev.frame_bytes()];
596 let mut n = Normalizer::new();
597 assert_eq!(n.write_frame(&src.view(false), &dev, &mut out), dev.frame_bytes());
598 assert_eq!(out[0], 16);
599 assert_eq!(out[4 + 3 * 16], 200);
600 assert_eq!(out[15], 16);
601 let uv = 16 * 8;
602 assert_eq!(out[uv], 128);
603 assert_eq!(out[uv + 2 + 8 * 2], 90);
604 assert_eq!(out[uv + 8 * 4 + 2 + 8 * 2], 160);
605 assert_eq!(n.write_frame(&src.view(false), &dev, &mut out), dev.frame_bytes());
606 assert_eq!(out[4 + 3 * 16], 200);
607 }
608
609 #[test]
610 fn orient_rotates_clockwise_then_mirrors() {
611 let mut src = I420Buffer::new(2, 2);
612 src.data[..4].copy_from_slice(&[1, 2, 3, 4]);
613 src.data[4] = 10;
614 src.data[5] = 20;
615 let mut out = I420Buffer::new(2, 2);
616 let v = src.view(false);
617 orient_i420(&v, Orientation { quarter_turns: 1, hflip: false }, &mut out);
618 assert_eq!(&out.data[..4], &[3, 1, 4, 2]);
619 orient_i420(&v, Orientation { quarter_turns: 2, hflip: false }, &mut out);
620 assert_eq!(&out.data[..4], &[4, 3, 2, 1]);
621 orient_i420(&v, Orientation { quarter_turns: 3, hflip: false }, &mut out);
622 assert_eq!(&out.data[..4], &[2, 4, 1, 3]);
623 orient_i420(&v, Orientation { quarter_turns: 0, hflip: true }, &mut out);
624 assert_eq!(&out.data[..4], &[2, 1, 4, 3]);
625 assert_eq!((out.data[4], out.data[5]), (10, 20));
626 assert!(Orientation::UPRIGHT.is_upright());
627 assert!(!Orientation { quarter_turns: 2, hflip: false }.is_upright());
628 }
629
630 #[test]
631 fn orient_quarter_turn_spans_tiles() {
632 let (w, h) = (ORIENT_TILE * 2 + 6, ORIENT_TILE + 8);
635 let mut src = I420Buffer::new(w, h);
636 for (i, b) in src.data[..w * h].iter_mut().enumerate() {
637 *b = (i % 251) as u8;
638 }
639 let v = src.view(false);
640 let mut out = I420Buffer::new(2, 2);
641 for (q, hflip) in [(1u8, false), (1, true), (3, false), (3, true)] {
642 orient_i420(&v, Orientation { quarter_turns: q, hflip }, &mut out);
643 assert_eq!((out.width, out.height), (h, w));
644 for dy in 0..w {
645 for dx in 0..h {
646 let (sx, sy) = if q == 1 { (dy, h - 1 - dx) } else { (w - 1 - dy, dx) };
647 let dst_x = if hflip { h - 1 - dx } else { dx };
648 assert_eq!(out.data[dy * h + dst_x], v.y[sy * v.y_stride + sx],
649 "q={} hflip={} dx={} dy={}", q, hflip, dx, dy);
650 }
651 }
652 }
653 }
654
655 #[test]
656 fn orient_swaps_dimensions_for_odd_turns() {
657 let mut src = I420Buffer::new(4, 2);
658 for (i, b) in src.data[..8].iter_mut().enumerate() {
659 *b = i as u8;
660 }
661 let mut out = I420Buffer::new(2, 2);
662 orient_i420(&src.view(true), Orientation { quarter_turns: 1, hflip: false }, &mut out);
663 assert_eq!((out.width, out.height), (2, 4));
664 assert_eq!(&out.data[..8], &[4, 0, 5, 1, 6, 2, 7, 3]);
665 assert!(out.view(true).full_range);
666 }
667
668 #[test]
669 fn downscale_averages() {
670 let mut src = I420Buffer::new(64, 64);
671 for (i, b) in src.data.iter_mut().enumerate() {
672 *b = if i < 64 * 64 { ((i % 64) * 4) as u8 } else { 128 };
673 }
674 let dev = DeviceFormat { width: 16, height: 16, fourcc: V4L2_PIX_FMT_YUV420 };
675 let mut out = vec![0u8; dev.frame_bytes()];
676 assert_eq!(Normalizer::new().write_frame(&src.view(false), &dev, &mut out), dev.frame_bytes());
677 assert!(out[0] < out[8] && out[8] < out[15]);
678 assert!(out[..256].iter().all(|v| *v <= 252));
679 assert!(out[256..].iter().all(|v| *v == 128));
680 }
681}