1use crate::decoder::YuvFrame;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum VideoTransferMatrix {
13 Bt601,
14 Bt709,
15}
16
17impl VideoTransferMatrix {
18 #[inline]
24 pub const fn for_unspecified_source(height: u32) -> Self {
25 if height > 576 {
26 Self::Bt709
27 } else {
28 Self::Bt601
29 }
30 }
31}
32
33#[derive(Clone, Copy)]
34struct LimitedRangeCoefficients {
35 rv: i32,
36 gu: i32,
37 gv: i32,
38 bu: i32,
39}
40
41impl VideoTransferMatrix {
42 #[inline]
43 const fn limited_range_coefficients(self) -> LimitedRangeCoefficients {
44 match self {
45 Self::Bt601 => LimitedRangeCoefficients {
49 rv: 409,
50 gu: 100,
51 gv: 208,
52 bu: 516,
53 },
54 Self::Bt709 => LimitedRangeCoefficients {
58 rv: 459,
59 gu: 55,
60 gv: 136,
61 bu: 541,
62 },
63 }
64 }
65}
66
67#[inline]
72pub fn yuv_limited_to_rgb(y: u8, cb: u8, cr: u8, matrix: VideoTransferMatrix) -> [u8; 3] {
73 let coeff = matrix.limited_range_coefficients();
74 let c = (y as i32 - 16).max(0);
75 let d = cb as i32 - 128;
76 let e = cr as i32 - 128;
77
78 let r = ((298 * c + coeff.rv * e + 128) >> 8).clamp(0, 255) as u8;
79 let g = ((298 * c - coeff.gu * d - coeff.gv * e + 128) >> 8).clamp(0, 255) as u8;
80 let b = ((298 * c + coeff.bu * d + 128) >> 8).clamp(0, 255) as u8;
81 [r, g, b]
82}
83
84pub fn yuv420p_to_rgb(frame: &YuvFrame) -> Vec<u8> {
87 let width = frame.width as usize;
88 let height = frame.height as usize;
89 let chroma_width = width / 2;
90 let matrix = VideoTransferMatrix::for_unspecified_source(frame.height);
91 let mut rgb = vec![0u8; width.saturating_mul(height).saturating_mul(3)];
92
93 for y in 0..height {
94 for x in 0..width {
95 let luma = frame.y.get(y * width + x).copied().unwrap_or(16);
96 let chroma_index = (y / 2).saturating_mul(chroma_width).saturating_add(x / 2);
97 let cb = frame.cb.get(chroma_index).copied().unwrap_or(128);
98 let cr = frame.cr.get(chroma_index).copied().unwrap_or(128);
99 let [r, g, b] = yuv_limited_to_rgb(luma, cb, cr, matrix);
100 let out = (y * width + x) * 3;
101 rgb[out] = r;
102 rgb[out + 1] = g;
103 rgb[out + 2] = b;
104 }
105 }
106
107 rgb
108}
109
110pub fn yuv420p_to_rgba(frame: &YuvFrame) -> Vec<u8> {
113 yuv420p_to_rgba_scaled(frame, frame.width, frame.height)
114}
115
116pub fn yuv420p_to_rgba_scaled(frame: &YuvFrame, output_width: u32, output_height: u32) -> Vec<u8> {
119 let width = frame.width as usize;
120 let height = frame.height as usize;
121 let output_width = output_width as usize;
122 let output_height = output_height as usize;
123 let chroma_width = width / 2;
124 let matrix = VideoTransferMatrix::for_unspecified_source(frame.height);
125 let mut rgba = vec![0u8; output_width.saturating_mul(output_height).saturating_mul(4)];
126
127 for out_y in 0..output_height {
128 let y = out_y * height / output_height;
129 for out_x in 0..output_width {
130 let x = out_x * width / output_width;
131 let luma = frame.y.get(y * width + x).copied().unwrap_or(16);
132 let chroma_index = (y / 2).saturating_mul(chroma_width).saturating_add(x / 2);
133 let cb = frame.cb.get(chroma_index).copied().unwrap_or(128);
134 let cr = frame.cr.get(chroma_index).copied().unwrap_or(128);
135 let [r, g, b] = yuv_limited_to_rgb(luma, cb, cr, matrix);
136 let out = (out_y * output_width + out_x) * 4;
137 rgba[out] = r;
138 rgba[out + 1] = g;
139 rgba[out + 2] = b;
140 rgba[out + 3] = 255;
141 }
142 }
143
144 rgba
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn windows_unspecified_matrix_switches_above_576_lines() {
153 assert_eq!(
154 VideoTransferMatrix::for_unspecified_source(480),
155 VideoTransferMatrix::Bt601
156 );
157 assert_eq!(
158 VideoTransferMatrix::for_unspecified_source(576),
159 VideoTransferMatrix::Bt601
160 );
161 assert_eq!(
162 VideoTransferMatrix::for_unspecified_source(577),
163 VideoTransferMatrix::Bt709
164 );
165 assert_eq!(
166 VideoTransferMatrix::for_unspecified_source(1080),
167 VideoTransferMatrix::Bt709
168 );
169 }
170
171 #[test]
172 fn studio_black_and_white_are_matrix_independent() {
173 for matrix in [VideoTransferMatrix::Bt601, VideoTransferMatrix::Bt709] {
174 assert_eq!(yuv_limited_to_rgb(16, 128, 128, matrix), [0, 0, 0]);
175 assert_eq!(yuv_limited_to_rgb(235, 128, 128, matrix), [255, 255, 255]);
176 }
177 }
178
179 #[test]
180 fn bt601_and_bt709_use_different_chroma_matrices() {
181 let sample = (100, 90, 200);
182 assert_ne!(
183 yuv_limited_to_rgb(sample.0, sample.1, sample.2, VideoTransferMatrix::Bt601,),
184 yuv_limited_to_rgb(sample.0, sample.1, sample.2, VideoTransferMatrix::Bt709,),
185 );
186 }
187}