oxedyne/fe2o3/fe2o3_graphics/src/yuv.rs
3.3 KiB, 5 runs
created by r1870400018:21192, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! One decoded planar picture, and the one way out of it into something anybody can look at. |
| 2 | //! |
| 3 | //! Both video decoders in this crate produce the same thing -- three planes of brightness and |
| 4 | //! colour difference, the colour ones at half the width and half the height -- and for a while they |
| 5 | //! produced it as two unrelated types, with the conversion into red, green and blue written against |
| 6 | //! only one of them. So a caller that had decoded an H.264 frame held a picture with no way to draw |
| 7 | //! it, and a caller that wanted to serve either codec had to know which it had. |
| 8 | //! |
| 9 | //! This module names the shared form. [`Frame`] is the picture, [`Plane`] is one of its components, |
| 10 | //! and [`rgb`] is the conversion; an H.264 picture reaches all three through `From`, which widens |
| 11 | //! its eight-bit samples into the sixteen-bit ones a picture of any depth needs. Nothing here is a |
| 12 | //! second implementation of anything: the type and the conversion are `hevc`'s, and this is where |
| 13 | //! they are named as belonging to neither codec. |
| 14 | //! |
| 15 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 16 | //! Anthropic Claude |
| 17 | |
| 18 | pub use crate::hevc::{ |
| 19 | colour::{ |
| 20 | rgb, |
| 21 | Matrix, |
| 22 | }, |
| 23 | decode::{ |
| 24 | Picture as Frame, |
| 25 | Plane, |
| 26 | }, |
| 27 | }; |
| 28 | |
| 29 | use crate::{ |
| 30 | h264, |
| 31 | hevc, |
| 32 | }; |
| 33 | |
| 34 | impl From<&h264::decode::Picture> for hevc::decode::Picture { |
| 35 | |
| 36 | /// H.264 as this crate reads it is eight bits a sample and the shared plane holds sixteen, so |
| 37 | /// the samples are widened and the depth is stated rather than assumed. The numbers do not |
| 38 | /// change: a sample of 235 is 235 at either width, and the studio range [`rgb`] undoes is the |
| 39 | /// same range. |
| 40 | fn from(pic: &h264::decode::Picture) -> Self { |
| 41 | let plane = |p: &h264::decode::Plane| hevc::decode::Plane { |
| 42 | w: p.w, |
| 43 | h: p.h, |
| 44 | px: p.px.iter().map(|v| *v as u16).collect(), |
| 45 | }; |
| 46 | Self { |
| 47 | y: plane(&pic.y), |
| 48 | cb: plane(&pic.cb), |
| 49 | cr: plane(&pic.cr), |
| 50 | depth: 8, |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | #[cfg(test)] |
| 56 | mod tests { |
| 57 | use super::*; |
| 58 | |
| 59 | use oxedyne_fe2o3_core::prelude::*; |
| 60 | |
| 61 | fn flat(w: usize, h: usize, y: u8, cb: u8, cr: u8) -> h264::decode::Picture { |
| 62 | let plane = |w: usize, h: usize, v: u8| h264::decode::Plane { w, h, px: vec![v; w * h] }; |
| 63 | h264::decode::Picture { |
| 64 | y: plane(w, h, y), |
| 65 | cb: plane(w / 2, h / 2, cb), |
| 66 | cr: plane(w / 2, h / 2, cr), |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | #[test] |
| 71 | fn test_an_h264_picture_reaches_the_shared_conversion_00() -> Outcome<()> { |
| 72 | // The point of the module: a picture out of the other decoder is drawable at all, and it |
| 73 | // comes out with the same studio range undone. A conversion that read the widened samples |
| 74 | // as though they were ten-bit would put this well away from white. |
| 75 | let white: Frame = (&flat(8, 8, 235, 128, 128)).into(); |
| 76 | req!(white.depth, 8u32); |
| 77 | req!(white.y.w, 8usize); |
| 78 | let px = res!(rgb(&white, Matrix::Hd, false)); |
| 79 | req!(px.data()[0], 255u8, "the brightest studio level is not white"); |
| 80 | let black: Frame = (&flat(8, 8, 16, 128, 128)).into(); |
| 81 | let px = res!(rgb(&black, Matrix::Hd, false)); |
| 82 | req!(px.data()[0], 0u8, "the darkest studio level is not black"); |
| 83 | Ok(()) |
| 84 | } |
| 85 | |
| 86 | #[test] |
| 87 | fn test_widening_changes_no_sample_01() -> Outcome<()> { |
| 88 | // Every sample the same number at either width, and the colour planes still half size. |
| 89 | let src = flat(4, 4, 90, 40, 200); |
| 90 | let wide: Frame = (&src).into(); |
| 91 | for (a, b) in src.y.px.iter().zip(wide.y.px.iter()) { |
| 92 | req!(*b, *a as u16); |
| 93 | } |
| 94 | req!(wide.cb.w, 2usize); |
| 95 | req!(wide.cr.h, 2usize); |
| 96 | Ok(()) |
| 97 | } |
| 98 | } |