1use alpha2_media::YuvEncoding;
5
6use super::half;
7use super::image::CpuImage;
8use super::workers::Workers;
9use crate::{ReadbackFailure, RenderError, Yuv420pTargets};
10
11#[derive(Copy, Clone)]
13struct Coding {
14 kr: f32,
15 kg: f32,
16 kb: f32,
17 luma_black: f32,
18 luma_span: f32,
19 chroma_center: f32,
20 chroma_span: f32,
21 cosited: [bool; 2],
22}
23
24impl Coding {
25 fn new(encoding: YuvEncoding) -> Self {
26 let values = crate::readback::yuv_uniform(encoding);
27 Self {
28 kr: values[0],
29 kg: values[1],
30 kb: values[2],
31 luma_black: values[4],
32 luma_span: values[5],
33 chroma_center: values[6],
34 chroma_span: values[7],
35 cosited: [values[8] != 0.0, values[9] != 0.0],
36 }
37 }
38
39 #[inline]
40 fn luma(self, rgb: [f32; 3]) -> f32 {
41 rgb[0] * self.kr + rgb[1] * self.kg + rgb[2] * self.kb
42 }
43
44 #[inline]
45 fn yuv(self, rgb: [f32; 3]) -> [f32; 3] {
46 let luma = self.luma(rgb);
47 [
48 self.luma_black + self.luma_span * luma,
49 self.chroma_center + self.chroma_span * (rgb[2] - luma) / (2.0 * (1.0 - self.kb)),
50 self.chroma_center + self.chroma_span * (rgb[0] - luma) / (2.0 * (1.0 - self.kr)),
51 ]
52 }
53}
54
55fn tap_weight(offset: i64, cosited: bool) -> f32 {
58 if cosited {
59 if offset == 0 { 0.5 } else { 0.25 }
60 } else if offset >= 0 {
61 0.5
62 } else {
63 0.0
64 }
65}
66
67fn chroma_taps(coding: Coding) -> Vec<(usize, usize, f32)> {
70 let mut taps = Vec::with_capacity(9);
71 for dy in -1..=1_i64 {
72 let row_weight = tap_weight(dy, coding.cosited[1]);
73 if row_weight == 0.0 {
74 continue;
75 }
76 for dx in -1..=1_i64 {
77 let column_weight = tap_weight(dx, coding.cosited[0]);
78 if column_weight == 0.0 {
79 continue;
80 }
81 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
82 taps.push((
83 (dy + 1) as usize,
84 (dx + 1) as usize,
85 row_weight * column_weight,
86 ));
87 }
88 }
89 taps
90}
91
92fn chroma_row(
95 lines: &[Vec<[f32; 3]>; 3],
96 coding: Coding,
97 taps: &[(usize, usize, f32)],
98 u: &mut [u8],
99 v: &mut [u8],
100) {
101 for (column, (u, v)) in u.iter_mut().zip(v.iter_mut()).enumerate() {
102 let x = column * 2;
103 let mut rgb = [0.0_f32; 3];
104 if !coding.cosited[0] && !coding.cosited[1] {
105 for sample in [lines[1][x], lines[1][x + 1], lines[2][x], lines[2][x + 1]] {
106 for channel in 0..3 {
107 rgb[channel] += sample[channel];
108 }
109 }
110 rgb = rgb.map(|value| value * 0.25);
111 } else {
112 for &(line, offset, weight) in taps {
113 let sample = lines[line][(x + offset).saturating_sub(1)];
115 for channel in 0..3 {
116 rgb[channel] += sample[channel] * weight;
117 }
118 }
119 }
120 let yuv = coding.yuv(rgb);
121 *u = half::unorm8(yuv[1]);
122 *v = half::unorm8(yuv[2]);
123 }
124}
125
126pub(crate) fn pack_yuv420p(
131 workers: &Workers,
132 picture: &CpuImage,
133 encoding: YuvEncoding,
134 targets: &mut Yuv420pTargets<'_>,
135) -> Result<(), RenderError> {
136 let size = picture.size();
137 if !size.width.is_multiple_of(2) || !size.height.is_multiple_of(2) {
138 return Err(RenderError::TextureReadback(
139 ReadbackFailure::UnsupportedTarget,
140 ));
141 }
142 let (width, height) = (size.width as usize, size.height as usize);
143 let (chroma_width, chroma_height) = (width / 2, height / 2);
144 targets.y.fits(width, height)?;
145 targets.u.fits(chroma_width, chroma_height)?;
146 targets.v.fits(chroma_width, chroma_height)?;
147 let coding = Coding::new(encoding);
148 let taps = chroma_taps(coding);
149 let (y_stride, u_stride, v_stride) = (targets.y.stride, targets.u.stride, targets.v.stride);
150 let mut lumas = targets.y.bytes.chunks_mut(y_stride);
152 let mut rows = Vec::with_capacity(chroma_height);
153 for (u, v) in targets
154 .u
155 .bytes
156 .chunks_mut(u_stride)
157 .zip(targets.v.bytes.chunks_mut(v_stride))
158 .take(chroma_height)
159 {
160 let top = lumas.next().expect("the luma plane fits");
161 let bottom = lumas.next().expect("the luma plane fits");
162 rows.push((top, bottom, u, v));
163 }
164 workers.bands(&mut rows, 1, |first, band| {
165 let mut lines = [
167 vec![[0.0_f32; 3]; width],
168 vec![[0.0_f32; 3]; width],
169 vec![[0.0_f32; 3]; width],
170 ];
171 let mut decoded = vec![[0.0_f32; 4]; width];
172 for (index, (top, bottom, u, v)) in band.iter_mut().enumerate() {
173 let row = first + index;
174 let first_line = usize::from(!coding.cosited[1]);
176 for (offset, line) in lines.iter_mut().enumerate().skip(first_line) {
177 let y = (row * 2 + offset).saturating_sub(1).min(height - 1);
178 #[allow(clippy::cast_possible_truncation)]
179 picture.decode_row(y as u32, 0, &mut decoded);
180 for (rgb, texel) in line.iter_mut().zip(&decoded) {
181 *rgb = [
182 texel[0].clamp(0.0, 1.0),
183 texel[1].clamp(0.0, 1.0),
184 texel[2].clamp(0.0, 1.0),
185 ];
186 }
187 }
188 for (luma_row, line) in [(&mut **top, &lines[1]), (&mut **bottom, &lines[2])] {
189 for (out, rgb) in luma_row[..width].iter_mut().zip(line) {
190 *out = half::unorm8(coding.luma_black + coding.luma_span * coding.luma(*rgb));
191 }
192 }
193 chroma_row(
194 &lines,
195 coding,
196 &taps,
197 &mut u[..chroma_width],
198 &mut v[..chroma_width],
199 );
200 }
201 });
202 Ok(())
203}
204
205pub fn pack_cpu_image(
213 picture: &CpuImage,
214 encoding: YuvEncoding,
215 layout: alpha2_media::PixelLayout,
216 targets: &mut [crate::PlaneTarget<'_>],
217) -> Result<(), RenderError> {
218 let size = picture.size();
219 let row = |y: usize, out: &mut [[f32; 4]]| {
220 #[allow(clippy::cast_possible_truncation)]
221 picture.decode_row(y as u32, 0, out);
222 };
223 let rows = crate::layout_pack::Picture {
224 width: size.width as usize,
225 height: size.height as usize,
226 row: &row,
227 };
228 crate::layout_pack::pack(&rows, encoding, layout, targets)
229}