1use crate::internals::{ProcessedOffset, WideRow420InversionHandler, WideRowInversionHandler};
30use crate::numerics::qrshr;
31use crate::yuv_error::check_rgba_destination;
32use crate::yuv_support::*;
33use crate::{YuvError, YuvPlanarImage};
34#[cfg(feature = "rayon")]
35use rayon::iter::{IndexedParallelIterator, ParallelIterator};
36#[cfg(feature = "rayon")]
37use rayon::prelude::{ParallelSlice, ParallelSliceMut};
38
39type RowHandle = Option<
40 unsafe fn(
41 range: &YuvChromaRange,
42 transform: &CbCrInverseTransform<i32>,
43 y_plane: &[u8],
44 u_plane: &[u8],
45 v_plane: &[u8],
46 rgba: &mut [u8],
47 start_cx: usize,
48 start_ux: usize,
49 width: usize,
50 ) -> ProcessedOffset,
51>;
52
53type RowHandle420 = Option<
54 unsafe fn(
55 range: &YuvChromaRange,
56 transform: &CbCrInverseTransform<i32>,
57 y_plane0: &[u8],
58 y_plane1: &[u8],
59 u_plane: &[u8],
60 v_plane: &[u8],
61 rgba0: &mut [u8],
62 rgba1: &mut [u8],
63 start_cx: usize,
64 start_ux: usize,
65 width: usize,
66 ) -> ProcessedOffset,
67>;
68
69struct RowHandler<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32> {
70 handler: RowHandle,
71}
72
73macro_rules! impl_row_inversion_handler {
74 ($struct_name:ident) => {
75 impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32>
76 WideRowInversionHandler<u8, i32>
77 for $struct_name<DESTINATION_CHANNELS, SAMPLING, PRECISION>
78 {
79 fn handle_row(
80 &self,
81 y_plane: &[u8],
82 u_plane: &[u8],
83 v_plane: &[u8],
84 rgba: &mut [u8],
85 width: u32,
86 chroma: YuvChromaRange,
87 transform: &CbCrInverseTransform<i32>,
88 ) -> ProcessedOffset {
89 if let Some(handler) = self.handler {
90 unsafe {
91 return handler(
92 &chroma,
93 transform,
94 y_plane,
95 u_plane,
96 v_plane,
97 rgba,
98 0,
99 0,
100 width as usize,
101 );
102 }
103 }
104 ProcessedOffset { cx: 0, ux: 0 }
105 }
106 }
107 };
108}
109
110impl_row_inversion_handler!(RowHandler);
111
112impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32> Default
113 for RowHandler<DESTINATION_CHANNELS, SAMPLING, PRECISION>
114{
115 fn default() -> Self {
116 if PRECISION != 13 {
117 return RowHandler { handler: None };
118 }
119 assert_eq!(PRECISION, 13);
120 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
121 {
122 #[cfg(feature = "rdm")]
123 {
124 let is_rdm_available = std::arch::is_aarch64_feature_detected!("rdm");
125 if is_rdm_available {
126 use crate::neon::neon_yuv_to_rgba_row_rdm;
127
128 return RowHandler {
129 handler: Some(neon_yuv_to_rgba_row_rdm::<DESTINATION_CHANNELS, SAMPLING>),
130 };
131 }
132 }
133 use crate::neon::neon_yuv_to_rgba_row;
134 RowHandler {
135 handler: Some(neon_yuv_to_rgba_row::<DESTINATION_CHANNELS, SAMPLING>),
136 }
137 }
138 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
139 {
140 #[cfg(feature = "nightly_avx512")]
141 {
142 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
143 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
144 let sampling: YuvChromaSubsampling = SAMPLING.into();
145 if use_avx512 {
146 return if sampling == YuvChromaSubsampling::Yuv420
147 || sampling == YuvChromaSubsampling::Yuv422
148 {
149 assert!(
150 sampling == YuvChromaSubsampling::Yuv420
151 || sampling == YuvChromaSubsampling::Yuv422
152 );
153 use crate::avx512bw::avx512_yuv_to_rgba422;
154 RowHandler {
155 handler: Some(if use_vbmi {
156 avx512_yuv_to_rgba422::<DESTINATION_CHANNELS, true>
157 } else {
158 avx512_yuv_to_rgba422::<DESTINATION_CHANNELS, false>
159 }),
160 }
161 } else {
162 use crate::avx512bw::avx512_yuv_to_rgba;
163 RowHandler {
164 handler: Some(if use_vbmi {
165 avx512_yuv_to_rgba::<DESTINATION_CHANNELS, SAMPLING, true>
166 } else {
167 avx512_yuv_to_rgba::<DESTINATION_CHANNELS, SAMPLING, false>
168 }),
169 }
170 };
171 }
172 }
173
174 #[cfg(feature = "avx")]
175 {
176 let use_avx2 = std::arch::is_x86_feature_detected!("avx2");
177 if use_avx2 {
178 let sampling: YuvChromaSubsampling = SAMPLING.into();
179 return if sampling == YuvChromaSubsampling::Yuv420
180 || sampling == YuvChromaSubsampling::Yuv422
181 {
182 assert!(
183 sampling == YuvChromaSubsampling::Yuv420
184 || sampling == YuvChromaSubsampling::Yuv422
185 );
186 use crate::avx2::avx2_yuv_to_rgba_row422;
187 RowHandler {
188 handler: Some(avx2_yuv_to_rgba_row422::<DESTINATION_CHANNELS>),
189 }
190 } else {
191 use crate::avx2::avx2_yuv_to_rgba_row;
192 RowHandler {
193 handler: Some(avx2_yuv_to_rgba_row::<DESTINATION_CHANNELS, SAMPLING>),
194 }
195 };
196 }
197 }
198
199 #[cfg(feature = "sse")]
200 {
201 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
202 if use_sse {
203 let sampling: YuvChromaSubsampling = SAMPLING.into();
204 if sampling == YuvChromaSubsampling::Yuv420
205 || sampling == YuvChromaSubsampling::Yuv422
206 {
207 assert!(
208 sampling == YuvChromaSubsampling::Yuv420
209 || sampling == YuvChromaSubsampling::Yuv422
210 );
211 use crate::sse::sse_yuv_to_rgba_row422;
212 return RowHandler {
213 handler: Some(sse_yuv_to_rgba_row422::<DESTINATION_CHANNELS>),
214 };
215 } else {
216 use crate::sse::sse_yuv_to_rgba_row;
217 return RowHandler {
218 handler: Some(sse_yuv_to_rgba_row::<DESTINATION_CHANNELS, SAMPLING>),
219 };
220 }
221 }
222 }
223 }
224 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
225 {
226 use crate::wasm32::wasm_yuv_to_rgba_row;
227 return RowHandler {
228 handler: Some(wasm_yuv_to_rgba_row::<DESTINATION_CHANNELS, SAMPLING>),
229 };
230 }
231 #[cfg(not(any(
232 all(target_arch = "aarch64", target_feature = "neon"),
233 all(target_arch = "wasm32", target_feature = "simd128")
234 )))]
235 {
236 RowHandler { handler: None }
237 }
238 }
239}
240
241struct RowHandler420<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32> {
242 handler: RowHandle420,
243}
244
245impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32> Default
246 for RowHandler420<DESTINATION_CHANNELS, SAMPLING, PRECISION>
247{
248 fn default() -> Self {
249 if PRECISION != 13 {
250 return RowHandler420 { handler: None };
251 }
252 assert_eq!(PRECISION, 13);
253 let sampling: YuvChromaSubsampling = SAMPLING.into();
254 if sampling != YuvChromaSubsampling::Yuv420 {
255 return RowHandler420 { handler: None };
256 }
257 assert_eq!(sampling, YuvChromaSubsampling::Yuv420);
258 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
259 {
260 #[cfg(feature = "rdm")]
261 {
262 let is_rdm_available = std::arch::is_aarch64_feature_detected!("rdm");
263 if is_rdm_available {
264 use crate::neon::neon_yuv_to_rgba_row_rdm420;
265 return RowHandler420 {
266 handler: Some(neon_yuv_to_rgba_row_rdm420::<DESTINATION_CHANNELS>),
267 };
268 }
269 }
270 use crate::neon::neon_yuv_to_rgba_row420;
271 RowHandler420 {
272 handler: Some(neon_yuv_to_rgba_row420::<DESTINATION_CHANNELS>),
273 }
274 }
275 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
276 {
277 #[cfg(feature = "nightly_avx512")]
278 {
279 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
280 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
281 if use_avx512 {
282 use crate::avx512bw::avx512_yuv_to_rgba420;
283 return RowHandler420 {
284 handler: Some(if use_vbmi {
285 avx512_yuv_to_rgba420::<DESTINATION_CHANNELS, true>
286 } else {
287 avx512_yuv_to_rgba420::<DESTINATION_CHANNELS, false>
288 }),
289 };
290 }
291 }
292 #[cfg(feature = "avx")]
293 {
294 let use_avx2 = std::arch::is_x86_feature_detected!("avx2");
295 if use_avx2 {
296 use crate::avx2::avx2_yuv_to_rgba_row420;
297 return RowHandler420 {
298 handler: Some(avx2_yuv_to_rgba_row420::<DESTINATION_CHANNELS>),
299 };
300 }
301 }
302 #[cfg(feature = "sse")]
303 {
304 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
305 if use_sse {
306 use crate::sse::sse_yuv_to_rgba_row420;
307 return RowHandler420 {
308 handler: Some(sse_yuv_to_rgba_row420::<DESTINATION_CHANNELS>),
309 };
310 }
311 }
312 }
313 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
314 {
315 use crate::wasm32::wasm_yuv_to_rgba_row420;
316 return RowHandler420 {
317 handler: Some(wasm_yuv_to_rgba_row420::<DESTINATION_CHANNELS, SAMPLING>),
318 };
319 }
320 #[cfg(not(any(
321 all(target_arch = "aarch64", target_feature = "neon"),
322 all(target_arch = "wasm32", target_feature = "simd128")
323 )))]
324 {
325 RowHandler420 { handler: None }
326 }
327 }
328}
329
330macro_rules! impl_row_420_inversion_handler {
331 ($struct_name:ident) => {
332 impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32>
333 WideRow420InversionHandler<u8, i32>
334 for $struct_name<DESTINATION_CHANNELS, SAMPLING, PRECISION>
335 {
336 fn handle_row(
337 &self,
338 y0_plane: &[u8],
339 y1_plane: &[u8],
340 u_plane: &[u8],
341 v_plane: &[u8],
342 rgba0: &mut [u8],
343 rgba1: &mut [u8],
344 width: u32,
345 chroma: YuvChromaRange,
346 transform: &CbCrInverseTransform<i32>,
347 ) -> ProcessedOffset {
348 if let Some(handler) = self.handler {
349 unsafe {
350 return handler(
351 &chroma,
352 transform,
353 y0_plane,
354 y1_plane,
355 u_plane,
356 v_plane,
357 rgba0,
358 rgba1,
359 0,
360 0,
361 width as usize,
362 );
363 }
364 }
365 ProcessedOffset { cx: 0, ux: 0 }
366 }
367 }
368 };
369}
370
371impl_row_420_inversion_handler!(RowHandler420);
372
373fn yuv_to_rgbx_impl<const DESTINATION_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32>(
374 image: &YuvPlanarImage<u8>,
375 rgba: &mut [u8],
376 rgba_stride: u32,
377 range: YuvRange,
378 matrix: YuvStandardMatrix,
379 row_handler: impl WideRowInversionHandler<u8, i32> + Send + Sync,
380 row_handler420: impl WideRow420InversionHandler<u8, i32> + Send + Sync,
381) -> Result<(), YuvError> {
382 let chroma_subsampling: YuvChromaSubsampling = SAMPLING.into();
383 let dst_chans: YuvSourceChannels = DESTINATION_CHANNELS.into();
384 let channels = dst_chans.get_channels_count();
385
386 check_rgba_destination(rgba, rgba_stride, image.width, image.height, channels)?;
387 image.check_constraints(chroma_subsampling)?;
388
389 let chroma_range = get_yuv_range(8, range);
390 let kr_kb = matrix.get_kr_kb();
391
392 let inverse_transform =
393 search_inverse_transform(PRECISION, 8, range, matrix, chroma_range, kr_kb);
394 let cr_coef = inverse_transform.cr_coef;
395 let cb_coef = inverse_transform.cb_coef;
396 let y_coef = inverse_transform.y_coef;
397 let g_coef_1 = inverse_transform.g_coeff_1;
398 let g_coef_2 = inverse_transform.g_coeff_2;
399
400 let bias_y = chroma_range.bias_y as i32;
401 let bias_uv = chroma_range.bias_uv as i32;
402
403 const BIT_DEPTH: usize = 8;
404
405 let process_halved_chroma_row =
406 |y_plane: &[u8], u_plane: &[u8], v_plane: &[u8], rgba: &mut [u8]| {
407 let cx = row_handler
408 .handle_row(
409 y_plane,
410 u_plane,
411 v_plane,
412 rgba,
413 image.width,
414 chroma_range,
415 &inverse_transform,
416 )
417 .cx;
418
419 if cx != image.width as usize {
420 for (((rgba, y_src), &u_src), &v_src) in rgba
421 .chunks_exact_mut(channels * 2)
422 .zip(y_plane.chunks_exact(2))
423 .zip(u_plane.iter())
424 .zip(v_plane.iter())
425 .skip(cx / 2)
426 {
427 let y_value0 = (y_src[0] as i32 - bias_y) * y_coef;
428 let cb_value = u_src as i32 - bias_uv;
429 let cr_value = v_src as i32 - bias_uv;
430
431 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
432 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
433 let g0 = qrshr::<PRECISION, BIT_DEPTH>(
434 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
435 );
436
437 let rgba0 = &mut rgba[0..channels];
438
439 rgba0[dst_chans.get_r_channel_offset()] = r0 as u8;
440 rgba0[dst_chans.get_g_channel_offset()] = g0 as u8;
441 rgba0[dst_chans.get_b_channel_offset()] = b0 as u8;
442 if dst_chans.has_alpha() {
443 rgba0[dst_chans.get_a_channel_offset()] = 255u8;
444 }
445
446 let y_value1 = (y_src[1] as i32 - bias_y) * y_coef;
447
448 let r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value);
449 let b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value);
450 let g1 = qrshr::<PRECISION, BIT_DEPTH>(
451 y_value1 - g_coef_1 * cr_value - g_coef_2 * cb_value,
452 );
453
454 let rgba1 = &mut rgba[channels..channels * 2];
455
456 rgba1[dst_chans.get_r_channel_offset()] = r1 as u8;
457 rgba1[dst_chans.get_g_channel_offset()] = g1 as u8;
458 rgba1[dst_chans.get_b_channel_offset()] = b1 as u8;
459 if dst_chans.has_alpha() {
460 rgba1[dst_chans.get_a_channel_offset()] = 255u8;
461 }
462 }
463
464 if image.width & 1 != 0 {
465 let y_value0 = (*y_plane.last().unwrap() as i32 - bias_y) * y_coef;
466 let cb_value = *u_plane.last().unwrap() as i32 - bias_uv;
467 let cr_value = *v_plane.last().unwrap() as i32 - bias_uv;
468 let rgba = rgba.chunks_exact_mut(channels).last().unwrap();
469 let rgba0 = &mut rgba[0..channels];
470
471 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
472 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
473 let g0 = qrshr::<PRECISION, BIT_DEPTH>(
474 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
475 );
476 rgba0[dst_chans.get_r_channel_offset()] = r0 as u8;
477 rgba0[dst_chans.get_g_channel_offset()] = g0 as u8;
478 rgba0[dst_chans.get_b_channel_offset()] = b0 as u8;
479 if dst_chans.has_alpha() {
480 rgba0[dst_chans.get_a_channel_offset()] = 255;
481 }
482 }
483 }
484 };
485
486 let process_doubled_chroma_row = |y_plane0: &[u8],
487 y_plane1: &[u8],
488 u_plane: &[u8],
489 v_plane: &[u8],
490 rgba0: &mut [u8],
491 rgba1: &mut [u8]| {
492 let cx = row_handler420
493 .handle_row(
494 y_plane0,
495 y_plane1,
496 u_plane,
497 v_plane,
498 rgba0,
499 rgba1,
500 image.width,
501 chroma_range,
502 &inverse_transform,
503 )
504 .cx;
505
506 if cx != image.width as usize {
507 for (((((rgba0, rgba1), y_src0), y_src1), &u_src), &v_src) in rgba0
508 .chunks_exact_mut(channels * 2)
509 .zip(rgba1.chunks_exact_mut(channels * 2))
510 .zip(y_plane0.chunks_exact(2))
511 .zip(y_plane1.chunks_exact(2))
512 .zip(u_plane.iter())
513 .zip(v_plane.iter())
514 .skip(cx / 2)
515 {
516 let y_value0 = (y_src0[0] as i32 - bias_y) * y_coef;
517 let cb_value = u_src as i32 - bias_uv;
518 let cr_value = v_src as i32 - bias_uv;
519
520 let g_built_coeff = -g_coef_1 * cr_value - g_coef_2 * cb_value;
521
522 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
523 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
524 let g0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + g_built_coeff);
525
526 let rgba00 = &mut rgba0[0..channels];
527
528 rgba00[dst_chans.get_r_channel_offset()] = r0 as u8;
529 rgba00[dst_chans.get_g_channel_offset()] = g0 as u8;
530 rgba00[dst_chans.get_b_channel_offset()] = b0 as u8;
531 if dst_chans.has_alpha() {
532 rgba00[dst_chans.get_a_channel_offset()] = 255u8;
533 }
534
535 let y_value1 = (y_src0[1] as i32 - bias_y) * y_coef;
536
537 let r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value);
538 let b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value);
539 let g1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + g_built_coeff);
540
541 let rgba01 = &mut rgba0[channels..channels * 2];
542
543 rgba01[dst_chans.get_r_channel_offset()] = r1 as u8;
544 rgba01[dst_chans.get_g_channel_offset()] = g1 as u8;
545 rgba01[dst_chans.get_b_channel_offset()] = b1 as u8;
546 if dst_chans.has_alpha() {
547 rgba01[dst_chans.get_a_channel_offset()] = 255u8;
548 }
549
550 let y_value10 = (y_src1[0] as i32 - bias_y) * y_coef;
551
552 let r10 = qrshr::<PRECISION, BIT_DEPTH>(y_value10 + cr_coef * cr_value);
553 let b10 = qrshr::<PRECISION, BIT_DEPTH>(y_value10 + cb_coef * cb_value);
554 let g10 = qrshr::<PRECISION, BIT_DEPTH>(y_value10 + g_built_coeff);
555
556 let rgba10 = &mut rgba1[0..channels];
557
558 rgba10[dst_chans.get_r_channel_offset()] = r10 as u8;
559 rgba10[dst_chans.get_g_channel_offset()] = g10 as u8;
560 rgba10[dst_chans.get_b_channel_offset()] = b10 as u8;
561 if dst_chans.has_alpha() {
562 rgba10[dst_chans.get_a_channel_offset()] = 255u8;
563 }
564
565 let y_value11 = (y_src1[1] as i32 - bias_y) * y_coef;
566
567 let r11 = qrshr::<PRECISION, BIT_DEPTH>(y_value11 + cr_coef * cr_value);
568 let b11 = qrshr::<PRECISION, BIT_DEPTH>(y_value11 + cb_coef * cb_value);
569 let g11 = qrshr::<PRECISION, BIT_DEPTH>(y_value11 + g_built_coeff);
570
571 let rgba11 = &mut rgba1[channels..channels * 2];
572
573 rgba11[dst_chans.get_r_channel_offset()] = r11 as u8;
574 rgba11[dst_chans.get_g_channel_offset()] = g11 as u8;
575 rgba11[dst_chans.get_b_channel_offset()] = b11 as u8;
576 if dst_chans.has_alpha() {
577 rgba11[dst_chans.get_a_channel_offset()] = 255u8;
578 }
579 }
580
581 if image.width & 1 != 0 {
582 let y_value0 = (*y_plane0.last().unwrap() as i32 - bias_y) * y_coef;
583 let y_value1 = (*y_plane1.last().unwrap() as i32 - bias_y) * y_coef;
584 let cb_value = *u_plane.last().unwrap() as i32 - bias_uv;
585 let cr_value = *v_plane.last().unwrap() as i32 - bias_uv;
586 let rgba = rgba0.chunks_exact_mut(channels).last().unwrap();
587 let rgba0 = &mut rgba[0..channels];
588
589 let g_built_coeff = -g_coef_1 * cr_value - g_coef_2 * cb_value;
590
591 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
592 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
593 let g0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + g_built_coeff);
594
595 rgba0[dst_chans.get_r_channel_offset()] = r0 as u8;
596 rgba0[dst_chans.get_g_channel_offset()] = g0 as u8;
597 rgba0[dst_chans.get_b_channel_offset()] = b0 as u8;
598 if dst_chans.has_alpha() {
599 rgba0[dst_chans.get_a_channel_offset()] = 255;
600 }
601
602 let r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value);
603 let b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value);
604 let g1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + g_built_coeff);
605
606 let rgba = rgba1.chunks_exact_mut(channels).last().unwrap();
607 let rgba1 = &mut rgba[0..channels];
608 rgba1[dst_chans.get_r_channel_offset()] = r1 as u8;
609 rgba1[dst_chans.get_g_channel_offset()] = g1 as u8;
610 rgba1[dst_chans.get_b_channel_offset()] = b1 as u8;
611 if dst_chans.has_alpha() {
612 rgba1[dst_chans.get_a_channel_offset()] = 255;
613 }
614 }
615 }
616 };
617
618 if chroma_subsampling == YuvChromaSubsampling::Yuv444 {
619 let iter;
620 #[cfg(feature = "rayon")]
621 {
622 iter = rgba
623 .par_chunks_exact_mut(rgba_stride as usize)
624 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
625 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
626 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
627 }
628 #[cfg(not(feature = "rayon"))]
629 {
630 iter = rgba
631 .chunks_exact_mut(rgba_stride as usize)
632 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
633 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
634 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
635 }
636 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
637 let y_plane = &y_plane[0..image.width as usize];
638 let cx = row_handler
639 .handle_row(
640 y_plane,
641 u_plane,
642 v_plane,
643 rgba,
644 image.width,
645 chroma_range,
646 &inverse_transform,
647 )
648 .cx;
649
650 if cx != image.width as usize {
651 for (((rgba, &y_src), &u_src), &v_src) in rgba
652 .chunks_exact_mut(channels)
653 .zip(y_plane.iter())
654 .zip(u_plane.iter())
655 .zip(v_plane.iter())
656 .skip(cx)
657 {
658 let y_value = (y_src as i32 - bias_y) * y_coef;
659 let cb_value = u_src as i32 - bias_uv;
660 let cr_value = v_src as i32 - bias_uv;
661
662 let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
663 let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
664 let g = qrshr::<PRECISION, BIT_DEPTH>(
665 y_value - g_coef_1 * cr_value - g_coef_2 * cb_value,
666 );
667
668 rgba[dst_chans.get_r_channel_offset()] = r as u8;
669 rgba[dst_chans.get_g_channel_offset()] = g as u8;
670 rgba[dst_chans.get_b_channel_offset()] = b as u8;
671 if dst_chans.has_alpha() {
672 rgba[dst_chans.get_a_channel_offset()] = 255;
673 }
674 }
675 }
676 });
677 } else if chroma_subsampling == YuvChromaSubsampling::Yuv422 {
678 let iter;
679 #[cfg(feature = "rayon")]
680 {
681 iter = rgba
682 .par_chunks_exact_mut(rgba_stride as usize)
683 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
684 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
685 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
686 }
687 #[cfg(not(feature = "rayon"))]
688 {
689 iter = rgba
690 .chunks_exact_mut(rgba_stride as usize)
691 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
692 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
693 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
694 }
695 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
696 process_halved_chroma_row(
697 &y_plane[0..image.width as usize],
698 &u_plane[0..(image.width as usize).div_ceil(2)],
699 &v_plane[0..(image.width as usize).div_ceil(2)],
700 &mut rgba[0..image.width as usize * channels],
701 );
702 });
703 } else if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
704 let iter;
705 #[cfg(feature = "rayon")]
706 {
707 iter = rgba
708 .par_chunks_exact_mut(rgba_stride as usize * 2)
709 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize * 2))
710 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
711 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
712 }
713 #[cfg(not(feature = "rayon"))]
714 {
715 iter = rgba
716 .chunks_exact_mut(rgba_stride as usize * 2)
717 .zip(image.y_plane.chunks_exact(image.y_stride as usize * 2))
718 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
719 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
720 }
721 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
722 let (rgba0, rgba1) = rgba.split_at_mut(rgba_stride as usize);
723 let (y_plane0, y_plane1) = y_plane.split_at(image.y_stride as usize);
724 process_doubled_chroma_row(
725 &y_plane0[0..image.width as usize],
726 &y_plane1[0..image.width as usize],
727 &u_plane[0..(image.width as usize).div_ceil(2)],
728 &v_plane[0..(image.width as usize).div_ceil(2)],
729 &mut rgba0[0..image.width as usize * channels],
730 &mut rgba1[0..image.width as usize * channels],
731 );
732 });
733
734 if image.height & 1 != 0 {
735 let rgba = rgba.chunks_exact_mut(rgba_stride as usize).last().unwrap();
736 let u_plane = image
737 .u_plane
738 .chunks_exact(image.u_stride as usize)
739 .last()
740 .unwrap();
741 let v_plane = image
742 .v_plane
743 .chunks_exact(image.v_stride as usize)
744 .last()
745 .unwrap();
746 let y_plane = image
747 .y_plane
748 .chunks_exact(image.y_stride as usize)
749 .last()
750 .unwrap();
751 process_halved_chroma_row(
752 &y_plane[0..image.width as usize],
753 &u_plane[0..(image.width as usize).div_ceil(2)],
754 &v_plane[0..(image.width as usize).div_ceil(2)],
755 &mut rgba[0..image.width as usize * channels],
756 );
757 }
758 } else {
759 unreachable!();
760 }
761
762 Ok(())
763}
764
765fn yuv_to_rgbx<const DESTINATION_CHANNELS: u8, const SAMPLING: u8>(
766 image: &YuvPlanarImage<u8>,
767 rgba: &mut [u8],
768 rgba_stride: u32,
769 range: YuvRange,
770 matrix: YuvStandardMatrix,
771) -> Result<(), YuvError> {
772 yuv_to_rgbx_impl::<DESTINATION_CHANNELS, SAMPLING, 13>(
773 image,
774 rgba,
775 rgba_stride,
776 range,
777 matrix,
778 RowHandler::<DESTINATION_CHANNELS, SAMPLING, 13>::default(),
779 RowHandler420::<DESTINATION_CHANNELS, SAMPLING, 13>::default(),
780 )
781}
782
783pub fn yuv420_to_rgb(
802 planar_image: &YuvPlanarImage<u8>,
803 rgb: &mut [u8],
804 rgb_stride: u32,
805 range: YuvRange,
806 matrix: YuvStandardMatrix,
807) -> Result<(), YuvError> {
808 yuv_to_rgbx::<{ YuvSourceChannels::Rgb as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
809 planar_image,
810 rgb,
811 rgb_stride,
812 range,
813 matrix,
814 )
815}
816
817pub fn yuv420_to_bgr(
836 planar_image: &YuvPlanarImage<u8>,
837 bgr: &mut [u8],
838 bgr_stride: u32,
839 range: YuvRange,
840 matrix: YuvStandardMatrix,
841) -> Result<(), YuvError> {
842 yuv_to_rgbx::<{ YuvSourceChannels::Bgr as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
843 planar_image,
844 bgr,
845 bgr_stride,
846 range,
847 matrix,
848 )
849}
850
851pub fn yuv420_to_rgba(
870 planar_image: &YuvPlanarImage<u8>,
871 rgba: &mut [u8],
872 rgba_stride: u32,
873 range: YuvRange,
874 matrix: YuvStandardMatrix,
875) -> Result<(), YuvError> {
876 yuv_to_rgbx::<{ YuvSourceChannels::Rgba as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
877 planar_image,
878 rgba,
879 rgba_stride,
880 range,
881 matrix,
882 )
883}
884
885pub fn yuv420_to_bgra(
904 planar_image: &YuvPlanarImage<u8>,
905 bgra: &mut [u8],
906 bgra_stride: u32,
907 range: YuvRange,
908 matrix: YuvStandardMatrix,
909) -> Result<(), YuvError> {
910 yuv_to_rgbx::<{ YuvSourceChannels::Bgra as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
911 planar_image,
912 bgra,
913 bgra_stride,
914 range,
915 matrix,
916 )
917}
918
919pub fn yuv422_to_rgb(
938 planar_image: &YuvPlanarImage<u8>,
939 rgb: &mut [u8],
940 rgb_stride: u32,
941 range: YuvRange,
942 matrix: YuvStandardMatrix,
943) -> Result<(), YuvError> {
944 yuv_to_rgbx::<{ YuvSourceChannels::Rgb as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
945 planar_image,
946 rgb,
947 rgb_stride,
948 range,
949 matrix,
950 )
951}
952
953pub fn yuv422_to_bgr(
972 planar_image: &YuvPlanarImage<u8>,
973 bgr: &mut [u8],
974 bgr_stride: u32,
975 range: YuvRange,
976 matrix: YuvStandardMatrix,
977) -> Result<(), YuvError> {
978 yuv_to_rgbx::<{ YuvSourceChannels::Bgr as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
979 planar_image,
980 bgr,
981 bgr_stride,
982 range,
983 matrix,
984 )
985}
986
987pub fn yuv422_to_rgba(
1006 planar_image: &YuvPlanarImage<u8>,
1007 rgba: &mut [u8],
1008 rgba_stride: u32,
1009 range: YuvRange,
1010 matrix: YuvStandardMatrix,
1011) -> Result<(), YuvError> {
1012 yuv_to_rgbx::<{ YuvSourceChannels::Rgba as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
1013 planar_image,
1014 rgba,
1015 rgba_stride,
1016 range,
1017 matrix,
1018 )
1019}
1020
1021pub fn yuv422_to_bgra(
1040 planar_image: &YuvPlanarImage<u8>,
1041 bgra: &mut [u8],
1042 bgra_stride: u32,
1043 range: YuvRange,
1044 matrix: YuvStandardMatrix,
1045) -> Result<(), YuvError> {
1046 yuv_to_rgbx::<{ YuvSourceChannels::Bgra as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
1047 planar_image,
1048 bgra,
1049 bgra_stride,
1050 range,
1051 matrix,
1052 )
1053}
1054
1055pub fn yuv444_to_rgba(
1074 planar_image: &YuvPlanarImage<u8>,
1075 rgba: &mut [u8],
1076 rgba_stride: u32,
1077 range: YuvRange,
1078 matrix: YuvStandardMatrix,
1079) -> Result<(), YuvError> {
1080 yuv_to_rgbx::<{ YuvSourceChannels::Rgba as u8 }, { YuvChromaSubsampling::Yuv444 as u8 }>(
1081 planar_image,
1082 rgba,
1083 rgba_stride,
1084 range,
1085 matrix,
1086 )
1087}
1088
1089pub fn yuv444_to_bgra(
1109 planar_image: &YuvPlanarImage<u8>,
1110 bgra: &mut [u8],
1111 bgra_stride: u32,
1112 range: YuvRange,
1113 matrix: YuvStandardMatrix,
1114) -> Result<(), YuvError> {
1115 yuv_to_rgbx::<{ YuvSourceChannels::Bgra as u8 }, { YuvChromaSubsampling::Yuv444 as u8 }>(
1116 planar_image,
1117 bgra,
1118 bgra_stride,
1119 range,
1120 matrix,
1121 )
1122}
1123
1124pub fn yuv444_to_rgb(
1143 planar_image: &YuvPlanarImage<u8>,
1144 rgb: &mut [u8],
1145 rgb_stride: u32,
1146 range: YuvRange,
1147 matrix: YuvStandardMatrix,
1148) -> Result<(), YuvError> {
1149 yuv_to_rgbx::<{ YuvSourceChannels::Rgb as u8 }, { YuvChromaSubsampling::Yuv444 as u8 }>(
1150 planar_image,
1151 rgb,
1152 rgb_stride,
1153 range,
1154 matrix,
1155 )
1156}
1157
1158pub fn yuv444_to_bgr(
1177 planar_image: &YuvPlanarImage<u8>,
1178 bgr: &mut [u8],
1179 bgr_stride: u32,
1180 range: YuvRange,
1181 matrix: YuvStandardMatrix,
1182) -> Result<(), YuvError> {
1183 yuv_to_rgbx::<{ YuvSourceChannels::Bgr as u8 }, { YuvChromaSubsampling::Yuv444 as u8 }>(
1184 planar_image,
1185 bgr,
1186 bgr_stride,
1187 range,
1188 matrix,
1189 )
1190}
1191
1192#[cfg(test)]
1193mod tests {
1194 use super::*;
1195 use crate::{rgb_to_yuv420, rgb_to_yuv422, rgb_to_yuv444, yuv444_to_rgb, YuvPlanarImageMut};
1196 use rand::Rng;
1197
1198 #[test]
1199 fn test_yuv444_round_trip_full_range() {
1200 fn matrix(yuv_accuracy: YuvConversionMode, max_diff: i32) {
1201 let image_width = 256usize;
1202 let image_height = 256usize;
1203
1204 let random_point_x = rand::rng().random_range(0..image_width);
1205 let random_point_y = rand::rng().random_range(0..image_height);
1206
1207 let pixel_points = [
1208 [0, 0],
1209 [image_width - 1, image_height - 1],
1210 [image_width - 1, 0],
1211 [0, image_height - 1],
1212 [(image_width - 1) / 2, (image_height - 1) / 2],
1213 [image_width / 5, image_height / 5],
1214 [0, image_height / 5],
1215 [image_width / 5, 0],
1216 [image_width / 5 * 3, image_height / 5],
1217 [image_width / 5 * 3, image_height / 5 * 3],
1218 [image_width / 5, image_height / 5 * 3],
1219 [random_point_x, random_point_y],
1220 ];
1221 let mut image_rgb = vec![0u8; image_width * image_height * 3];
1222
1223 let or = rand::rng().random_range(0..256) as u8;
1224 let og = rand::rng().random_range(0..256) as u8;
1225 let ob = rand::rng().random_range(0..256) as u8;
1226
1227 for point in &pixel_points {
1228 image_rgb[point[0] * 3 + point[1] * image_width * 3] = or;
1229 image_rgb[point[0] * 3 + point[1] * image_width * 3 + 1] = og;
1230 image_rgb[point[0] * 3 + point[1] * image_width * 3 + 2] = ob;
1231 }
1232
1233 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
1234 image_width as u32,
1235 image_height as u32,
1236 YuvChromaSubsampling::Yuv444,
1237 );
1238
1239 rgb_to_yuv444(
1240 &mut planar_image,
1241 &image_rgb,
1242 image_width as u32 * 3,
1243 YuvRange::Full,
1244 YuvStandardMatrix::Bt709,
1245 yuv_accuracy,
1246 )
1247 .unwrap();
1248
1249 image_rgb.fill(0);
1250
1251 let fixed_planar = planar_image.to_fixed();
1252
1253 yuv444_to_rgb(
1254 &fixed_planar,
1255 &mut image_rgb,
1256 image_width as u32 * 3,
1257 YuvRange::Full,
1258 YuvStandardMatrix::Bt709,
1259 )
1260 .unwrap();
1261
1262 for point in &pixel_points {
1263 let x = point[0];
1264 let y = point[1];
1265 let r = image_rgb[x * 3 + y * image_width * 3];
1266 let g = image_rgb[x * 3 + y * image_width * 3 + 1];
1267 let b = image_rgb[x * 3 + y * image_width * 3 + 2];
1268
1269 let diff_r = (r as i32 - or as i32).abs();
1270 let diff_g = (g as i32 - og as i32).abs();
1271 let diff_b = (b as i32 - ob as i32).abs();
1272
1273 assert!(
1274 diff_r <= max_diff,
1275 "Matrix {}, diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1276 diff_r,
1277 yuv_accuracy,
1278 [or, og, ob],
1279 [r, g, b]
1280 );
1281 assert!(
1282 diff_g <= max_diff,
1283 "Matrix {}, diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1284 diff_g,
1285 yuv_accuracy,
1286 [or, og, ob],
1287 [r, g, b]
1288 );
1289 assert!(
1290 diff_b <= max_diff,
1291 "Matrix {}, diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1292 diff_b,
1293 yuv_accuracy,
1294 [or, og, ob],
1295 [r, g, b]
1296 );
1297 }
1298 }
1299 matrix(YuvConversionMode::Balanced, 3);
1300 #[cfg(feature = "fast_mode")]
1301 matrix(YuvConversionMode::Fast, 6);
1302 #[cfg(feature = "professional_mode")]
1303 matrix(YuvConversionMode::Professional, 3);
1304 }
1305
1306 #[test]
1307 fn test_yuv444_round_trip_limited_range() {
1308 fn matrix(yuv_accuracy: YuvConversionMode, max_diff: i32) {
1309 let image_width = 256usize;
1310 let image_height = 256usize;
1311
1312 let random_point_x = rand::rng().random_range(0..image_width);
1313 let random_point_y = rand::rng().random_range(0..image_height);
1314
1315 let pixel_points = [
1316 [0, 0],
1317 [image_width - 1, image_height - 1],
1318 [image_width - 1, 0],
1319 [0, image_height - 1],
1320 [(image_width - 1) / 2, (image_height - 1) / 2],
1321 [image_width / 5, image_height / 5],
1322 [0, image_height / 5],
1323 [image_width / 5, 0],
1324 [image_width / 5 * 3, image_height / 5],
1325 [image_width / 5 * 3, image_height / 5 * 3],
1326 [image_width / 5, image_height / 5 * 3],
1327 [random_point_x, random_point_y],
1328 ];
1329 let mut image_rgb = vec![0u8; image_width * image_height * 3];
1330
1331 let or = rand::rng().random_range(0..256) as u8;
1332 let og = rand::rng().random_range(0..256) as u8;
1333 let ob = rand::rng().random_range(0..256) as u8;
1334
1335 for point in &pixel_points {
1336 image_rgb[point[0] * 3 + point[1] * image_width * 3] = or;
1337 image_rgb[point[0] * 3 + point[1] * image_width * 3 + 1] = og;
1338 image_rgb[point[0] * 3 + point[1] * image_width * 3 + 2] = ob;
1339 }
1340
1341 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
1342 image_width as u32,
1343 image_height as u32,
1344 YuvChromaSubsampling::Yuv444,
1345 );
1346
1347 rgb_to_yuv444(
1348 &mut planar_image,
1349 &image_rgb,
1350 image_width as u32 * 3,
1351 YuvRange::Limited,
1352 YuvStandardMatrix::Bt709,
1353 yuv_accuracy,
1354 )
1355 .unwrap();
1356
1357 image_rgb.fill(0);
1358
1359 let fixed_planar = planar_image.to_fixed();
1360
1361 yuv444_to_rgb(
1362 &fixed_planar,
1363 &mut image_rgb,
1364 image_width as u32 * 3,
1365 YuvRange::Limited,
1366 YuvStandardMatrix::Bt709,
1367 )
1368 .unwrap();
1369
1370 for point in &pixel_points {
1371 let x = point[0];
1372 let y = point[1];
1373 let r = image_rgb[x * 3 + y * image_width * 3];
1374 let g = image_rgb[x * 3 + y * image_width * 3 + 1];
1375 let b = image_rgb[x * 3 + y * image_width * 3 + 2];
1376
1377 let diff_r = (r as i32 - or as i32).abs();
1378 let diff_g = (g as i32 - og as i32).abs();
1379 let diff_b = (b as i32 - ob as i32).abs();
1380
1381 assert!(
1382 diff_r <= max_diff,
1383 "Matrix {} Original RGB {:?}, Round-tripped RGB {:?}, diff {}",
1384 yuv_accuracy,
1385 [or, og, ob],
1386 [r, g, b],
1387 diff_r
1388 );
1389 assert!(
1390 diff_g <= max_diff,
1391 "Matrix {} Original RGB {:?}, Round-tripped RGB {:?}, diff {}",
1392 yuv_accuracy,
1393 [or, og, ob],
1394 [r, g, b],
1395 diff_g,
1396 );
1397 assert!(
1398 diff_b <= max_diff,
1399 "Matrix {} Original RGB {:?}, Round-tripped RGB {:?}, diff {}",
1400 yuv_accuracy,
1401 [or, og, ob],
1402 [r, g, b],
1403 diff_b,
1404 );
1405 }
1406 }
1407 matrix(YuvConversionMode::Balanced, 20);
1408 #[cfg(feature = "fast_mode")]
1409 matrix(YuvConversionMode::Fast, 30);
1410 }
1411
1412 #[test]
1413 fn test_yuv422_round_trip_full_range() {
1414 fn matrix(yuv_accuracy: YuvConversionMode, max_diff: i32) {
1415 let image_width = 256usize;
1416 let image_height = 256usize;
1417
1418 let random_point_x = rand::rng().random_range(0..image_width);
1419 let random_point_y = rand::rng().random_range(0..image_height);
1420
1421 const CHANNELS: usize = 3;
1422
1423 let pixel_points = [
1424 [0, 0],
1425 [image_width - 1, image_height - 1],
1426 [image_width - 1, 0],
1427 [0, image_height - 1],
1428 [(image_width - 1) / 2, (image_height - 1) / 2],
1429 [image_width / 5, image_height / 5],
1430 [0, image_height / 5],
1431 [image_width / 5, 0],
1432 [image_width / 5 * 3, image_height / 5],
1433 [image_width / 5 * 3, image_height / 5 * 3],
1434 [image_width / 5, image_height / 5 * 3],
1435 [random_point_x, random_point_y],
1436 ];
1437
1438 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
1439
1440 let or = rand::rng().random_range(0..256) as u8;
1441 let og = rand::rng().random_range(0..256) as u8;
1442 let ob = rand::rng().random_range(0..256) as u8;
1443
1444 for point in &pixel_points {
1445 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1446 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1447 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1448
1449 let nx = (point[0] + 1).min(image_width - 1);
1450 let ny = point[1].min(image_height - 1);
1451
1452 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1453 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1454 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1455
1456 let nx = point[0].saturating_sub(1).min(image_width - 1);
1457 let ny = point[1].min(image_height - 1);
1458
1459 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1460 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1461 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1462 }
1463
1464 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
1465 image_width as u32,
1466 image_height as u32,
1467 YuvChromaSubsampling::Yuv422,
1468 );
1469
1470 rgb_to_yuv422(
1471 &mut planar_image,
1472 &source_rgb,
1473 image_width as u32 * 3,
1474 YuvRange::Full,
1475 YuvStandardMatrix::Bt709,
1476 yuv_accuracy,
1477 )
1478 .unwrap();
1479
1480 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
1481
1482 let fixed_planar = planar_image.to_fixed();
1483
1484 yuv422_to_rgb(
1485 &fixed_planar,
1486 &mut dest_rgb,
1487 image_width as u32 * 3,
1488 YuvRange::Full,
1489 YuvStandardMatrix::Bt709,
1490 )
1491 .unwrap();
1492
1493 for point in &pixel_points {
1494 let x = point[0];
1495 let y = point[1];
1496 let px = x * CHANNELS + y * image_width * CHANNELS;
1497
1498 let r = dest_rgb[px];
1499 let g = dest_rgb[px + 1];
1500 let b = dest_rgb[px + 2];
1501
1502 let diff_r = r as i32 - or as i32;
1503 let diff_g = g as i32 - og as i32;
1504 let diff_b = b as i32 - ob as i32;
1505
1506 assert!(
1507 diff_r <= max_diff,
1508 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1509 yuv_accuracy,
1510 diff_r,
1511 [or, og, ob],
1512 [r, g, b]
1513 );
1514 assert!(
1515 diff_g <= max_diff,
1516 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1517 yuv_accuracy,
1518 diff_g,
1519 [or, og, ob],
1520 [r, g, b]
1521 );
1522 assert!(
1523 diff_b <= max_diff,
1524 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1525 yuv_accuracy,
1526 diff_b,
1527 [or, og, ob],
1528 [r, g, b]
1529 );
1530 }
1531 }
1532 matrix(YuvConversionMode::Balanced, 3);
1533 #[cfg(feature = "fast_mode")]
1534 matrix(YuvConversionMode::Fast, 7);
1535 #[cfg(feature = "professional_mode")]
1536 matrix(YuvConversionMode::Professional, 3);
1537 }
1538
1539 #[test]
1540 fn test_yuv422_round_trip_limited_range() {
1541 fn matrix(yuv_accuracy: YuvConversionMode, max_diff: i32) {
1542 let image_width = 256usize;
1543 let image_height = 256usize;
1544
1545 let random_point_x = rand::rng().random_range(0..image_width);
1546 let random_point_y = rand::rng().random_range(0..image_height);
1547
1548 const CHANNELS: usize = 3;
1549
1550 let pixel_points = [
1551 [0, 0],
1552 [image_width - 1, image_height - 1],
1553 [image_width - 1, 0],
1554 [0, image_height - 1],
1555 [(image_width - 1) / 2, (image_height - 1) / 2],
1556 [image_width / 5, image_height / 5],
1557 [0, image_height / 5],
1558 [image_width / 5, 0],
1559 [image_width / 5 * 3, image_height / 5],
1560 [image_width / 5 * 3, image_height / 5 * 3],
1561 [image_width / 5, image_height / 5 * 3],
1562 [random_point_x, random_point_y],
1563 ];
1564
1565 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
1566
1567 let or = rand::rng().random_range(0..256) as u8;
1568 let og = rand::rng().random_range(0..256) as u8;
1569 let ob = rand::rng().random_range(0..256) as u8;
1570
1571 for point in &pixel_points {
1572 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1573 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1574 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1575
1576 let nx = (point[0] + 1).min(image_width - 1);
1577 let ny = point[1].min(image_height - 1);
1578
1579 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1580 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1581 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1582
1583 let nx = point[0].saturating_sub(1).min(image_width - 1);
1584 let ny = point[1].min(image_height - 1);
1585
1586 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1587 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1588 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1589 }
1590
1591 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
1592 image_width as u32,
1593 image_height as u32,
1594 YuvChromaSubsampling::Yuv422,
1595 );
1596
1597 rgb_to_yuv422(
1598 &mut planar_image,
1599 &source_rgb,
1600 image_width as u32 * 3,
1601 YuvRange::Limited,
1602 YuvStandardMatrix::Bt709,
1603 yuv_accuracy,
1604 )
1605 .unwrap();
1606
1607 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
1608
1609 let fixed_planar = planar_image.to_fixed();
1610
1611 yuv422_to_rgb(
1612 &fixed_planar,
1613 &mut dest_rgb,
1614 image_width as u32 * 3,
1615 YuvRange::Limited,
1616 YuvStandardMatrix::Bt709,
1617 )
1618 .unwrap();
1619
1620 for point in pixel_points.iter() {
1621 let x = point[0];
1622 let y = point[1];
1623 let px = x * CHANNELS + y * image_width * CHANNELS;
1624
1625 let r = dest_rgb[px];
1626 let g = dest_rgb[px + 1];
1627 let b = dest_rgb[px + 2];
1628
1629 let diff_r = r as i32 - or as i32;
1630 let diff_g = g as i32 - og as i32;
1631 let diff_b = b as i32 - ob as i32;
1632
1633 assert!(
1634 diff_r <= max_diff,
1635 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1636 yuv_accuracy,
1637 diff_r,
1638 [or, og, ob],
1639 [r, g, b]
1640 );
1641 assert!(
1642 diff_g <= max_diff,
1643 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1644 yuv_accuracy,
1645 diff_g,
1646 [or, og, ob],
1647 [r, g, b]
1648 );
1649 assert!(
1650 diff_b <= max_diff,
1651 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1652 yuv_accuracy,
1653 diff_b,
1654 [or, og, ob],
1655 [r, g, b]
1656 );
1657 }
1658 }
1659 #[cfg(feature = "fast_mode")]
1660 matrix(YuvConversionMode::Fast, 15);
1661 matrix(YuvConversionMode::Balanced, 10);
1662 #[cfg(feature = "professional_mode")]
1663 matrix(YuvConversionMode::Professional, 10);
1664 }
1665
1666 #[test]
1667 fn test_yuv420_round_trip_full_range() {
1668 fn matrix(yuv_accuracy: YuvConversionMode, max_diff: i32) {
1669 let image_width = 256usize;
1670 let image_height = 256usize;
1671
1672 let random_point_x = rand::rng().random_range(0..image_width);
1673 let random_point_y = rand::rng().random_range(0..image_height);
1674
1675 const CHANNELS: usize = 3;
1676
1677 let pixel_points = [
1678 [0, 0],
1679 [image_width - 1, image_height - 1],
1680 [image_width - 1, 0],
1681 [0, image_height - 1],
1682 [(image_width - 1) / 2, (image_height - 1) / 2],
1683 [image_width / 5, image_height / 5],
1684 [0, image_height / 5],
1685 [image_width / 5, 0],
1686 [image_width / 5 * 3, image_height / 5],
1687 [image_width / 5 * 3, image_height / 5 * 3],
1688 [image_width / 5, image_height / 5 * 3],
1689 [random_point_x, random_point_y],
1690 ];
1691
1692 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
1693
1694 let or = rand::rng().random_range(0..256) as u8;
1695 let og = rand::rng().random_range(0..256) as u8;
1696 let ob = rand::rng().random_range(0..256) as u8;
1697
1698 for point in &pixel_points {
1699 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1700 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1701 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1702
1703 let nx = (point[0] + 1).min(image_width - 1);
1704 let ny = point[1].min(image_height - 1);
1705
1706 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1707 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1708 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1709
1710 let nx = (point[0] + 1).min(image_width - 1);
1711 let ny = (point[1] + 1).min(image_height - 1);
1712
1713 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1714 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1715 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1716
1717 let nx = point[0].min(image_width - 1);
1718 let ny = (point[1] + 1).min(image_height - 1);
1719
1720 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1721 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1722 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1723
1724 let nx = point[0].saturating_sub(1).min(image_width - 1);
1725 let ny = point[1].saturating_sub(1).min(image_height - 1);
1726
1727 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1728 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1729 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1730
1731 let nx = point[0].min(image_width - 1);
1732 let ny = point[1].saturating_sub(1).min(image_height - 1);
1733
1734 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1735 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1736 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1737
1738 let nx = point[0].saturating_sub(1).min(image_width - 1);
1739 let ny = point[1].min(image_height - 1);
1740
1741 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1742 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1743 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1744 }
1745
1746 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
1747 image_width as u32,
1748 image_height as u32,
1749 YuvChromaSubsampling::Yuv420,
1750 );
1751
1752 rgb_to_yuv420(
1753 &mut planar_image,
1754 &source_rgb,
1755 image_width as u32 * 3,
1756 YuvRange::Full,
1757 YuvStandardMatrix::Bt709,
1758 yuv_accuracy,
1759 )
1760 .unwrap();
1761
1762 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
1763
1764 let fixed_planar = planar_image.to_fixed();
1765
1766 yuv420_to_rgb(
1767 &fixed_planar,
1768 &mut dest_rgb,
1769 image_width as u32 * 3,
1770 YuvRange::Full,
1771 YuvStandardMatrix::Bt709,
1772 )
1773 .unwrap();
1774
1775 for point in &pixel_points {
1776 let x = point[0];
1777 let y = point[1];
1778 let px = x * CHANNELS + y * image_width * CHANNELS;
1779
1780 let r = dest_rgb[px];
1781 let g = dest_rgb[px + 1];
1782 let b = dest_rgb[px + 2];
1783
1784 let diff_r = r as i32 - or as i32;
1785 let diff_g = g as i32 - og as i32;
1786 let diff_b = b as i32 - ob as i32;
1787
1788 assert!(
1789 diff_r <= max_diff,
1790 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1791 yuv_accuracy,
1792 diff_r,
1793 [or, og, ob],
1794 [r, g, b]
1795 );
1796 assert!(
1797 diff_g <= max_diff,
1798 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1799 yuv_accuracy,
1800 diff_g,
1801 [or, og, ob],
1802 [r, g, b]
1803 );
1804 assert!(
1805 diff_b <= max_diff,
1806 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1807 yuv_accuracy,
1808 diff_b,
1809 [or, og, ob],
1810 [r, g, b]
1811 );
1812 }
1813 }
1814 matrix(YuvConversionMode::Balanced, 80);
1815 #[cfg(feature = "fast_mode")]
1816 matrix(YuvConversionMode::Fast, 80);
1817 #[cfg(feature = "professional_mode")]
1818 matrix(YuvConversionMode::Professional, 72);
1819 }
1820
1821 #[test]
1822 fn test_yuv420_round_trip_limited_range() {
1823 fn matrix(yuv_accuracy: YuvConversionMode, max_diff: i32) {
1824 let image_width = 256usize;
1825 let image_height = 256usize;
1826
1827 let random_point_x = rand::rng().random_range(0..image_width);
1828 let random_point_y = rand::rng().random_range(0..image_height);
1829
1830 const CHANNELS: usize = 3;
1831
1832 let pixel_points = [
1833 [0, 0],
1834 [image_width - 1, image_height - 1],
1835 [image_width - 1, 0],
1836 [0, image_height - 1],
1837 [(image_width - 1) / 2, (image_height - 1) / 2],
1838 [image_width / 5, image_height / 5],
1839 [0, image_height / 5],
1840 [image_width / 5, 0],
1841 [image_width / 5 * 3, image_height / 5],
1842 [image_width / 5 * 3, image_height / 5 * 3],
1843 [image_width / 5, image_height / 5 * 3],
1844 [random_point_x, random_point_y],
1845 ];
1846
1847 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
1848
1849 let or = rand::rng().random_range(0..256) as u8;
1850 let og = rand::rng().random_range(0..256) as u8;
1851 let ob = rand::rng().random_range(0..256) as u8;
1852
1853 for point in &pixel_points {
1854 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1855 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1856 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1857
1858 let nx = (point[0] + 1).min(image_width - 1);
1859 let ny = point[1].min(image_height - 1);
1860
1861 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1862 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1863 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1864
1865 let nx = (point[0] + 1).min(image_width - 1);
1866 let ny = (point[1] + 1).min(image_height - 1);
1867
1868 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1869 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1870 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1871
1872 let nx = point[0].min(image_width - 1);
1873 let ny = (point[1] + 1).min(image_height - 1);
1874
1875 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1876 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1877 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1878
1879 let nx = point[0].saturating_sub(1).min(image_width - 1);
1880 let ny = point[1].saturating_sub(1).min(image_height - 1);
1881
1882 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1883 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1884 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1885
1886 let nx = point[0].min(image_width - 1);
1887 let ny = point[1].saturating_sub(1).min(image_height - 1);
1888
1889 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1890 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1891 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1892
1893 let nx = point[0].saturating_sub(1).min(image_width - 1);
1894 let ny = point[1].min(image_height - 1);
1895
1896 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1897 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1898 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1899 }
1900
1901 let mut planar_image = YuvPlanarImageMut::<u8>::alloc(
1902 image_width as u32,
1903 image_height as u32,
1904 YuvChromaSubsampling::Yuv420,
1905 );
1906
1907 rgb_to_yuv420(
1908 &mut planar_image,
1909 &source_rgb,
1910 image_width as u32 * 3,
1911 YuvRange::Limited,
1912 YuvStandardMatrix::Bt709,
1913 yuv_accuracy,
1914 )
1915 .unwrap();
1916
1917 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
1918
1919 let fixed_planar = planar_image.to_fixed();
1920
1921 yuv420_to_rgb(
1922 &fixed_planar,
1923 &mut dest_rgb,
1924 image_width as u32 * 3,
1925 YuvRange::Limited,
1926 YuvStandardMatrix::Bt709,
1927 )
1928 .unwrap();
1929
1930 for point in &pixel_points {
1931 let x = point[0];
1932 let y = point[1];
1933 let px = x * CHANNELS + y * image_width * CHANNELS;
1934
1935 let r = dest_rgb[px];
1936 let g = dest_rgb[px + 1];
1937 let b = dest_rgb[px + 2];
1938
1939 let diff_r = r as i32 - or as i32;
1940 let diff_g = g as i32 - og as i32;
1941 let diff_b = b as i32 - ob as i32;
1942
1943 assert!(
1944 diff_r <= max_diff,
1945 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1946 yuv_accuracy,
1947 diff_r,
1948 [or, og, ob],
1949 [r, g, b]
1950 );
1951 assert!(
1952 diff_g <= max_diff,
1953 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1954 yuv_accuracy,
1955 diff_g,
1956 [or, og, ob],
1957 [r, g, b]
1958 );
1959 assert!(
1960 diff_b <= max_diff,
1961 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1962 yuv_accuracy,
1963 diff_b,
1964 [or, og, ob],
1965 [r, g, b]
1966 );
1967 }
1968 }
1969 matrix(YuvConversionMode::Balanced, 82);
1970 #[cfg(feature = "fast_mode")]
1971 matrix(YuvConversionMode::Fast, 78);
1972 #[cfg(feature = "professional_mode")]
1973 matrix(YuvConversionMode::Professional, 74);
1974 }
1975}