Oregami
Repositories/oxedyne/fe2o3

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
18pub use crate::hevc::{
19 colour::{
20 rgb,
21 Matrix,
22 },
23 decode::{
24 Picture as Frame,
25 Plane,
26 },
27};
28
29use crate::{
30 h264,
31 hevc,
32};
33
34impl 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)]
56mod 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}