1use crate::internals::ProcessedOffset;
30use crate::internals::WideDRowInversionHandler;
31use crate::numerics::{qrshr, to_ne};
32use crate::yuv_error::check_rgba_destination;
33use crate::yuv_support::{
34 get_yuv_range, search_inverse_transform, CbCrInverseTransform, YuvBytesPacking, YuvChromaRange,
35 YuvChromaSubsampling, YuvEndianness, YuvRange, YuvSourceChannels, YuvStandardMatrix,
36};
37use crate::{YuvError, YuvPlanarImage};
38use core::f16;
39#[cfg(feature = "rayon")]
40use rayon::iter::{IndexedParallelIterator, ParallelIterator};
41#[cfg(feature = "rayon")]
42use rayon::prelude::{ParallelSlice, ParallelSliceMut};
43
44struct WideRowAnyHandler<
45 const DESTINATION_CHANNELS: u8,
46 const SAMPLING: u8,
47 const ENDIANNESS: u8,
48 const BYTES_POSITION: u8,
49 const PRECISION: i32,
50 const BIT_DEPTH: usize,
51> {
52 handler: Option<
53 unsafe fn(
54 y_ld_ptr: &[u16],
55 u_ld_ptr: &[u16],
56 v_ld_ptr: &[u16],
57 rgba: &mut [f16],
58 width: u32,
59 range: &YuvChromaRange,
60 transform: &CbCrInverseTransform<i32>,
61 start_cx: usize,
62 start_ux: usize,
63 ) -> ProcessedOffset,
64 >,
65}
66
67impl<
68 const DESTINATION_CHANNELS: u8,
69 const SAMPLING: u8,
70 const ENDIANNESS: u8,
71 const BYTES_POSITION: u8,
72 const PRECISION: i32,
73 const BIT_DEPTH: usize,
74 > Default
75 for WideRowAnyHandler<
76 DESTINATION_CHANNELS,
77 SAMPLING,
78 ENDIANNESS,
79 BYTES_POSITION,
80 PRECISION,
81 BIT_DEPTH,
82 >
83{
84 fn default() -> WideRowAnyHandler<
85 DESTINATION_CHANNELS,
86 SAMPLING,
87 ENDIANNESS,
88 BYTES_POSITION,
89 PRECISION,
90 BIT_DEPTH,
91 > {
92 if PRECISION != 13 {
93 return WideRowAnyHandler { handler: None };
94 }
95 assert_eq!(PRECISION, 13);
96 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
97 {
98 if BIT_DEPTH <= 12 {
99 use crate::neon::neon_yuv_p16_to_rgba_f16_row;
100 return WideRowAnyHandler {
101 handler: Some(
102 neon_yuv_p16_to_rgba_f16_row::<
103 DESTINATION_CHANNELS,
104 SAMPLING,
105 ENDIANNESS,
106 BYTES_POSITION,
107 PRECISION,
108 BIT_DEPTH,
109 >,
110 ),
111 };
112 }
113 }
114 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
115 {
116 #[cfg(feature = "avx")]
117 {
118 let use_avx = std::arch::is_x86_feature_detected!("avx2");
119 let has_f16c = std::arch::is_x86_feature_detected!("f16c");
120 if use_avx && has_f16c && BIT_DEPTH <= 12 {
121 use crate::avx2::avx_yuv_p16_to_rgba_f16_row;
122 return WideRowAnyHandler {
123 handler: Some(
124 avx_yuv_p16_to_rgba_f16_row::<
125 DESTINATION_CHANNELS,
126 SAMPLING,
127 ENDIANNESS,
128 BYTES_POSITION,
129 BIT_DEPTH,
130 PRECISION,
131 >,
132 ),
133 };
134 }
135 }
136 }
137 WideRowAnyHandler { handler: None }
138 }
139}
140
141impl<
142 const DESTINATION_CHANNELS: u8,
143 const SAMPLING: u8,
144 const ENDIANNESS: u8,
145 const BYTES_POSITION: u8,
146 const PRECISION: i32,
147 const BIT_DEPTH: usize,
148 > WideDRowInversionHandler<u16, f16, i32>
149 for WideRowAnyHandler<
150 DESTINATION_CHANNELS,
151 SAMPLING,
152 ENDIANNESS,
153 BYTES_POSITION,
154 PRECISION,
155 BIT_DEPTH,
156 >
157{
158 #[inline]
159 fn handle_row(
160 &self,
161 y_plane: &[u16],
162 u_plane: &[u16],
163 v_plane: &[u16],
164 rgba: &mut [f16],
165 width: u32,
166 yuv_chroma_range: YuvChromaRange,
167 transform: &CbCrInverseTransform<i32>,
168 ) -> ProcessedOffset {
169 if let Some(handler) = self.handler {
170 unsafe {
171 return handler(
172 y_plane,
173 u_plane,
174 v_plane,
175 rgba,
176 width,
177 &yuv_chroma_range,
178 transform,
179 0,
180 0,
181 );
182 }
183 }
184 ProcessedOffset { cx: 0, ux: 0 }
185 }
186}
187
188fn yuv_p16_to_image_p16_ant<
189 const DESTINATION_CHANNELS: u8,
190 const SAMPLING: u8,
191 const ENDIANNESS: u8,
192 const BYTES_POSITION: u8,
193 const BIT_DEPTH: usize,
194>(
195 image: &YuvPlanarImage<u16>,
196 rgba16: &mut [f16],
197 rgba_stride: u32,
198 range: YuvRange,
199 matrix: YuvStandardMatrix,
200) -> Result<(), YuvError> {
201 let dst_chans: YuvSourceChannels = DESTINATION_CHANNELS.into();
202 let channels = dst_chans.get_channels_count();
203
204 let chroma_subsampling: YuvChromaSubsampling = SAMPLING.into();
205 let chroma_range = get_yuv_range(BIT_DEPTH as u32, range);
206
207 image.check_constraints(chroma_subsampling)?;
208 check_rgba_destination(rgba16, rgba_stride, image.width, image.height, channels)?;
209
210 let kr_kb = matrix.get_kr_kb();
211 let max_range_p16 = ((1u32 << BIT_DEPTH as u32) - 1) as i32;
212
213 const PRECISION: i32 = 13;
214
215 let i_transform = search_inverse_transform(
216 PRECISION,
217 BIT_DEPTH as u32,
218 range,
219 matrix,
220 chroma_range,
221 kr_kb,
222 );
223
224 let cr_coef = i_transform.cr_coef;
225 let cb_coef = i_transform.cb_coef;
226 let y_coef = i_transform.y_coef;
227 let g_coef_1 = i_transform.g_coeff_1;
228 let g_coef_2 = i_transform.g_coeff_2;
229
230 let bias_y = chroma_range.bias_y as i32;
231 let bias_uv = chroma_range.bias_uv as i32;
232
233 let msb_shift = (16 - BIT_DEPTH) as i32;
234 let wide_row_handler = WideRowAnyHandler::<
235 DESTINATION_CHANNELS,
236 SAMPLING,
237 ENDIANNESS,
238 BYTES_POSITION,
239 PRECISION,
240 BIT_DEPTH,
241 >::default();
242
243 let default_scale = (1f32 / max_range_p16 as f32) as f16;
244
245 let process_halved_chroma_row = |y_plane: &[u16],
246 u_plane: &[u16],
247 v_plane: &[u16],
248 rgba: &mut [f16]| {
249 let cx = wide_row_handler
250 .handle_row(
251 y_plane,
252 u_plane,
253 v_plane,
254 rgba,
255 image.width,
256 chroma_range,
257 &i_transform,
258 )
259 .cx;
260
261 if cx != image.width as usize {
262 for (((rgba, y_src), &u_src), &v_src) in rgba
263 .chunks_exact_mut(channels * 2)
264 .zip(y_plane.chunks_exact(2))
265 .zip(u_plane.iter())
266 .zip(v_plane.iter())
267 .skip(cx / 2)
268 {
269 let y_value0 = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src[0], msb_shift) as i32
270 - bias_y)
271 * y_coef;
272 let cb_value =
273 to_ne::<ENDIANNESS, BYTES_POSITION>(u_src, msb_shift) as i32 - bias_uv;
274 let cr_value =
275 to_ne::<ENDIANNESS, BYTES_POSITION>(v_src, msb_shift) as i32 - bias_uv;
276
277 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value) as f16
278 * default_scale;
279 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value) as f16
280 * default_scale;
281 let g0 = qrshr::<PRECISION, BIT_DEPTH>(
282 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
283 ) as f16
284 * default_scale;
285
286 let rgba0 = &mut rgba[0..channels];
287
288 rgba0[dst_chans.get_r_channel_offset()] = r0;
289 rgba0[dst_chans.get_g_channel_offset()] = g0;
290 rgba0[dst_chans.get_b_channel_offset()] = b0;
291 if dst_chans.has_alpha() {
292 rgba0[dst_chans.get_a_channel_offset()] = 1.;
293 }
294
295 let y_value1 = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src[1], msb_shift) as i32
296 - bias_y)
297 * y_coef;
298
299 let r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value) as f16
300 * default_scale;
301 let b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value) as f16
302 * default_scale;
303 let g1 = qrshr::<PRECISION, BIT_DEPTH>(
304 y_value1 - g_coef_1 * cr_value - g_coef_2 * cb_value,
305 ) as f16
306 * default_scale;
307
308 let rgba1 = &mut rgba[channels..channels * 2];
309
310 rgba1[dst_chans.get_r_channel_offset()] = r1;
311 rgba1[dst_chans.get_g_channel_offset()] = g1;
312 rgba1[dst_chans.get_b_channel_offset()] = b1;
313 if dst_chans.has_alpha() {
314 rgba1[dst_chans.get_a_channel_offset()] = 1.;
315 }
316 }
317
318 if image.width & 1 != 0 {
319 let y_value0 =
320 (to_ne::<ENDIANNESS, BYTES_POSITION>(*y_plane.last().unwrap(), msb_shift)
321 as i32
322 - bias_y)
323 * y_coef;
324 let cb_value =
325 to_ne::<ENDIANNESS, BYTES_POSITION>(*u_plane.last().unwrap(), msb_shift) as i32
326 - bias_uv;
327 let cr_value =
328 to_ne::<ENDIANNESS, BYTES_POSITION>(*v_plane.last().unwrap(), msb_shift) as i32
329 - bias_uv;
330 let rgba = rgba.chunks_exact_mut(channels).last().unwrap();
331 let rgba0 = &mut rgba[0..channels];
332
333 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value) as f16
334 * default_scale;
335 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value) as f16
336 * default_scale;
337 let g0 = qrshr::<PRECISION, BIT_DEPTH>(
338 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
339 ) as f16
340 * default_scale;
341 rgba0[dst_chans.get_r_channel_offset()] = r0;
342 rgba0[dst_chans.get_g_channel_offset()] = g0;
343 rgba0[dst_chans.get_b_channel_offset()] = b0;
344 if dst_chans.has_alpha() {
345 rgba0[dst_chans.get_a_channel_offset()] = 1.;
346 }
347 }
348 }
349 };
350
351 if chroma_subsampling == YuvChromaSubsampling::Yuv444 {
352 let iter;
353 #[cfg(feature = "rayon")]
354 {
355 iter = rgba16
356 .par_chunks_exact_mut(rgba_stride as usize)
357 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
358 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
359 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
360 }
361 #[cfg(not(feature = "rayon"))]
362 {
363 iter = rgba16
364 .chunks_exact_mut(rgba_stride as usize)
365 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
366 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
367 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
368 }
369 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
370 let y_plane = &y_plane[0..image.width as usize];
371 let cx = wide_row_handler
372 .handle_row(
373 y_plane,
374 u_plane,
375 v_plane,
376 rgba,
377 image.width,
378 chroma_range,
379 &i_transform,
380 )
381 .cx;
382 if cx != image.width as usize {
383 for (((rgba, &y_src), &u_src), &v_src) in rgba
384 .chunks_exact_mut(channels)
385 .zip(y_plane.iter())
386 .zip(u_plane.iter())
387 .zip(v_plane.iter())
388 .skip(cx)
389 {
390 let y_value = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src, msb_shift) as i32
391 - bias_y)
392 * y_coef;
393 let cb_value =
394 to_ne::<ENDIANNESS, BYTES_POSITION>(u_src, msb_shift) as i32 - bias_uv;
395 let cr_value =
396 to_ne::<ENDIANNESS, BYTES_POSITION>(v_src, msb_shift) as i32 - bias_uv;
397
398 let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value) as f16
399 * default_scale;
400 let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value) as f16
401 * default_scale;
402 let g = qrshr::<PRECISION, BIT_DEPTH>(
403 y_value - g_coef_1 * cr_value - g_coef_2 * cb_value,
404 ) as f16
405 * default_scale;
406
407 rgba[dst_chans.get_r_channel_offset()] = r;
408 rgba[dst_chans.get_g_channel_offset()] = g;
409 rgba[dst_chans.get_b_channel_offset()] = b;
410 if dst_chans.has_alpha() {
411 rgba[dst_chans.get_a_channel_offset()] = 1.;
412 }
413 }
414 }
415 });
416 } else if chroma_subsampling == YuvChromaSubsampling::Yuv422 {
417 let iter;
418 #[cfg(feature = "rayon")]
419 {
420 iter = rgba16
421 .par_chunks_exact_mut(rgba_stride as usize)
422 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
423 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
424 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
425 }
426 #[cfg(not(feature = "rayon"))]
427 {
428 iter = rgba16
429 .chunks_exact_mut(rgba_stride as usize)
430 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
431 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
432 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
433 }
434 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
435 process_halved_chroma_row(
436 &y_plane[0..image.width as usize],
437 &u_plane[0..(image.width as usize).div_ceil(2)],
438 &v_plane[0..(image.width as usize).div_ceil(2)],
439 &mut rgba[0..image.width as usize * channels],
440 );
441 });
442 } else if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
443 let iter;
444 #[cfg(feature = "rayon")]
445 {
446 iter = rgba16
447 .par_chunks_exact_mut(rgba_stride as usize * 2)
448 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize * 2))
449 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
450 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
451 }
452 #[cfg(not(feature = "rayon"))]
453 {
454 iter = rgba16
455 .chunks_exact_mut(rgba_stride as usize * 2)
456 .zip(image.y_plane.chunks_exact(image.y_stride as usize * 2))
457 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
458 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
459 }
460 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
461 for (rgba, y_plane) in rgba
462 .chunks_exact_mut(rgba_stride as usize)
463 .zip(y_plane.chunks_exact(image.y_stride as usize))
464 {
465 process_halved_chroma_row(
466 &y_plane[0..image.width as usize],
467 &u_plane[0..(image.width as usize).div_ceil(2)],
468 &v_plane[0..(image.width as usize).div_ceil(2)],
469 &mut rgba[0..image.width as usize * channels],
470 );
471 }
472 });
473
474 if image.height & 1 != 0 {
475 let rgba = rgba16
476 .chunks_exact_mut(rgba_stride as usize)
477 .last()
478 .unwrap();
479 let u_plane = image
480 .u_plane
481 .chunks_exact(image.u_stride as usize)
482 .last()
483 .unwrap();
484 let v_plane = image
485 .v_plane
486 .chunks_exact(image.v_stride as usize)
487 .last()
488 .unwrap();
489 let y_plane = image
490 .y_plane
491 .chunks_exact(image.y_stride as usize)
492 .last()
493 .unwrap();
494 process_halved_chroma_row(
495 &y_plane[0..image.width as usize],
496 &u_plane[0..(image.width as usize).div_ceil(2)],
497 &v_plane[0..(image.width as usize).div_ceil(2)],
498 &mut rgba[0..image.width as usize * channels],
499 );
500 }
501 } else {
502 unreachable!();
503 }
504
505 Ok(())
506}
507
508macro_rules! build_cnv {
509 ($method: ident, $px_fmt: expr, $sampling: expr, $bit_depth: expr, $sampling_written: expr, $px_written: expr, $px_written_small: expr) => {
510 #[doc = concat!("
511Convert ",$sampling_written, " planar format with ", $bit_depth," bit pixel format to ", $px_written," float16 format.
512
513This function takes ", $sampling_written, " planar data with ",$bit_depth," bit precision.
514and converts it to ", $px_written," format with float16 image.
515
516# Arguments
517
518* `planar_image` - Source ",$sampling_written," planar image.
519* `", $px_written_small, "` - A mutable slice to store the converted ", $px_written," float16 format.
520* `", $px_written_small, "_stride` - The stride (components per row) for ", $px_written," float16 format.
521* `range` - The YUV range (limited or full).
522* `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
523
524# Panics
525
526This function panics if the lengths of the planes or the input ", $px_written," data are not valid based
527on the specified width, height, and strides, or if invalid YUV range or matrix is provided.")]
528 pub fn $method(
529 planar_image: &YuvPlanarImage<u16>,
530 dst: &mut [f16],
531 dst_stride: u32,
532 range: YuvRange,
533 matrix: YuvStandardMatrix,
534 ) -> Result<(), YuvError> {
535 yuv_p16_to_image_p16_ant::<
536 { $px_fmt as u8 },
537 { $sampling as u8 },
538 { YuvEndianness::LittleEndian as u8 },
539 { YuvBytesPacking::LeastSignificantBytes as u8 },
540 $bit_depth>(planar_image, dst, dst_stride, range, matrix)
541 }
542 };
543}
544
545build_cnv!(
546 i010_to_rgba_f16,
547 YuvSourceChannels::Rgba,
548 YuvChromaSubsampling::Yuv420,
549 10,
550 "I010",
551 "RGBA",
552 "rgba"
553);
554
555build_cnv!(
556 i010_to_rgb_f16,
557 YuvSourceChannels::Rgb,
558 YuvChromaSubsampling::Yuv420,
559 10,
560 "YUV 420",
561 "RGB",
562 "rgb"
563);
564
565build_cnv!(
566 i012_to_rgba_f16,
567 YuvSourceChannels::Rgba,
568 YuvChromaSubsampling::Yuv420,
569 12,
570 "I010",
571 "RGBA",
572 "rgba"
573);
574
575build_cnv!(
576 i012_to_rgb_f16,
577 YuvSourceChannels::Rgb,
578 YuvChromaSubsampling::Yuv420,
579 12,
580 "I012",
581 "RGB",
582 "rgb"
583);
584
585build_cnv!(
586 i014_to_rgba_f16,
587 YuvSourceChannels::Rgba,
588 YuvChromaSubsampling::Yuv420,
589 14,
590 "I014",
591 "RGBA",
592 "rgba"
593);
594
595build_cnv!(
596 i014_to_rgb_f16,
597 YuvSourceChannels::Rgb,
598 YuvChromaSubsampling::Yuv420,
599 14,
600 "I014",
601 "RGB",
602 "rgb"
603);
604
605build_cnv!(
606 i210_to_rgba_f16,
607 YuvSourceChannels::Rgba,
608 YuvChromaSubsampling::Yuv422,
609 10,
610 "I210",
611 "RGBA",
612 "rgba"
613);
614
615build_cnv!(
616 i210_to_rgb_f16,
617 YuvSourceChannels::Rgb,
618 YuvChromaSubsampling::Yuv422,
619 10,
620 "I210",
621 "RGB",
622 "rgb"
623);
624
625build_cnv!(
626 i212_to_rgba_f16,
627 YuvSourceChannels::Rgba,
628 YuvChromaSubsampling::Yuv422,
629 12,
630 "I212",
631 "RGBA",
632 "rgba"
633);
634
635build_cnv!(
636 i212_to_rgb_f16,
637 YuvSourceChannels::Rgb,
638 YuvChromaSubsampling::Yuv422,
639 12,
640 "I212",
641 "RGB",
642 "rgb"
643);
644
645build_cnv!(
646 i214_to_rgba_f16,
647 YuvSourceChannels::Rgba,
648 YuvChromaSubsampling::Yuv422,
649 14,
650 "I214",
651 "RGBA",
652 "rgba"
653);
654
655build_cnv!(
656 i214_to_rgb_f16,
657 YuvSourceChannels::Rgb,
658 YuvChromaSubsampling::Yuv422,
659 14,
660 "I214",
661 "RGB",
662 "rgb"
663);
664
665build_cnv!(
666 i410_to_rgba_f16,
667 YuvSourceChannels::Rgba,
668 YuvChromaSubsampling::Yuv444,
669 10,
670 "I410",
671 "RGBA",
672 "rgba"
673);
674
675build_cnv!(
676 i410_to_rgb_f16,
677 YuvSourceChannels::Rgb,
678 YuvChromaSubsampling::Yuv444,
679 10,
680 "I410",
681 "RGB",
682 "rgb"
683);
684
685build_cnv!(
686 i412_to_rgba_f16,
687 YuvSourceChannels::Rgba,
688 YuvChromaSubsampling::Yuv444,
689 12,
690 "I412",
691 "RGBA",
692 "rgba"
693);
694
695build_cnv!(
696 i412_to_rgb_f16,
697 YuvSourceChannels::Rgb,
698 YuvChromaSubsampling::Yuv444,
699 12,
700 "I412",
701 "RGB",
702 "rgb"
703);
704
705build_cnv!(
706 i414_to_rgba_f16,
707 YuvSourceChannels::Rgba,
708 YuvChromaSubsampling::Yuv444,
709 14,
710 "I414",
711 "RGBA",
712 "rgba"
713);
714
715build_cnv!(
716 i414_to_rgb_f16,
717 YuvSourceChannels::Rgb,
718 YuvChromaSubsampling::Yuv444,
719 14,
720 "I414",
721 "RGB",
722 "rgb"
723);
724
725#[cfg(test)]
726#[cfg(feature = "nightly_f16")]
727mod tests {
728 use super::*;
729 use crate::{rgb10_to_i210, rgb10_to_i410, YuvPlanarImageMut};
730 use rand::Rng;
731
732 #[test]
733 fn test_yuv444_f16_round_trip_full_range() {
734 let image_width = 256usize;
735 let image_height = 256usize;
736
737 let random_point_x = rand::rng().random_range(0..image_width);
738 let random_point_y = rand::rng().random_range(0..image_height);
739
740 const CHANNELS: usize = 3;
741
742 let pixel_points = [
743 [0, 0],
744 [image_width - 1, image_height - 1],
745 [image_width - 1, 0],
746 [0, image_height - 1],
747 [(image_width - 1) / 2, (image_height - 1) / 2],
748 [image_width / 5, image_height / 5],
749 [0, image_height / 5],
750 [image_width / 5, 0],
751 [image_width / 5 * 3, image_height / 5],
752 [image_width / 5 * 3, image_height / 5 * 3],
753 [image_width / 5, image_height / 5 * 3],
754 [random_point_x, random_point_y],
755 ];
756 let mut image_rgb = vec![0u16; image_width * image_height * 3];
757
758 let or = rand::rng().random_range(0..1024) as u16;
759 let og = rand::rng().random_range(0..1024) as u16;
760 let ob = rand::rng().random_range(0..1024) as u16;
761
762 for point in &pixel_points {
763 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
764 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
765 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
766 }
767
768 let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
769 image_width as u32,
770 image_height as u32,
771 YuvChromaSubsampling::Yuv444,
772 );
773
774 rgb10_to_i410(
775 &mut planar_image,
776 &image_rgb,
777 image_width as u32 * CHANNELS as u32,
778 YuvRange::Full,
779 YuvStandardMatrix::Bt709,
780 )
781 .unwrap();
782
783 let mut image_rgb: Vec<f16> = vec![0.; image_width * image_height * 3];
784
785 let fixed_planar = planar_image.to_fixed();
786
787 i410_to_rgb_f16(
788 &fixed_planar,
789 &mut image_rgb,
790 image_width as u32 * CHANNELS as u32,
791 YuvRange::Full,
792 YuvStandardMatrix::Bt709,
793 )
794 .unwrap();
795
796 for point in &pixel_points {
797 let x = point[0];
798 let y = point[1];
799 let r = (image_rgb[x * CHANNELS + y * image_width * CHANNELS] as f32 * 1023.).round();
800 let g =
801 (image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1] as f32 * 1023.).round();
802 let b =
803 (image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2] as f32 * 1023.).round();
804
805 let diff_r = (r as i32 - or as i32).abs();
806 let diff_g = (g as i32 - og as i32).abs();
807 let diff_b = (b as i32 - ob as i32).abs();
808
809 assert!(
810 diff_r <= 150,
811 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
812 diff_r,
813 [or, og, ob],
814 [r, g, b]
815 );
816 assert!(
817 diff_g <= 150,
818 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
819 diff_g,
820 [or, og, ob],
821 [r, g, b]
822 );
823 assert!(
824 diff_b <= 150,
825 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
826 diff_b,
827 [or, og, ob],
828 [r, g, b]
829 );
830 }
831 }
832
833 #[test]
834 fn test_yuv422_f16_round_trip_limited_range() {
835 let image_width = 256usize;
836 let image_height = 256usize;
837
838 let random_point_x = rand::rng().random_range(0..image_width);
839 let random_point_y = rand::rng().random_range(0..image_height);
840
841 const CHANNELS: usize = 3;
842
843 let pixel_points = [
844 [0, 0],
845 [image_width - 1, image_height - 1],
846 [image_width - 1, 0],
847 [0, image_height - 1],
848 [(image_width - 1) / 2, (image_height - 1) / 2],
849 [image_width / 5, image_height / 5],
850 [0, image_height / 5],
851 [image_width / 5, 0],
852 [image_width / 5 * 3, image_height / 5],
853 [image_width / 5 * 3, image_height / 5 * 3],
854 [image_width / 5, image_height / 5 * 3],
855 [random_point_x, random_point_y],
856 ];
857
858 let mut source_rgb = vec![0u16; image_width * image_height * CHANNELS];
859
860 let or = rand::rng().random_range(0..1024) as u16;
861 let og = rand::rng().random_range(0..1024) as u16;
862 let ob = rand::rng().random_range(0..1024) as u16;
863
864 for point in &pixel_points {
865 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
866 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
867 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
868
869 let nx = (point[0] + 1).min(image_width - 1);
870 let ny = point[1].min(image_height - 1);
871
872 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
873 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
874 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
875
876 let nx = point[0].saturating_sub(1).min(image_width - 1);
877 let ny = point[1].min(image_height - 1);
878
879 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
880 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
881 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
882 }
883
884 let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
885 image_width as u32,
886 image_height as u32,
887 YuvChromaSubsampling::Yuv422,
888 );
889
890 rgb10_to_i210(
891 &mut planar_image,
892 &source_rgb,
893 image_width as u32 * CHANNELS as u32,
894 YuvRange::Limited,
895 YuvStandardMatrix::Bt709,
896 )
897 .unwrap();
898
899 let mut dest_rgb: Vec<f16> = vec![0.; image_width * image_height * CHANNELS];
900
901 let fixed_planar = planar_image.to_fixed();
902
903 i210_to_rgb_f16(
904 &fixed_planar,
905 &mut dest_rgb,
906 image_width as u32 * CHANNELS as u32,
907 YuvRange::Limited,
908 YuvStandardMatrix::Bt709,
909 )
910 .unwrap();
911
912 for point in &pixel_points {
913 let x = point[0];
914 let y = point[1];
915 let px = x * CHANNELS + y * image_width * CHANNELS;
916
917 let r = (dest_rgb[px] as f32 * 1023.).round();
918 let g = (dest_rgb[px + 1] as f32 * 1023.).round();
919 let b = (dest_rgb[px + 2] as f32 * 1023.).round();
920
921 let diff_r = r as i32 - or as i32;
922 let diff_g = g as i32 - og as i32;
923 let diff_b = b as i32 - ob as i32;
924
925 assert!(
926 diff_r <= 264,
927 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
928 diff_r,
929 [or, og, ob],
930 [r, g, b]
931 );
932 assert!(
933 diff_g <= 264,
934 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
935 diff_g,
936 [or, og, ob],
937 [r, g, b]
938 );
939 assert!(
940 diff_b <= 264,
941 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
942 diff_b,
943 [or, og, ob],
944 [r, g, b]
945 );
946 }
947 }
948}