1use yuv::{BufferStoreMut, YuvPackedImageMut, YuvPlanarImage};
11
12use super::ring::{V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUYV};
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 {
71 width,
72 height,
73 data: Vec::new(),
74 };
75 b.resize(width, height);
76 b
77 }
78
79 pub fn resize(&mut self, width: usize, height: usize) {
80 self.width = width;
81 self.height = height;
82 self.data.resize(
83 width * height + 2 * (width.div_ceil(2) * height.div_ceil(2)),
84 0,
85 );
86 }
87
88 pub fn y_len(&self) -> usize {
89 self.width * self.height
90 }
91
92 pub fn uv_len(&self) -> usize {
93 self.width.div_ceil(2) * self.height.div_ceil(2)
94 }
95
96 pub fn view(&self, full_range: bool) -> I420View<'_> {
97 let (y, rest) = self.data.split_at(self.y_len());
98 let (u, v) = rest.split_at(self.uv_len());
99 I420View {
100 width: self.width,
101 height: self.height,
102 y,
103 u,
104 v,
105 y_stride: self.width,
106 uv_stride: self.width.div_ceil(2),
107 full_range,
108 }
109 }
110
111 pub fn fill_black(&mut self, full_range: bool) {
113 let y_len = self.y_len();
114 self.data[..y_len].fill(if full_range { 0 } else { 16 });
115 self.data[y_len..].fill(128);
116 }
117}
118
119struct RangeLut {
121 luma: [u8; 256],
122 chroma: [u8; 256],
123 luma_up: [u8; 256],
124 chroma_up: [u8; 256],
125}
126
127fn range_lut() -> &'static RangeLut {
128 static LUT: std::sync::OnceLock<RangeLut> = std::sync::OnceLock::new();
129 LUT.get_or_init(|| {
130 let mut luma = [0u8; 256];
131 let mut chroma = [0u8; 256];
132 let mut luma_up = [0u8; 256];
133 let mut chroma_up = [0u8; 256];
134 for i in 0..256usize {
135 luma[i] = (16 + (i * 219 + 127) / 255) as u8;
136 let c = i as i32 - 128;
137 let scaled = if c >= 0 {
138 (c * 224 + 127) / 255
139 } else {
140 -((-c * 224 + 127) / 255)
141 };
142 chroma[i] = (128 + scaled) as u8;
143 let ly = i as i32 - 16;
144 luma_up[i] = ((ly * 255 + 109) / 219).clamp(0, 255) as u8;
145 let up = if c >= 0 {
146 (c * 255 + 112) / 224
147 } else {
148 -((-c * 255 + 112) / 224)
149 };
150 chroma_up[i] = (128 + up).clamp(0, 255) as u8;
151 }
152 RangeLut {
153 luma,
154 chroma,
155 luma_up,
156 chroma_up,
157 }
158 })
159}
160
161fn range_luts(
165 src_full: bool,
166 dev: &DeviceFormat,
167) -> (Option<&'static [u8; 256]>, Option<&'static [u8; 256]>) {
168 let lut = range_lut();
169 match (src_full, dev.full_range()) {
170 (true, false) => (Some(&lut.luma), Some(&lut.chroma)),
171 (false, true) => (Some(&lut.luma_up), Some(&lut.chroma_up)),
172 _ => (None, None),
173 }
174}
175
176fn copy_plane(
178 src: &[u8],
179 src_stride: usize,
180 width: usize,
181 height: usize,
182 dst: &mut [u8],
183 dst_stride: usize,
184 lut: Option<&[u8; 256]>,
185) {
186 for row in 0..height {
187 let s = &src[row * src_stride..row * src_stride + width];
188 let d = &mut dst[row * dst_stride..row * dst_stride + width];
189 match lut {
190 Some(t) => {
191 for (o, i) in d.iter_mut().zip(s) {
192 *o = t[*i as usize];
193 }
194 }
195 None => d.copy_from_slice(s),
196 }
197 }
198}
199
200fn halve_plane(
203 src: &[u8],
204 sw: usize,
205 sh: usize,
206 sstride: usize,
207 dst: &mut Vec<u8>,
208) -> (usize, usize) {
209 let dw = (sw / 2).max(1);
210 let dh = (sh / 2).max(1);
211 dst.resize(dw * dh, 0);
212 for y in 0..dh {
213 let r0 = &src[(2 * y).min(sh - 1) * sstride..];
214 let r1 = &src[(2 * y + 1).min(sh - 1) * sstride..];
215 let out = &mut dst[y * dw..(y + 1) * dw];
216 for (x, o) in out.iter_mut().enumerate() {
217 let x0 = (2 * x).min(sw - 1);
218 let x1 = (2 * x + 1).min(sw - 1);
219 *o = ((u32::from(r0[x0])
220 + u32::from(r0[x1])
221 + u32::from(r1[x0])
222 + u32::from(r1[x1])
223 + 2)
224 / 4) as u8;
225 }
226 }
227 (dw, dh)
228}
229
230#[allow(clippy::too_many_arguments)]
232fn bilinear_plane(
233 src: &[u8],
234 sw: usize,
235 sh: usize,
236 sstride: usize,
237 dst: &mut [u8],
238 dw: usize,
239 dh: usize,
240 dstride: usize,
241) {
242 if sw == 0 || sh == 0 || dw == 0 || dh == 0 {
243 return;
244 }
245 if sw == dw && sh == dh {
246 copy_plane(src, sstride, sw, sh, dst, dstride, None);
247 return;
248 }
249 let xs: Vec<(usize, usize, u32)> = (0..dw)
250 .map(|x| {
251 let fx =
252 ((x as u64 * 2 + 1) * sw as u64 * 65536 / (dw as u64 * 2)).saturating_sub(32768);
253 let x0 = (fx >> 16) as usize;
254 let frac = (fx & 0xFFFF) as u32;
255 let x0 = x0.min(sw - 1);
256 let x1 = (x0 + 1).min(sw - 1);
257 (x0, x1, frac)
258 })
259 .collect();
260 for y in 0..dh {
261 let fy = ((y as u64 * 2 + 1) * sh as u64 * 65536 / (dh as u64 * 2)).saturating_sub(32768);
262 let y0 = ((fy >> 16) as usize).min(sh - 1);
263 let y1 = (y0 + 1).min(sh - 1);
264 let wy = (fy & 0xFFFF) as u32;
265 let r0 = &src[y0 * sstride..y0 * sstride + sw];
266 let r1 = &src[y1 * sstride..y1 * sstride + sw];
267 let out = &mut dst[y * dstride..y * dstride + dw];
268 for (o, &(x0, x1, wx)) in out.iter_mut().zip(&xs) {
269 let top = u32::from(r0[x0]) * (65536 - wx) + u32::from(r0[x1]) * wx;
270 let bot = u32::from(r1[x0]) * (65536 - wx) + u32::from(r1[x1]) * wx;
271 let v = ((top >> 8) * (65536 - wy) + (bot >> 8) * wy + (1 << 23)) >> 24;
272 *o = v.min(255) as u8;
273 }
274 }
275}
276
277#[allow(clippy::too_many_arguments)]
280fn scale_plane(
281 src: &[u8],
282 sw: usize,
283 sh: usize,
284 sstride: usize,
285 dst: &mut [u8],
286 dw: usize,
287 dh: usize,
288 tmp: &mut [Vec<u8>; 2],
289) {
290 let mut cur: Option<(usize, usize, usize)> = None;
291 let mut which = 0;
292 let (mut w, mut h, mut stride) = (sw, sh, sstride);
293 while w >= 2 * dw && h >= 2 * dh && w > 1 && h > 1 {
294 let (nw, nh) = {
295 let (a, b) = tmp.split_at_mut(1);
296 let (out, input) = if which == 0 {
297 (&mut b[0], &a[0])
298 } else {
299 (&mut a[0], &b[0])
300 };
301 match cur {
302 None => halve_plane(src, w, h, stride, out),
303 Some(_) => halve_plane(input, w, h, stride, out),
304 }
305 };
306 which ^= 1;
307 w = nw;
308 h = nh;
309 stride = nw;
310 cur = Some((w, h, stride));
311 }
312 match cur {
313 None => bilinear_plane(src, sw, sh, sstride, dst, dw, dh, dw),
314 Some(_) => {
315 let input = if which == 0 { &tmp[0] } else { &tmp[1] };
316 bilinear_plane(input, w, h, stride, dst, dw, dh, dw);
317 }
318 }
319}
320
321pub struct Normalizer {
323 fitted: I420Buffer,
324 fitted_geometry: Option<(usize, usize, usize, usize)>,
325 scaled: [Vec<u8>; 3],
326 halves: [Vec<u8>; 2],
327}
328
329impl Normalizer {
330 pub fn new() -> Self {
331 Normalizer {
332 fitted: I420Buffer::new(2, 2),
333 fitted_geometry: None,
334 scaled: [Vec::new(), Vec::new(), Vec::new()],
335 halves: [Vec::new(), Vec::new()],
336 }
337 }
338
339 pub fn write_frame(&mut self, src: &I420View<'_>, dev: &DeviceFormat, out: &mut [u8]) -> usize {
341 let need = dev.frame_bytes();
342 if out.len() < need || src.width == 0 || src.height == 0 {
343 return 0;
344 }
345 if src.width == dev.width && src.height == dev.height {
346 emit(src, dev, out);
347 return need;
348 }
349 let (dw, dh, ox, oy) = fit(src.width, src.height, dev.width, dev.height);
350 if self.fitted.width != dev.width || self.fitted.height != dev.height {
351 self.fitted.resize(dev.width, dev.height);
352 self.fitted_geometry = None;
353 }
354 if self.fitted_geometry != Some((dw, dh, ox, oy)) {
355 self.fitted.fill_black(dev.full_range());
356 self.fitted_geometry = Some((dw, dh, ox, oy));
357 }
358 let cw = dw.div_ceil(2);
359 let ch = dh.div_ceil(2);
360 self.scaled[0].resize(dw * dh, 0);
361 self.scaled[1].resize(cw * ch, 0);
362 self.scaled[2].resize(cw * ch, 0);
363 scale_plane(
364 src.y,
365 src.width,
366 src.height,
367 src.y_stride,
368 &mut self.scaled[0],
369 dw,
370 dh,
371 &mut self.halves,
372 );
373 scale_plane(
374 src.u,
375 src.chroma_width(),
376 src.chroma_height(),
377 src.uv_stride,
378 &mut self.scaled[1],
379 cw,
380 ch,
381 &mut self.halves,
382 );
383 scale_plane(
384 src.v,
385 src.chroma_width(),
386 src.chroma_height(),
387 src.uv_stride,
388 &mut self.scaled[2],
389 cw,
390 ch,
391 &mut self.halves,
392 );
393 let (y_lut, c_lut) = range_luts(src.full_range, dev);
394 let fw = self.fitted.width;
395 let fcw = fw.div_ceil(2);
396 let y_len = self.fitted.y_len();
397 let uv_len = self.fitted.uv_len();
398 let (yp, rest) = self.fitted.data.split_at_mut(y_len);
399 let (up, vp) = rest.split_at_mut(uv_len);
400 copy_plane(
401 &self.scaled[0],
402 dw,
403 dw,
404 dh,
405 &mut yp[oy * fw + ox..],
406 fw,
407 y_lut,
408 );
409 copy_plane(
410 &self.scaled[1],
411 cw,
412 cw,
413 ch,
414 &mut up[(oy / 2) * fcw + ox / 2..],
415 fcw,
416 c_lut,
417 );
418 copy_plane(
419 &self.scaled[2],
420 cw,
421 cw,
422 ch,
423 &mut vp[(oy / 2) * fcw + ox / 2..],
424 fcw,
425 c_lut,
426 );
427 let fitted = self.fitted.view(dev.full_range());
428 emit(&fitted, dev, out);
429 need
430 }
431}
432
433impl Default for Normalizer {
434 fn default() -> Self {
435 Self::new()
436 }
437}
438
439#[derive(Clone, Copy, PartialEq, Eq, Debug)]
445pub struct Orientation {
446 pub quarter_turns: u8,
448 pub hflip: bool,
450}
451
452impl Orientation {
453 pub const UPRIGHT: Orientation = Orientation {
454 quarter_turns: 0,
455 hflip: false,
456 };
457
458 pub fn is_upright(&self) -> bool {
459 self.quarter_turns.is_multiple_of(4) && !self.hflip
460 }
461}
462
463const ORIENT_TILE: usize = 32;
466
467fn orient_plane(src: &[u8], stride: usize, w: usize, h: usize, o: Orientation, dst: &mut [u8]) {
475 let q = o.quarter_turns % 4;
476 let (dw, dh) = if q % 2 == 1 { (h, w) } else { (w, h) };
477 if q.is_multiple_of(2) {
478 let reversed = (q == 2) != o.hflip;
479 for dy in 0..dh {
480 let sy = if q == 2 { h - 1 - dy } else { dy };
481 let row = &src[sy * stride..sy * stride + w];
482 let out = &mut dst[dy * dw..dy * dw + dw];
483 if reversed {
484 for (dx, o) in out.iter_mut().enumerate() {
485 *o = row[w - 1 - dx];
486 }
487 } else {
488 out.copy_from_slice(row);
489 }
490 }
491 return;
492 }
493 for ty in (0..dh).step_by(ORIENT_TILE) {
494 for tx in (0..dw).step_by(ORIENT_TILE) {
495 for dy in ty..(ty + ORIENT_TILE).min(dh) {
496 let row = &mut dst[dy * dw..(dy + 1) * dw];
497 for dx in tx..(tx + ORIENT_TILE).min(dw) {
498 let (sx, sy) = if q == 1 {
499 (dy, h - 1 - dx)
500 } else {
501 (w - 1 - dy, dx)
502 };
503 row[if o.hflip { dw - 1 - dx } else { dx }] = src[sy * stride + sx];
504 }
505 }
506 }
507 }
508}
509
510pub fn orient_i420(src: &I420View<'_>, o: Orientation, out: &mut I420Buffer) {
515 let q = o.quarter_turns % 4;
516 let (dw, dh) = if q % 2 == 1 {
517 (src.height, src.width)
518 } else {
519 (src.width, src.height)
520 };
521 out.resize(dw, dh);
522 let y_len = out.y_len();
523 let uv_len = out.uv_len();
524 let (yp, rest) = out.data.split_at_mut(y_len);
525 let (up, vp) = rest.split_at_mut(uv_len);
526 orient_plane(src.y, src.y_stride, src.width, src.height, o, yp);
527 orient_plane(
528 src.u,
529 src.uv_stride,
530 src.chroma_width(),
531 src.chroma_height(),
532 o,
533 up,
534 );
535 orient_plane(
536 src.v,
537 src.uv_stride,
538 src.chroma_width(),
539 src.chroma_height(),
540 o,
541 vp,
542 );
543}
544
545pub fn fit(sw: usize, sh: usize, dw: usize, dh: usize) -> (usize, usize, usize, usize) {
548 let (mut w, mut h) = if sw * dh > dw * sh {
549 (dw, (sh * dw / sw).max(2))
550 } else {
551 ((sw * dh / sh).max(2), dh)
552 };
553 w = (w.min(dw) / 2) * 2;
554 h = (h.min(dh) / 2) * 2;
555 let ox = ((dw - w) / 2 / 2) * 2;
556 let oy = ((dh - h) / 2 / 2) * 2;
557 (w, h, ox, oy)
558}
559
560fn emit(src: &I420View<'_>, dev: &DeviceFormat, out: &mut [u8]) {
562 let (y_lut, c_lut) = range_luts(src.full_range, dev);
563 let w = dev.width;
564 let h = dev.height;
565 let cw = w.div_ceil(2);
566 let ch = h.div_ceil(2);
567 match dev.fourcc {
568 V4L2_PIX_FMT_NV12 => {
569 let (yp, uvp) = out.split_at_mut(w * h);
570 copy_plane(src.y, src.y_stride, w, h, yp, w, y_lut);
571 for row in 0..ch {
572 let u = &src.u[row * src.uv_stride..row * src.uv_stride + cw];
573 let v = &src.v[row * src.uv_stride..row * src.uv_stride + cw];
574 let o = &mut uvp[row * cw * 2..(row + 1) * cw * 2];
575 for x in 0..cw {
576 let (uu, vv) = match c_lut {
577 Some(t) => (t[u[x] as usize], t[v[x] as usize]),
578 None => (u[x], v[x]),
579 };
580 o[2 * x] = uu;
581 o[2 * x + 1] = vv;
582 }
583 }
584 }
585 V4L2_PIX_FMT_YUYV => {
586 let planar = if src.full_range {
587 None
588 } else {
589 Some(YuvPlanarImage {
590 y_plane: src.y,
591 y_stride: src.y_stride as u32,
592 u_plane: src.u,
593 u_stride: src.uv_stride as u32,
594 v_plane: src.v,
595 v_stride: src.uv_stride as u32,
596 width: w as u32,
597 height: h as u32,
598 })
599 };
600 match planar {
601 Some(p) => {
602 let mut packed = YuvPackedImageMut {
603 yuy: BufferStoreMut::Borrowed(&mut out[..w * 2 * h]),
604 yuy_stride: (w * 2) as u32,
605 width: w as u32,
606 height: h as u32,
607 };
608 if yuv::yuv420_to_yuyv422(&mut packed, &p).is_ok() {
609 return;
610 }
611 yuyv_scalar(src, w, h, out, y_lut, c_lut);
612 }
613 None => yuyv_scalar(src, w, h, out, y_lut, c_lut),
614 }
615 }
616 _ => {
617 let (yp, rest) = out.split_at_mut(w * h);
618 let (up, vp) = rest.split_at_mut(cw * ch);
619 copy_plane(src.y, src.y_stride, w, h, yp, w, y_lut);
620 copy_plane(src.u, src.uv_stride, cw, ch, up, cw, c_lut);
621 copy_plane(src.v, src.uv_stride, cw, ch, vp, cw, c_lut);
622 }
623 }
624}
625
626fn yuyv_scalar(
627 src: &I420View<'_>,
628 w: usize,
629 h: usize,
630 out: &mut [u8],
631 y_lut: Option<&[u8; 256]>,
632 c_lut: Option<&[u8; 256]>,
633) {
634 let cw = w.div_ceil(2);
635 for row in 0..h {
636 let y = &src.y[row * src.y_stride..row * src.y_stride + w];
637 let u = &src.u[(row / 2) * src.uv_stride..(row / 2) * src.uv_stride + cw];
638 let v = &src.v[(row / 2) * src.uv_stride..(row / 2) * src.uv_stride + cw];
639 let o = &mut out[row * w * 2..(row + 1) * w * 2];
640 for x in 0..w {
641 let yy = match y_lut {
642 Some(t) => t[y[x] as usize],
643 None => y[x],
644 };
645 let c = if x % 2 == 0 { u[x / 2] } else { v[x / 2] };
646 let cc = match c_lut {
647 Some(t) => t[c as usize],
648 None => c,
649 };
650 o[2 * x] = yy;
651 o[2 * x + 1] = cc;
652 }
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::super::ring::V4L2_PIX_FMT_YUV420;
659 use super::*;
660
661 fn solid(width: usize, height: usize, y: u8, u: u8, v: u8) -> I420Buffer {
662 let mut b = I420Buffer::new(width, height);
663 let yl = b.y_len();
664 let ul = b.uv_len();
665 b.data[..yl].fill(y);
666 b.data[yl..yl + ul].fill(u);
667 b.data[yl + ul..].fill(v);
668 b
669 }
670
671 #[test]
672 fn mjpeg_device_takes_full_range_samples() {
673 let mjpeg = DeviceFormat {
674 width: 4,
675 height: 2,
676 fourcc: V4L2_PIX_FMT_MJPEG,
677 };
678 let raw = DeviceFormat {
679 width: 4,
680 height: 2,
681 fourcc: V4L2_PIX_FMT_YUV420,
682 };
683 let mut limited = I420Buffer::new(4, 2);
684 limited.data[..8].fill(16);
685 limited.data[8..].fill(128);
686 let mut out = vec![0u8; mjpeg.frame_bytes()];
687 let mut n = Normalizer::new();
688 assert_eq!(
689 n.write_frame(&limited.view(false), &mjpeg, &mut out),
690 mjpeg.frame_bytes()
691 );
692 assert_eq!((out[0], out[8]), (0, 128));
693 limited.data[..8].fill(235);
694 n.write_frame(&limited.view(false), &mjpeg, &mut out);
695 assert_eq!(out[0], 255);
696 let mut full = I420Buffer::new(4, 2);
697 full.data[..8].fill(200);
698 full.data[8..].fill(60);
699 n.write_frame(&full.view(true), &mjpeg, &mut out);
700 assert_eq!((out[0], out[8]), (200, 60));
701 n.write_frame(&full.view(true), &raw, &mut out);
702 assert_eq!(
703 (out[0], out[8]),
704 (range_lut().luma[200], range_lut().chroma[60])
705 );
706 }
707
708 #[test]
709 fn fit_preserves_aspect_and_alignment() {
710 assert_eq!(fit(1280, 720, 1280, 720), (1280, 720, 0, 0));
711 assert_eq!(fit(640, 480, 1280, 720), (960, 720, 160, 0));
712 assert_eq!(fit(720, 1280, 1280, 720), (404, 720, 438, 0));
713 assert_eq!(fit(1920, 1080, 640, 480), (640, 360, 0, 60));
714 let (w, h, ox, oy) = fit(333, 777, 1280, 720);
715 assert!(w % 2 == 0 && h % 2 == 0 && ox % 2 == 0 && oy % 2 == 0);
716 assert!(w + ox <= 1280 && h + oy <= 720);
717 }
718
719 #[test]
720 fn same_size_i420_is_a_plain_copy() {
721 let src = solid(8, 4, 100, 60, 200);
722 let dev = DeviceFormat {
723 width: 8,
724 height: 4,
725 fourcc: V4L2_PIX_FMT_YUV420,
726 };
727 let mut out = vec![0u8; dev.frame_bytes() + 7];
728 let n = Normalizer::new().write_frame(&src.view(false), &dev, &mut out);
729 assert_eq!(n, 8 * 4 * 3 / 2);
730 assert_eq!(&out[..n], &src.data[..]);
731 }
732
733 #[test]
734 fn full_range_is_compressed_to_limited() {
735 let src = solid(4, 2, 255, 0, 255);
736 let dev = DeviceFormat {
737 width: 4,
738 height: 2,
739 fourcc: V4L2_PIX_FMT_YUV420,
740 };
741 let mut out = vec![0u8; dev.frame_bytes()];
742 Normalizer::new().write_frame(&src.view(true), &dev, &mut out);
743 assert_eq!(out[0], 235);
744 assert_eq!(out[8], 16);
745 assert_eq!(out[10], 240);
746 let black = solid(4, 2, 0, 128, 128);
747 Normalizer::new().write_frame(&black.view(true), &dev, &mut out);
748 assert_eq!((out[0], out[8], out[10]), (16, 128, 128));
749 }
750
751 #[test]
752 fn nv12_and_yuyv_interleave() {
753 let src = solid(4, 2, 50, 60, 70);
754 let nv12 = DeviceFormat {
755 width: 4,
756 height: 2,
757 fourcc: V4L2_PIX_FMT_NV12,
758 };
759 let mut out = vec![0u8; nv12.frame_bytes()];
760 assert_eq!(
761 Normalizer::new().write_frame(&src.view(false), &nv12, &mut out),
762 12
763 );
764 assert_eq!(&out[..8], &[50; 8]);
765 assert_eq!(&out[8..], &[60, 70, 60, 70]);
766 let yuyv = DeviceFormat {
767 width: 4,
768 height: 2,
769 fourcc: V4L2_PIX_FMT_YUYV,
770 };
771 let mut out = vec![0u8; yuyv.frame_bytes()];
772 assert_eq!(
773 Normalizer::new().write_frame(&src.view(false), &yuyv, &mut out),
774 16
775 );
776 assert_eq!(&out[..8], &[50, 60, 50, 70, 50, 60, 50, 70]);
777 let mut out2 = vec![0u8; yuyv.frame_bytes()];
778 Normalizer::new().write_frame(&src.view(true), &yuyv, &mut out2);
779 assert_eq!(out2[0], range_lut().luma[50]);
780 assert_eq!(out2[1], range_lut().chroma[60]);
781 }
782
783 #[test]
784 fn letterbox_scales_and_pads_black() {
785 let src = solid(8, 8, 200, 90, 160);
786 let dev = DeviceFormat {
787 width: 16,
788 height: 8,
789 fourcc: V4L2_PIX_FMT_YUV420,
790 };
791 let mut out = vec![0u8; dev.frame_bytes()];
792 let mut n = Normalizer::new();
793 assert_eq!(
794 n.write_frame(&src.view(false), &dev, &mut out),
795 dev.frame_bytes()
796 );
797 assert_eq!(out[0], 16);
798 assert_eq!(out[4 + 3 * 16], 200);
799 assert_eq!(out[15], 16);
800 let uv = 16 * 8;
801 assert_eq!(out[uv], 128);
802 assert_eq!(out[uv + 2 + 8 * 2], 90);
803 assert_eq!(out[uv + 8 * 4 + 2 + 8 * 2], 160);
804 assert_eq!(
805 n.write_frame(&src.view(false), &dev, &mut out),
806 dev.frame_bytes()
807 );
808 assert_eq!(out[4 + 3 * 16], 200);
809 }
810
811 #[test]
812 fn orient_rotates_clockwise_then_mirrors() {
813 let mut src = I420Buffer::new(2, 2);
814 src.data[..4].copy_from_slice(&[1, 2, 3, 4]);
815 src.data[4] = 10;
816 src.data[5] = 20;
817 let mut out = I420Buffer::new(2, 2);
818 let v = src.view(false);
819 orient_i420(
820 &v,
821 Orientation {
822 quarter_turns: 1,
823 hflip: false,
824 },
825 &mut out,
826 );
827 assert_eq!(&out.data[..4], &[3, 1, 4, 2]);
828 orient_i420(
829 &v,
830 Orientation {
831 quarter_turns: 2,
832 hflip: false,
833 },
834 &mut out,
835 );
836 assert_eq!(&out.data[..4], &[4, 3, 2, 1]);
837 orient_i420(
838 &v,
839 Orientation {
840 quarter_turns: 3,
841 hflip: false,
842 },
843 &mut out,
844 );
845 assert_eq!(&out.data[..4], &[2, 4, 1, 3]);
846 orient_i420(
847 &v,
848 Orientation {
849 quarter_turns: 0,
850 hflip: true,
851 },
852 &mut out,
853 );
854 assert_eq!(&out.data[..4], &[2, 1, 4, 3]);
855 assert_eq!((out.data[4], out.data[5]), (10, 20));
856 assert!(Orientation::UPRIGHT.is_upright());
857 assert!(
858 !Orientation {
859 quarter_turns: 2,
860 hflip: false
861 }
862 .is_upright()
863 );
864 }
865
866 #[test]
867 fn orient_quarter_turn_spans_tiles() {
868 let (w, h) = (ORIENT_TILE * 2 + 6, ORIENT_TILE + 8);
871 let mut src = I420Buffer::new(w, h);
872 for (i, b) in src.data[..w * h].iter_mut().enumerate() {
873 *b = (i % 251) as u8;
874 }
875 let v = src.view(false);
876 let mut out = I420Buffer::new(2, 2);
877 for (q, hflip) in [(1u8, false), (1, true), (3, false), (3, true)] {
878 orient_i420(
879 &v,
880 Orientation {
881 quarter_turns: q,
882 hflip,
883 },
884 &mut out,
885 );
886 assert_eq!((out.width, out.height), (h, w));
887 for dy in 0..w {
888 for dx in 0..h {
889 let (sx, sy) = if q == 1 {
890 (dy, h - 1 - dx)
891 } else {
892 (w - 1 - dy, dx)
893 };
894 let dst_x = if hflip { h - 1 - dx } else { dx };
895 assert_eq!(
896 out.data[dy * h + dst_x],
897 v.y[sy * v.y_stride + sx],
898 "q={} hflip={} dx={} dy={}",
899 q,
900 hflip,
901 dx,
902 dy
903 );
904 }
905 }
906 }
907 }
908
909 #[test]
910 fn orient_swaps_dimensions_for_odd_turns() {
911 let mut src = I420Buffer::new(4, 2);
912 for (i, b) in src.data[..8].iter_mut().enumerate() {
913 *b = i as u8;
914 }
915 let mut out = I420Buffer::new(2, 2);
916 orient_i420(
917 &src.view(true),
918 Orientation {
919 quarter_turns: 1,
920 hflip: false,
921 },
922 &mut out,
923 );
924 assert_eq!((out.width, out.height), (2, 4));
925 assert_eq!(&out.data[..8], &[4, 0, 5, 1, 6, 2, 7, 3]);
926 assert!(out.view(true).full_range);
927 }
928
929 #[test]
930 fn downscale_averages() {
931 let mut src = I420Buffer::new(64, 64);
932 for (i, b) in src.data.iter_mut().enumerate() {
933 *b = if i < 64 * 64 {
934 ((i % 64) * 4) as u8
935 } else {
936 128
937 };
938 }
939 let dev = DeviceFormat {
940 width: 16,
941 height: 16,
942 fourcc: V4L2_PIX_FMT_YUV420,
943 };
944 let mut out = vec![0u8; dev.frame_bytes()];
945 assert_eq!(
946 Normalizer::new().write_frame(&src.view(false), &dev, &mut out),
947 dev.frame_bytes()
948 );
949 assert!(out[0] < out[8] && out[8] < out[15]);
950 assert!(out[..256].iter().all(|v| *v <= 252));
951 assert!(out[256..].iter().all(|v| *v == 128));
952 }
953}