1use crate::internals::{ProcessedOffset, WideRowAlphaInversionHandler};
30use crate::numerics::{div_by_255, qrshr};
31use crate::yuv_error::check_rgba_destination;
32use crate::yuv_support::*;
33use crate::{YuvError, YuvPlanarImageWithAlpha, YuvRange, YuvStandardMatrix};
34#[cfg(feature = "rayon")]
35use rayon::iter::{IndexedParallelIterator, ParallelIterator};
36#[cfg(feature = "rayon")]
37use rayon::prelude::{ParallelSlice, ParallelSliceMut};
38
39type RgbHandler = unsafe fn(
40 range: &YuvChromaRange,
41 transform: &CbCrInverseTransform<i32>,
42 y_plane: &[u8],
43 u_plane: &[u8],
44 v_plane: &[u8],
45 a_plane: &[u8],
46 rgba: &mut [u8],
47 start_cx: usize,
48 start_ux: usize,
49 width: usize,
50 use_premultiply: bool,
51) -> ProcessedOffset;
52
53struct RgbDecoder<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32> {
54 handler: Option<RgbHandler>,
55}
56
57impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32> Default
58 for RgbDecoder<DESTINATION_CHANNELS, SAMPLING, PRECISION>
59{
60 fn default() -> Self {
61 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
62 {
63 #[cfg(feature = "rdm")]
64 {
65 let is_rdm_available = std::arch::is_aarch64_feature_detected!("rdm");
66 if is_rdm_available && PRECISION == 13 {
67 assert_eq!(PRECISION, 13);
68 use crate::neon::neon_yuv_to_rgba_alpha_rdm;
69 return Self {
70 handler: Some(neon_yuv_to_rgba_alpha_rdm::<DESTINATION_CHANNELS, SAMPLING>),
71 };
72 }
73 }
74 use crate::neon::neon_yuv_to_rgba_alpha;
75 Self {
76 handler: Some(neon_yuv_to_rgba_alpha::<DESTINATION_CHANNELS, SAMPLING>),
77 }
78 }
79 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
80 {
81 if PRECISION != 13 {
82 return Self { handler: None };
83 }
84 assert_eq!(PRECISION, 13);
85 #[cfg(feature = "nightly_avx512")]
86 {
87 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
88 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
89 if use_avx512 {
90 use crate::avx512bw::avx512_yuv_to_rgba_alpha;
91 return Self {
92 handler: Some(if use_vbmi {
93 avx512_yuv_to_rgba_alpha::<DESTINATION_CHANNELS, SAMPLING, true>
94 } else {
95 avx512_yuv_to_rgba_alpha::<DESTINATION_CHANNELS, SAMPLING, false>
96 }),
97 };
98 }
99 }
100 #[cfg(feature = "avx")]
101 {
102 let use_avx2 = std::arch::is_x86_feature_detected!("avx2");
103 if use_avx2 {
104 use crate::avx2::avx2_yuv_to_rgba_alpha;
105 return Self {
106 handler: Some(avx2_yuv_to_rgba_alpha::<DESTINATION_CHANNELS, SAMPLING>),
107 };
108 }
109 }
110 #[cfg(feature = "sse")]
111 {
112 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
113 if use_sse {
114 use crate::sse::sse_yuv_to_rgba_alpha_row;
115 return Self {
116 handler: Some(sse_yuv_to_rgba_alpha_row::<DESTINATION_CHANNELS, SAMPLING>),
117 };
118 }
119 }
120 }
121 #[cfg(not(all(target_arch = "aarch64", target_feature = "neon")))]
122 RgbDecoder { handler: None }
123 }
124}
125
126impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32>
127 WideRowAlphaInversionHandler<u8, u8, i32>
128 for RgbDecoder<DESTINATION_CHANNELS, SAMPLING, PRECISION>
129{
130 fn handle_row(
131 &self,
132 y_plane: &[u8],
133 u_plane: &[u8],
134 v_plane: &[u8],
135 a_plane: &[u8],
136 rgba: &mut [u8],
137 width: u32,
138 chroma: YuvChromaRange,
139 transform: &CbCrInverseTransform<i32>,
140 use_premultiplied_alpha: bool,
141 ) -> ProcessedOffset {
142 if let Some(handler) = self.handler {
143 unsafe {
144 return handler(
145 &chroma,
146 transform,
147 y_plane,
148 u_plane,
149 v_plane,
150 a_plane,
151 rgba,
152 0,
153 0,
154 width as usize,
155 use_premultiplied_alpha,
156 );
157 }
158 }
159 ProcessedOffset { cx: 0, ux: 0 }
160 }
161}
162
163fn yuv_with_alpha_to_rgbx<const DESTINATION_CHANNELS: u8, const SAMPLING: u8>(
164 image: &YuvPlanarImageWithAlpha<u8>,
165 rgba: &mut [u8],
166 rgba_stride: u32,
167 range: YuvRange,
168 matrix: YuvStandardMatrix,
169 premultiply_alpha: bool,
170) -> Result<(), YuvError> {
171 let chroma_subsampling: YuvChromaSubsampling = SAMPLING.into();
172 let dst_chans: YuvSourceChannels = DESTINATION_CHANNELS.into();
173 assert!(
174 dst_chans.has_alpha(),
175 "yuv_with_alpha_to_rgbx cannot be called on configuration without alpha"
176 );
177 let channels = dst_chans.get_channels_count();
178
179 check_rgba_destination(rgba, rgba_stride, image.width, image.height, channels)?;
180 image.check_constraints(chroma_subsampling)?;
181
182 let chroma_range = get_yuv_range(8, range);
183 let kr_kb = matrix.get_kr_kb();
184 const PRECISION: i32 = 13;
185 let inverse_transform =
186 search_inverse_transform(PRECISION, 8, range, matrix, chroma_range, kr_kb);
187
188 let cr_coef = inverse_transform.cr_coef;
189 let cb_coef = inverse_transform.cb_coef;
190 let y_coef = inverse_transform.y_coef;
191 let g_coef_1 = inverse_transform.g_coeff_1;
192 let g_coef_2 = inverse_transform.g_coeff_2;
193
194 let bias_y = chroma_range.bias_y as i32;
195 let bias_uv = chroma_range.bias_uv as i32;
196
197 const BIT_DEPTH: usize = 8;
198
199 let handler = RgbDecoder::<DESTINATION_CHANNELS, SAMPLING, PRECISION>::default();
200
201 let process_halved_chroma_row =
202 |y_plane: &[u8], u_plane: &[u8], v_plane: &[u8], a_plane: &[u8], rgba: &mut [u8]| {
203 let cx = handler
204 .handle_row(
205 y_plane,
206 u_plane,
207 v_plane,
208 a_plane,
209 rgba,
210 image.width,
211 chroma_range,
212 &inverse_transform,
213 premultiply_alpha,
214 )
215 .cx;
216 if cx != image.width as usize {
217 for ((((rgba, y_src), &u_src), &v_src), a_src) in rgba
218 .chunks_exact_mut(channels * 2)
219 .zip(y_plane.chunks_exact(2))
220 .zip(u_plane.iter())
221 .zip(v_plane.iter())
222 .zip(a_plane.chunks_exact(2))
223 .skip(cx / 2)
224 {
225 let y_value0 = (y_src[0] as i32 - bias_y) * y_coef;
226 let cb_value = u_src as i32 - bias_uv;
227 let cr_value = v_src as i32 - bias_uv;
228
229 let mut r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
230 let mut b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
231 let mut g0 = qrshr::<PRECISION, BIT_DEPTH>(
232 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
233 );
234
235 if premultiply_alpha {
236 let a0 = a_src[0];
237 r0 = div_by_255(r0 as u16 * a0 as u16) as i32;
238 g0 = div_by_255(g0 as u16 * a0 as u16) as i32;
239 b0 = div_by_255(b0 as u16 * a0 as u16) as i32;
240 }
241
242 let rgba0 = &mut rgba[0..channels];
243
244 rgba0[dst_chans.get_r_channel_offset()] = r0 as u8;
245 rgba0[dst_chans.get_g_channel_offset()] = g0 as u8;
246 rgba0[dst_chans.get_b_channel_offset()] = b0 as u8;
247 rgba0[dst_chans.get_a_channel_offset()] = a_src[0];
248
249 let y_value1 = (y_src[1] as i32 - bias_y) * y_coef;
250
251 let mut r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value);
252 let mut b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value);
253 let mut g1 = qrshr::<PRECISION, BIT_DEPTH>(
254 y_value1 - g_coef_1 * cr_value - g_coef_2 * cb_value,
255 );
256
257 if premultiply_alpha {
258 let a1 = a_src[1];
259 r1 = div_by_255(r1 as u16 * a1 as u16) as i32;
260 g1 = div_by_255(g1 as u16 * a1 as u16) as i32;
261 b1 = div_by_255(b1 as u16 * a1 as u16) as i32;
262 }
263
264 let rgba1 = &mut rgba[channels..channels * 2];
265
266 rgba1[dst_chans.get_r_channel_offset()] = r1 as u8;
267 rgba1[dst_chans.get_g_channel_offset()] = g1 as u8;
268 rgba1[dst_chans.get_b_channel_offset()] = b1 as u8;
269 rgba1[dst_chans.get_a_channel_offset()] = a_src[1];
270 }
271
272 if image.width & 1 != 0 {
273 let y_value0 = (*y_plane.last().unwrap() as i32 - bias_y) * y_coef;
274 let cb_value = *u_plane.last().unwrap() as i32 - bias_uv;
275 let cr_value = *v_plane.last().unwrap() as i32 - bias_uv;
276 let a_value = *a_plane.last().unwrap();
277 let rgba = rgba.chunks_exact_mut(channels).last().unwrap();
278 let rgba0 = &mut rgba[0..channels];
279
280 let mut r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
281 let mut b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
282 let mut g0 = qrshr::<PRECISION, BIT_DEPTH>(
283 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
284 );
285
286 if premultiply_alpha {
287 let a0 = a_value;
288 r0 = div_by_255(r0 as u16 * a0 as u16) as i32;
289 g0 = div_by_255(g0 as u16 * a0 as u16) as i32;
290 b0 = div_by_255(b0 as u16 * a0 as u16) as i32;
291 }
292
293 rgba0[dst_chans.get_r_channel_offset()] = r0 as u8;
294 rgba0[dst_chans.get_g_channel_offset()] = g0 as u8;
295 rgba0[dst_chans.get_b_channel_offset()] = b0 as u8;
296 rgba0[dst_chans.get_a_channel_offset()] = a_value;
297 }
298 }
299 };
300
301 if chroma_subsampling == YuvChromaSubsampling::Yuv444 {
302 let iter;
303 #[cfg(feature = "rayon")]
304 {
305 iter = rgba
306 .par_chunks_exact_mut(rgba_stride as usize)
307 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
308 .zip(image.a_plane.par_chunks_exact(image.a_stride as usize))
309 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
310 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
311 }
312 #[cfg(not(feature = "rayon"))]
313 {
314 iter = rgba
315 .chunks_exact_mut(rgba_stride as usize)
316 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
317 .zip(image.a_plane.chunks_exact(image.a_stride as usize))
318 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
319 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
320 }
321 iter.for_each(|((((rgba, y_plane), a_plane), u_plane), v_plane)| {
322 let y_plane = &y_plane[0..image.width as usize];
323 let cx = handler
324 .handle_row(
325 y_plane,
326 u_plane,
327 v_plane,
328 a_plane,
329 rgba,
330 image.width,
331 chroma_range,
332 &inverse_transform,
333 premultiply_alpha,
334 )
335 .cx;
336 if cx != image.width as usize {
337 for ((((rgba, &y_src), &u_src), &v_src), &a_src) in rgba
338 .chunks_exact_mut(channels)
339 .zip(y_plane.iter())
340 .zip(u_plane.iter())
341 .zip(v_plane.iter())
342 .zip(a_plane.iter())
343 .skip(cx)
344 {
345 let y_value = (y_src as i32 - bias_y) * y_coef;
346 let cb_value = u_src as i32 - bias_uv;
347 let cr_value = v_src as i32 - bias_uv;
348
349 let mut r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
350 let mut b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
351 let mut g = qrshr::<PRECISION, BIT_DEPTH>(
352 y_value - g_coef_1 * cr_value - g_coef_2 * cb_value,
353 );
354
355 if premultiply_alpha {
356 let a0 = a_src;
357 r = div_by_255(r as u16 * a0 as u16) as i32;
358 b = div_by_255(b as u16 * a0 as u16) as i32;
359 g = div_by_255(g as u16 * a0 as u16) as i32;
360 }
361
362 rgba[dst_chans.get_r_channel_offset()] = r as u8;
363 rgba[dst_chans.get_g_channel_offset()] = g as u8;
364 rgba[dst_chans.get_b_channel_offset()] = b as u8;
365 rgba[dst_chans.get_a_channel_offset()] = a_src;
366 }
367 }
368 });
369 } else if chroma_subsampling == YuvChromaSubsampling::Yuv422 {
370 let iter;
371 #[cfg(feature = "rayon")]
372 {
373 iter = rgba
374 .par_chunks_exact_mut(rgba_stride as usize)
375 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
376 .zip(image.a_plane.par_chunks_exact(image.a_stride as usize))
377 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
378 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
379 }
380 #[cfg(not(feature = "rayon"))]
381 {
382 iter = rgba
383 .chunks_exact_mut(rgba_stride as usize)
384 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
385 .zip(image.a_plane.chunks_exact(image.a_stride as usize))
386 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
387 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
388 }
389 iter.for_each(|((((rgba, y_plane), a_plane), u_plane), v_plane)| {
390 process_halved_chroma_row(
391 &y_plane[0..image.width as usize],
392 &u_plane[0..(image.width as usize).div_ceil(2)],
393 &v_plane[0..(image.width as usize).div_ceil(2)],
394 &a_plane[0..image.width as usize],
395 &mut rgba[0..image.width as usize * channels],
396 );
397 });
398 } else if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
399 let iter;
400 #[cfg(feature = "rayon")]
401 {
402 iter = rgba
403 .par_chunks_exact_mut(rgba_stride as usize * 2)
404 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize * 2))
405 .zip(image.a_plane.par_chunks_exact(image.a_stride as usize * 2))
406 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
407 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
408 }
409 #[cfg(not(feature = "rayon"))]
410 {
411 iter = rgba
412 .chunks_exact_mut(rgba_stride as usize * 2)
413 .zip(image.y_plane.chunks_exact(image.y_stride as usize * 2))
414 .zip(image.a_plane.chunks_exact(image.a_stride as usize * 2))
415 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
416 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
417 }
418 iter.for_each(|((((rgba, y_plane), a_plane), u_plane), v_plane)| {
419 for ((rgba, y_plane), a_plane) in rgba
420 .chunks_exact_mut(rgba_stride as usize)
421 .zip(y_plane.chunks_exact(image.y_stride as usize))
422 .zip(a_plane.chunks_exact(image.a_stride as usize))
423 {
424 process_halved_chroma_row(
425 &y_plane[0..image.width as usize],
426 &u_plane[0..(image.width as usize).div_ceil(2)],
427 &v_plane[0..(image.width as usize).div_ceil(2)],
428 &a_plane[0..image.width as usize],
429 &mut rgba[0..image.width as usize * channels],
430 );
431 }
432 });
433
434 if image.height & 1 != 0 {
435 let rgba = rgba.chunks_exact_mut(rgba_stride as usize).last().unwrap();
436 let u_plane = image
437 .u_plane
438 .chunks_exact(image.u_stride as usize)
439 .last()
440 .unwrap();
441 let v_plane = image
442 .v_plane
443 .chunks_exact(image.v_stride as usize)
444 .last()
445 .unwrap();
446 let a_plane = image
447 .a_plane
448 .chunks_exact(image.a_stride as usize)
449 .last()
450 .unwrap();
451 let y_plane = image
452 .y_plane
453 .chunks_exact(image.y_stride as usize)
454 .last()
455 .unwrap();
456 process_halved_chroma_row(
457 &y_plane[0..image.width as usize],
458 &u_plane[0..(image.width as usize).div_ceil(2)],
459 &v_plane[0..(image.width as usize).div_ceil(2)],
460 &a_plane[0..image.width as usize],
461 &mut rgba[0..image.width as usize * channels],
462 );
463 }
464 } else {
465 unreachable!();
466 }
467
468 Ok(())
469}
470
471pub fn yuv420_alpha_to_rgba(
491 planar_with_alpha: &YuvPlanarImageWithAlpha<u8>,
492 rgba: &mut [u8],
493 rgba_stride: u32,
494 range: YuvRange,
495 matrix: YuvStandardMatrix,
496 premultiply_alpha: bool,
497) -> Result<(), YuvError> {
498 yuv_with_alpha_to_rgbx::<
499 { YuvSourceChannels::Rgba as u8 },
500 { YuvChromaSubsampling::Yuv420 as u8 },
501 >(
502 planar_with_alpha,
503 rgba,
504 rgba_stride,
505 range,
506 matrix,
507 premultiply_alpha,
508 )
509}
510
511pub fn yuv420_alpha_to_bgra(
531 planar_with_alpha: &YuvPlanarImageWithAlpha<u8>,
532 bgra: &mut [u8],
533 bgra_stride: u32,
534 range: YuvRange,
535 matrix: YuvStandardMatrix,
536 premultiply_alpha: bool,
537) -> Result<(), YuvError> {
538 yuv_with_alpha_to_rgbx::<
539 { YuvSourceChannels::Bgra as u8 },
540 { YuvChromaSubsampling::Yuv420 as u8 },
541 >(
542 planar_with_alpha,
543 bgra,
544 bgra_stride,
545 range,
546 matrix,
547 premultiply_alpha,
548 )
549}
550
551pub fn yuv422_alpha_to_rgba(
571 planar_with_alpha: &YuvPlanarImageWithAlpha<u8>,
572 rgba: &mut [u8],
573 rgba_stride: u32,
574 range: YuvRange,
575 matrix: YuvStandardMatrix,
576 premultiply_alpha: bool,
577) -> Result<(), YuvError> {
578 yuv_with_alpha_to_rgbx::<
579 { YuvSourceChannels::Rgba as u8 },
580 { YuvChromaSubsampling::Yuv422 as u8 },
581 >(
582 planar_with_alpha,
583 rgba,
584 rgba_stride,
585 range,
586 matrix,
587 premultiply_alpha,
588 )
589}
590
591pub fn yuv422_alpha_to_bgra(
611 planar_with_alpha: &YuvPlanarImageWithAlpha<u8>,
612 bgra: &mut [u8],
613 bgra_stride: u32,
614 range: YuvRange,
615 matrix: YuvStandardMatrix,
616 premultiply_alpha: bool,
617) -> Result<(), YuvError> {
618 yuv_with_alpha_to_rgbx::<
619 { YuvSourceChannels::Bgra as u8 },
620 { YuvChromaSubsampling::Yuv422 as u8 },
621 >(
622 planar_with_alpha,
623 bgra,
624 bgra_stride,
625 range,
626 matrix,
627 premultiply_alpha,
628 )
629}
630
631pub fn yuv444_alpha_to_rgba(
651 planar_with_alpha: &YuvPlanarImageWithAlpha<u8>,
652 rgba: &mut [u8],
653 rgba_stride: u32,
654 range: YuvRange,
655 matrix: YuvStandardMatrix,
656 premultiply_alpha: bool,
657) -> Result<(), YuvError> {
658 yuv_with_alpha_to_rgbx::<
659 { YuvSourceChannels::Rgba as u8 },
660 { YuvChromaSubsampling::Yuv444 as u8 },
661 >(
662 planar_with_alpha,
663 rgba,
664 rgba_stride,
665 range,
666 matrix,
667 premultiply_alpha,
668 )
669}
670
671pub fn yuv444_alpha_to_bgra(
691 planar_with_alpha: &YuvPlanarImageWithAlpha<u8>,
692 bgra: &mut [u8],
693 bgra_stride: u32,
694 range: YuvRange,
695 matrix: YuvStandardMatrix,
696 premultiply_alpha: bool,
697) -> Result<(), YuvError> {
698 yuv_with_alpha_to_rgbx::<
699 { YuvSourceChannels::Bgra as u8 },
700 { YuvChromaSubsampling::Yuv444 as u8 },
701 >(
702 planar_with_alpha,
703 bgra,
704 bgra_stride,
705 range,
706 matrix,
707 premultiply_alpha,
708 )
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use crate::{rgba_to_yuv444, YuvPlanarImageMut};
715 use rand::Rng;
716
717 #[test]
718 fn test_yuv444_round_trip_full_range_with_alpha() {
719 let image_width = 256usize;
720 let image_height = 256usize;
721
722 let random_point_x = rand::rng().random_range(0..image_width);
723 let random_point_y = rand::rng().random_range(0..image_height);
724
725 const CHANNELS: usize = 4;
726
727 let pixel_points = [
728 [0, 0],
729 [image_width - 1, image_height - 1],
730 [image_width - 1, 0],
731 [0, image_height - 1],
732 [(image_width - 1) / 2, (image_height - 1) / 2],
733 [image_width / 5, image_height / 5],
734 [0, image_height / 5],
735 [image_width / 5, 0],
736 [image_width / 5 * 3, image_height / 5],
737 [image_width / 5 * 3, image_height / 5 * 3],
738 [image_width / 5, image_height / 5 * 3],
739 [random_point_x, random_point_y],
740 ];
741 let mut image_rgb = vec![0u8; image_width * image_height * CHANNELS];
742
743 let or = rand::rng().random_range(0..256) as u8;
744 let og = rand::rng().random_range(0..256) as u8;
745 let ob = rand::rng().random_range(0..256) as u8;
746 let oa = rand::rng().random_range(0..256) as u8;
747
748 for point in &pixel_points {
749 image_rgb[point[0] * 4 + point[1] * image_width * 4] = or;
750 image_rgb[point[0] * 4 + point[1] * image_width * 4 + 1] = og;
751 image_rgb[point[0] * 4 + point[1] * image_width * 4 + 2] = ob;
752 image_rgb[point[0] * 4 + point[1] * image_width * 4 + 3] = oa;
753 }
754
755 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
756 image_width as u32,
757 image_height as u32,
758 YuvChromaSubsampling::Yuv444,
759 );
760
761 rgba_to_yuv444(
762 &mut planar_image,
763 &image_rgb,
764 image_width as u32 * CHANNELS as u32,
765 YuvRange::Full,
766 YuvStandardMatrix::Bt709,
767 YuvConversionMode::Balanced,
768 )
769 .unwrap();
770
771 image_rgb.fill(0);
772
773 let a_plane = vec![oa; image_width * image_height];
774
775 let fixed_planar = YuvPlanarImageWithAlpha {
776 y_plane: planar_image.y_plane.borrow(),
777 y_stride: planar_image.y_stride,
778 u_plane: planar_image.u_plane.borrow(),
779 u_stride: planar_image.u_stride,
780 v_plane: planar_image.v_plane.borrow(),
781 v_stride: planar_image.v_stride,
782 a_plane: &a_plane,
783 a_stride: image_width as u32,
784 width: image_width as u32,
785 height: image_height as u32,
786 };
787
788 yuv444_alpha_to_rgba(
789 &fixed_planar,
790 &mut image_rgb,
791 image_width as u32 * CHANNELS as u32,
792 YuvRange::Full,
793 YuvStandardMatrix::Bt709,
794 false,
795 )
796 .unwrap();
797
798 for point in &pixel_points {
799 let x = point[0];
800 let y = point[1];
801 let r = image_rgb[x * CHANNELS + y * image_width * CHANNELS];
802 let g = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1];
803 let b = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2];
804 let a = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 3];
805
806 let diff_r = (r as i32 - or as i32).abs();
807 let diff_g = (g as i32 - og as i32).abs();
808 let diff_b = (b as i32 - ob as i32).abs();
809
810 assert!(
811 diff_r <= 3,
812 "Original RGBA {:?}, Round-tripped RGBA {:?}",
813 [or, og, ob, oa],
814 [r, g, b, a]
815 );
816 assert!(
817 diff_g <= 3,
818 "Original RGBA {:?}, Round-tripped RGBA {:?}",
819 [or, og, ob, oa],
820 [r, g, b, a]
821 );
822 assert!(
823 diff_b <= 3,
824 "Original RGBA {:?}, Round-tripped RGBA {:?}",
825 [or, og, ob, oa],
826 [r, g, b, a]
827 );
828 assert_eq!(
829 a,
830 oa,
831 "Original RGBA {:?}, Round-tripped RGBA {:?}",
832 [or, og, ob, oa],
833 [r, g, b, a]
834 );
835 }
836 }
837
838 #[test]
839 fn test_yuv444_round_trip_limited_range_with_alpha() {
840 let image_width = 256usize;
841 let image_height = 256usize;
842
843 let random_point_x = rand::rng().random_range(0..image_width);
844 let random_point_y = rand::rng().random_range(0..image_height);
845
846 const CHANNELS: usize = 4;
847
848 let pixel_points = [
849 [0, 0],
850 [image_width - 1, image_height - 1],
851 [image_width - 1, 0],
852 [0, image_height - 1],
853 [(image_width - 1) / 2, (image_height - 1) / 2],
854 [image_width / 5, image_height / 5],
855 [0, image_height / 5],
856 [image_width / 5, 0],
857 [image_width / 5 * 3, image_height / 5],
858 [image_width / 5 * 3, image_height / 5 * 3],
859 [image_width / 5, image_height / 5 * 3],
860 [random_point_x, random_point_y],
861 ];
862 let mut image_rgb = vec![0u8; image_width * image_height * CHANNELS];
863
864 let or = rand::rng().random_range(0..256) as u8;
865 let og = rand::rng().random_range(0..256) as u8;
866 let ob = rand::rng().random_range(0..256) as u8;
867 let oa = rand::rng().random_range(0..256) as u8;
868
869 for point in &pixel_points {
870 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
871 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
872 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
873 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 3] = oa;
874 }
875
876 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
877 image_width as u32,
878 image_height as u32,
879 YuvChromaSubsampling::Yuv444,
880 );
881
882 rgba_to_yuv444(
883 &mut planar_image,
884 &image_rgb,
885 image_width as u32 * CHANNELS as u32,
886 YuvRange::Full,
887 YuvStandardMatrix::Bt709,
888 YuvConversionMode::Balanced,
889 )
890 .unwrap();
891
892 image_rgb.fill(0);
893
894 let a_plane = vec![oa; image_width * image_height];
895
896 let fixed_planar = YuvPlanarImageWithAlpha {
897 y_plane: planar_image.y_plane.borrow(),
898 y_stride: planar_image.y_stride,
899 u_plane: planar_image.u_plane.borrow(),
900 u_stride: planar_image.u_stride,
901 v_plane: planar_image.v_plane.borrow(),
902 v_stride: planar_image.v_stride,
903 a_plane: &a_plane,
904 a_stride: image_width as u32,
905 width: image_width as u32,
906 height: image_height as u32,
907 };
908
909 yuv444_alpha_to_rgba(
910 &fixed_planar,
911 &mut image_rgb,
912 image_width as u32 * 4,
913 YuvRange::Full,
914 YuvStandardMatrix::Bt709,
915 false,
916 )
917 .unwrap();
918
919 for point in &pixel_points {
920 let x = point[0];
921 let y = point[1];
922 let r = image_rgb[x * CHANNELS + y * image_width * CHANNELS];
923 let g = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1];
924 let b = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2];
925 let a = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 3];
926
927 let diff_r = (r as i32 - or as i32).abs();
928 let diff_g = (g as i32 - og as i32).abs();
929 let diff_b = (b as i32 - ob as i32).abs();
930
931 assert!(
932 diff_r <= 10,
933 "Original RGBA {:?}, Round-tripped RGBA {:?}",
934 [or, og, ob, oa],
935 [r, g, b, a]
936 );
937 assert!(
938 diff_g <= 10,
939 "Original RGBA {:?}, Round-tripped RGBA {:?}",
940 [or, og, ob, oa],
941 [r, g, b, a]
942 );
943 assert!(
944 diff_b <= 10,
945 "Original RGBA {:?}, Round-tripped RGBA {:?}",
946 [or, og, ob, oa],
947 [r, g, b, a]
948 );
949 assert_eq!(
950 a,
951 oa,
952 "Original RGBA {:?}, Round-tripped RGBA {:?}",
953 [or, og, ob, oa],
954 [r, g, b, a]
955 );
956 }
957 }
958}