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