1#[cfg(all(
30 any(target_arch = "x86", target_arch = "x86_64"),
31 feature = "nightly_avx512"
32))]
33use crate::avx512bw::avx512_yuv_p16_to_rgba16_row;
34#[allow(unused_imports)]
35use crate::internals::ProcessedOffset;
36use crate::internals::WideRowInversionHandler;
37#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
38use crate::neon::neon_yuv_p16_to_rgba16_row;
39use crate::numerics::{qrshr, to_ne};
40use crate::yuv_error::check_rgba_destination;
41use crate::yuv_support::{
42 get_yuv_range, search_inverse_transform, CbCrInverseTransform, YuvBytesPacking, YuvChromaRange,
43 YuvChromaSubsampling, YuvEndianness, YuvRange, YuvSourceChannels, YuvStandardMatrix,
44};
45use crate::{YuvError, YuvPlanarImage};
46#[cfg(feature = "rayon")]
47use rayon::iter::{IndexedParallelIterator, ParallelIterator};
48#[cfg(feature = "rayon")]
49use rayon::prelude::{ParallelSlice, ParallelSliceMut};
50
51struct WideRowAnyHandler<
52 const DESTINATION_CHANNELS: u8,
53 const SAMPLING: u8,
54 const ENDIANNESS: u8,
55 const BYTES_POSITION: u8,
56 const PRECISION: i32,
57 const BIT_DEPTH: usize,
58> {
59 handler: Option<
60 unsafe fn(
61 y_ld_ptr: &[u16],
62 u_ld_ptr: &[u16],
63 v_ld_ptr: &[u16],
64 rgba: &mut [u16],
65 width: u32,
66 range: &YuvChromaRange,
67 transform: &CbCrInverseTransform<i32>,
68 start_cx: usize,
69 start_ux: usize,
70 ) -> ProcessedOffset,
71 >,
72}
73
74impl<
75 const DESTINATION_CHANNELS: u8,
76 const SAMPLING: u8,
77 const ENDIANNESS: u8,
78 const BYTES_POSITION: u8,
79 const PRECISION: i32,
80 const BIT_DEPTH: usize,
81 > Default
82 for WideRowAnyHandler<
83 DESTINATION_CHANNELS,
84 SAMPLING,
85 ENDIANNESS,
86 BYTES_POSITION,
87 PRECISION,
88 BIT_DEPTH,
89 >
90{
91 fn default() -> WideRowAnyHandler<
92 DESTINATION_CHANNELS,
93 SAMPLING,
94 ENDIANNESS,
95 BYTES_POSITION,
96 PRECISION,
97 BIT_DEPTH,
98 > {
99 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
100 {
101 #[cfg(feature = "rdm")]
102 {
103 let is_rdm_available = std::arch::is_aarch64_feature_detected!("rdm");
104 if is_rdm_available && BIT_DEPTH == 10 {
105 use crate::neon::neon_yuv_p16_to_rgba16_row_rdm;
106 return WideRowAnyHandler {
107 handler: Some(
108 neon_yuv_p16_to_rgba16_row_rdm::<
109 DESTINATION_CHANNELS,
110 SAMPLING,
111 ENDIANNESS,
112 BYTES_POSITION,
113 PRECISION,
114 BIT_DEPTH,
115 >,
116 ),
117 };
118 }
119 }
120 if BIT_DEPTH <= 16 {
121 return WideRowAnyHandler {
122 handler: Some(
123 neon_yuv_p16_to_rgba16_row::<
124 DESTINATION_CHANNELS,
125 SAMPLING,
126 ENDIANNESS,
127 BYTES_POSITION,
128 PRECISION,
129 BIT_DEPTH,
130 >,
131 ),
132 };
133 }
134 }
135 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
136 {
137 #[cfg(feature = "sse")]
138 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
139 #[cfg(feature = "avx")]
140 let use_avx = std::arch::is_x86_feature_detected!("avx2");
141 #[cfg(feature = "nightly_avx512")]
142 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
143 #[cfg(feature = "nightly_avx512")]
144 if use_avx512 && BIT_DEPTH <= 12 {
145 return WideRowAnyHandler {
146 handler: Some(
147 avx512_yuv_p16_to_rgba16_row::<
148 DESTINATION_CHANNELS,
149 SAMPLING,
150 ENDIANNESS,
151 BYTES_POSITION,
152 BIT_DEPTH,
153 PRECISION,
154 >,
155 ),
156 };
157 }
158 #[cfg(feature = "avx")]
159 {
160 if use_avx {
161 if BIT_DEPTH == 10 {
162 assert_eq!(BIT_DEPTH, 10);
163 use crate::avx2::avx_yuv_p16_to_rgba_row;
164 return WideRowAnyHandler {
165 handler: Some(
166 avx_yuv_p16_to_rgba_row::<
167 DESTINATION_CHANNELS,
168 SAMPLING,
169 ENDIANNESS,
170 BYTES_POSITION,
171 BIT_DEPTH,
172 PRECISION,
173 >,
174 ),
175 };
176 } else {
177 use crate::avx2::avx_yuv_p16_to_rgba_d16_row;
178 return WideRowAnyHandler {
179 handler: Some(
180 avx_yuv_p16_to_rgba_d16_row::<
181 DESTINATION_CHANNELS,
182 SAMPLING,
183 ENDIANNESS,
184 BYTES_POSITION,
185 BIT_DEPTH,
186 PRECISION,
187 >,
188 ),
189 };
190 }
191 }
192 }
193 #[cfg(feature = "sse")]
194 if use_sse && BIT_DEPTH <= 12 {
195 use crate::sse::sse_yuv_p16_to_rgba_row;
196 return WideRowAnyHandler {
197 handler: Some(
198 sse_yuv_p16_to_rgba_row::<
199 DESTINATION_CHANNELS,
200 SAMPLING,
201 ENDIANNESS,
202 BYTES_POSITION,
203 BIT_DEPTH,
204 PRECISION,
205 >,
206 ),
207 };
208 }
209 }
210 WideRowAnyHandler { handler: None }
211 }
212}
213
214impl<
215 const DESTINATION_CHANNELS: u8,
216 const SAMPLING: u8,
217 const ENDIANNESS: u8,
218 const BYTES_POSITION: u8,
219 const PRECISION: i32,
220 const BIT_DEPTH: usize,
221 > WideRowInversionHandler<u16, i32>
222 for WideRowAnyHandler<
223 DESTINATION_CHANNELS,
224 SAMPLING,
225 ENDIANNESS,
226 BYTES_POSITION,
227 PRECISION,
228 BIT_DEPTH,
229 >
230{
231 #[inline]
232 fn handle_row(
233 &self,
234 y_plane: &[u16],
235 u_plane: &[u16],
236 v_plane: &[u16],
237 rgba: &mut [u16],
238 width: u32,
239 yuv_chroma_range: YuvChromaRange,
240 transform: &CbCrInverseTransform<i32>,
241 ) -> ProcessedOffset {
242 if let Some(handler) = self.handler {
243 unsafe {
244 return handler(
245 y_plane,
246 u_plane,
247 v_plane,
248 rgba,
249 width,
250 &yuv_chroma_range,
251 transform,
252 0,
253 0,
254 );
255 }
256 }
257 ProcessedOffset { cx: 0, ux: 0 }
258 }
259}
260
261fn yuv_p16_to_image_p16_ant<
262 const DESTINATION_CHANNELS: u8,
263 const SAMPLING: u8,
264 const ENDIANNESS: u8,
265 const BYTES_POSITION: u8,
266 const BIT_DEPTH: usize,
267>(
268 image: &YuvPlanarImage<u16>,
269 rgba16: &mut [u16],
270 rgba_stride: u32,
271 range: YuvRange,
272 matrix: YuvStandardMatrix,
273) -> Result<(), YuvError> {
274 let dst_chans: YuvSourceChannels = DESTINATION_CHANNELS.into();
275 let channels = dst_chans.get_channels_count();
276
277 let chroma_subsampling: YuvChromaSubsampling = SAMPLING.into();
278 let chroma_range = get_yuv_range(BIT_DEPTH as u32, range);
279
280 image.check_constraints(chroma_subsampling)?;
281 check_rgba_destination(rgba16, rgba_stride, image.width, image.height, channels)?;
282
283 let kr_kb = matrix.get_kr_kb();
284 let max_range_p16 = ((1u32 << BIT_DEPTH as u32) - 1) as i32;
285 const PRECISION: i32 = 13;
286
287 let i_transform = search_inverse_transform(
288 PRECISION,
289 BIT_DEPTH as u32,
290 range,
291 matrix,
292 chroma_range,
293 kr_kb,
294 );
295
296 let cr_coef = i_transform.cr_coef;
297 let cb_coef = i_transform.cb_coef;
298 let y_coef = i_transform.y_coef;
299 let g_coef_1 = i_transform.g_coeff_1;
300 let g_coef_2 = i_transform.g_coeff_2;
301
302 let bias_y = chroma_range.bias_y as i32;
303 let bias_uv = chroma_range.bias_uv as i32;
304
305 let msb_shift = (16 - BIT_DEPTH) as i32;
306 let wide_row_handler = WideRowAnyHandler::<
307 DESTINATION_CHANNELS,
308 SAMPLING,
309 ENDIANNESS,
310 BYTES_POSITION,
311 PRECISION,
312 BIT_DEPTH,
313 >::default();
314
315 let process_halved_chroma_row = |y_plane: &[u16],
316 u_plane: &[u16],
317 v_plane: &[u16],
318 rgba: &mut [u16]| {
319 let cx = wide_row_handler
320 .handle_row(
321 y_plane,
322 u_plane,
323 v_plane,
324 rgba,
325 image.width,
326 chroma_range,
327 &i_transform,
328 )
329 .cx;
330 if cx != image.width as usize {
331 for (((rgba, y_src), &u_src), &v_src) in rgba
332 .chunks_exact_mut(channels * 2)
333 .zip(y_plane.chunks_exact(2))
334 .zip(u_plane.iter())
335 .zip(v_plane.iter())
336 .skip(cx / 2)
337 {
338 let y_value0 = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src[0], msb_shift) as i32
339 - bias_y)
340 * y_coef;
341 let cb_value =
342 to_ne::<ENDIANNESS, BYTES_POSITION>(u_src, msb_shift) as i32 - bias_uv;
343 let cr_value =
344 to_ne::<ENDIANNESS, BYTES_POSITION>(v_src, msb_shift) as i32 - bias_uv;
345
346 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
347 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
348 let g0 = qrshr::<PRECISION, BIT_DEPTH>(
349 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
350 );
351
352 let rgba0 = &mut rgba[0..channels];
353
354 rgba0[dst_chans.get_r_channel_offset()] = r0 as u16;
355 rgba0[dst_chans.get_g_channel_offset()] = g0 as u16;
356 rgba0[dst_chans.get_b_channel_offset()] = b0 as u16;
357 if dst_chans.has_alpha() {
358 rgba0[dst_chans.get_a_channel_offset()] = max_range_p16 as u16;
359 }
360
361 let y_value1 = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src[1], msb_shift) as i32
362 - bias_y)
363 * y_coef;
364
365 let r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value);
366 let b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value);
367 let g1 = qrshr::<PRECISION, BIT_DEPTH>(
368 y_value1 - g_coef_1 * cr_value - g_coef_2 * cb_value,
369 );
370
371 let rgba1 = &mut rgba[channels..channels * 2];
372
373 rgba1[dst_chans.get_r_channel_offset()] = r1 as u16;
374 rgba1[dst_chans.get_g_channel_offset()] = g1 as u16;
375 rgba1[dst_chans.get_b_channel_offset()] = b1 as u16;
376 if dst_chans.has_alpha() {
377 rgba1[dst_chans.get_a_channel_offset()] = max_range_p16 as u16;
378 }
379 }
380
381 if image.width & 1 != 0 {
382 let y_value0 =
383 (to_ne::<ENDIANNESS, BYTES_POSITION>(*y_plane.last().unwrap(), msb_shift)
384 as i32
385 - bias_y)
386 * y_coef;
387 let cb_value =
388 to_ne::<ENDIANNESS, BYTES_POSITION>(*u_plane.last().unwrap(), msb_shift) as i32
389 - bias_uv;
390 let cr_value =
391 to_ne::<ENDIANNESS, BYTES_POSITION>(*v_plane.last().unwrap(), msb_shift) as i32
392 - bias_uv;
393 let rgba = rgba.chunks_exact_mut(channels).last().unwrap();
394 let rgba0 = &mut rgba[0..channels];
395
396 let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value);
397 let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value);
398 let g0 = qrshr::<PRECISION, BIT_DEPTH>(
399 y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
400 );
401 rgba0[dst_chans.get_r_channel_offset()] = r0 as u16;
402 rgba0[dst_chans.get_g_channel_offset()] = g0 as u16;
403 rgba0[dst_chans.get_b_channel_offset()] = b0 as u16;
404 if dst_chans.has_alpha() {
405 rgba0[dst_chans.get_a_channel_offset()] = max_range_p16 as u16;
406 }
407 }
408 }
409 };
410
411 if chroma_subsampling == YuvChromaSubsampling::Yuv444 {
412 let iter;
413 #[cfg(feature = "rayon")]
414 {
415 iter = rgba16
416 .par_chunks_exact_mut(rgba_stride as usize)
417 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
418 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
419 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
420 }
421 #[cfg(not(feature = "rayon"))]
422 {
423 iter = rgba16
424 .chunks_exact_mut(rgba_stride as usize)
425 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
426 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
427 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
428 }
429 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
430 let y_plane = &y_plane[0..image.width as usize];
431 let cx = wide_row_handler
432 .handle_row(
433 y_plane,
434 u_plane,
435 v_plane,
436 rgba,
437 image.width,
438 chroma_range,
439 &i_transform,
440 )
441 .cx;
442 if cx != image.width as usize {
443 for (((rgba, &y_src), &u_src), &v_src) in rgba
444 .chunks_exact_mut(channels)
445 .zip(y_plane.iter())
446 .zip(u_plane.iter())
447 .zip(v_plane.iter())
448 .skip(cx)
449 {
450 let y_value = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src, msb_shift) as i32
451 - bias_y)
452 * y_coef;
453 let cb_value =
454 to_ne::<ENDIANNESS, BYTES_POSITION>(u_src, msb_shift) as i32 - bias_uv;
455 let cr_value =
456 to_ne::<ENDIANNESS, BYTES_POSITION>(v_src, msb_shift) as i32 - bias_uv;
457
458 let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value);
459 let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value);
460 let g = qrshr::<PRECISION, BIT_DEPTH>(
461 y_value - g_coef_1 * cr_value - g_coef_2 * cb_value,
462 );
463
464 rgba[dst_chans.get_r_channel_offset()] = r as u16;
465 rgba[dst_chans.get_g_channel_offset()] = g as u16;
466 rgba[dst_chans.get_b_channel_offset()] = b as u16;
467 if dst_chans.has_alpha() {
468 rgba[dst_chans.get_a_channel_offset()] = max_range_p16 as u16;
469 }
470 }
471 }
472 });
473 } else if chroma_subsampling == YuvChromaSubsampling::Yuv422 {
474 let iter;
475 #[cfg(feature = "rayon")]
476 {
477 iter = rgba16
478 .par_chunks_exact_mut(rgba_stride as usize)
479 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
480 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
481 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
482 }
483 #[cfg(not(feature = "rayon"))]
484 {
485 iter = rgba16
486 .chunks_exact_mut(rgba_stride as usize)
487 .zip(image.y_plane.chunks_exact(image.y_stride as usize))
488 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
489 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
490 }
491 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
492 process_halved_chroma_row(
493 &y_plane[0..image.width as usize],
494 &u_plane[0..(image.width as usize).div_ceil(2)],
495 &v_plane[0..(image.width as usize).div_ceil(2)],
496 &mut rgba[0..image.width as usize * channels],
497 );
498 });
499 } else if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
500 let iter;
501 #[cfg(feature = "rayon")]
502 {
503 iter = rgba16
504 .par_chunks_exact_mut(rgba_stride as usize * 2)
505 .zip(image.y_plane.par_chunks_exact(image.y_stride as usize * 2))
506 .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
507 .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
508 }
509 #[cfg(not(feature = "rayon"))]
510 {
511 iter = rgba16
512 .chunks_exact_mut(rgba_stride as usize * 2)
513 .zip(image.y_plane.chunks_exact(image.y_stride as usize * 2))
514 .zip(image.u_plane.chunks_exact(image.u_stride as usize))
515 .zip(image.v_plane.chunks_exact(image.v_stride as usize));
516 }
517 iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
518 for (rgba, y_plane) in rgba
519 .chunks_exact_mut(rgba_stride as usize)
520 .zip(y_plane.chunks_exact(image.y_stride as usize))
521 {
522 process_halved_chroma_row(
523 &y_plane[0..image.width as usize],
524 &u_plane[0..(image.width as usize).div_ceil(2)],
525 &v_plane[0..(image.width as usize).div_ceil(2)],
526 &mut rgba[0..image.width as usize * channels],
527 );
528 }
529 });
530
531 if image.height & 1 != 0 {
532 let rgba = rgba16
533 .chunks_exact_mut(rgba_stride as usize)
534 .last()
535 .unwrap();
536 let u_plane = image
537 .u_plane
538 .chunks_exact(image.u_stride as usize)
539 .last()
540 .unwrap();
541 let v_plane = image
542 .v_plane
543 .chunks_exact(image.v_stride as usize)
544 .last()
545 .unwrap();
546 let y_plane = image
547 .y_plane
548 .chunks_exact(image.y_stride as usize)
549 .last()
550 .unwrap();
551 process_halved_chroma_row(
552 &y_plane[0..image.width as usize],
553 &u_plane[0..(image.width as usize).div_ceil(2)],
554 &v_plane[0..(image.width as usize).div_ceil(2)],
555 &mut rgba[0..image.width as usize * channels],
556 );
557 }
558 } else {
559 unreachable!();
560 }
561
562 Ok(())
563}
564
565macro_rules! d_cnv {
566 ($method: ident, $px_fmt: expr, $sampling: expr, $endian: expr, $sampling_written: expr, $px_written: expr, $px_written_small: expr, $bit_depth: expr) => {
567 #[doc = concat!("
568Convert ",$sampling_written, " planar format with ", stringify!($bit_depth), " bit pixel format to ", $px_written," ", stringify!($bit_depth), " bit-depth format.
569
570This function takes ", $sampling_written, " planar data with ", stringify!($bit_depth), " bit precision.
571and converts it to ", $px_written," format with ", stringify!($bit_depth), " bit-depth precision per channel
572
573# Arguments
574
575* `planar_image` - Source ",$sampling_written," planar image.
576* `", $px_written_small, "` - A mutable slice to store the converted ", $px_written," ", stringify!($bit_depth), " bit-depth data.
577* `", $px_written_small, "_stride` - The stride (components per row) for ", $px_written," ", stringify!($bit_depth), " bit-depth data.
578* `range` - The YUV range (limited or full).
579* `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
580* `endianness` - The endianness of stored bytes
581* `bytes_packing` - see [YuvBytesPacking] for more info.
582* `bit_depth` - Bit depth of source YUV planes, only 10 and 12 is supported.
583
584# Panics
585
586This function panics if the lengths of the planes or the input ", $px_written," data are not valid based
587on the specified width, height, and strides, or if invalid YUV range or matrix is provided.")]
588 pub fn $method(
589 planar_image: &YuvPlanarImage<u16>,
590 dst: &mut [u16],
591 dst_stride: u32,
592 range: YuvRange,
593 matrix: YuvStandardMatrix,
594 ) -> Result<(), YuvError> {
595 yuv_p16_to_image_p16_ant::<{ $px_fmt as u8 },
596 { $sampling as u8 },
597 { $endian as u8 },
598 { YuvBytesPacking::LeastSignificantBytes as u8 }, $bit_depth>(
599 planar_image, dst, dst_stride, range, matrix)
600 }
601 };
602}
603
604d_cnv!(
605 i010_to_rgba10,
606 YuvSourceChannels::Rgba,
607 YuvChromaSubsampling::Yuv420,
608 YuvEndianness::LittleEndian,
609 "I010",
610 "RGBA",
611 "rgba",
612 10
613);
614#[cfg(feature = "big_endian")]
615d_cnv!(
616 i010_be_to_rgba10,
617 YuvSourceChannels::Rgba,
618 YuvChromaSubsampling::Yuv420,
619 YuvEndianness::BigEndian,
620 "I010BE",
621 "RGBA",
622 "rgba",
623 10
624);
625d_cnv!(
626 i010_to_rgb10,
627 YuvSourceChannels::Rgb,
628 YuvChromaSubsampling::Yuv420,
629 YuvEndianness::LittleEndian,
630 "I010",
631 "RGB",
632 "rgb",
633 10
634);
635#[cfg(feature = "big_endian")]
636d_cnv!(
637 i010_be_to_rgb10,
638 YuvSourceChannels::Rgb,
639 YuvChromaSubsampling::Yuv420,
640 YuvEndianness::BigEndian,
641 "I010BE",
642 "RGB",
643 "rgb",
644 10
645);
646d_cnv!(
647 i210_to_rgba10,
648 YuvSourceChannels::Rgba,
649 YuvChromaSubsampling::Yuv422,
650 YuvEndianness::LittleEndian,
651 "I210",
652 "RGBA",
653 "rgba",
654 10
655);
656#[cfg(feature = "big_endian")]
657d_cnv!(
658 i210_be_to_rgba10,
659 YuvSourceChannels::Rgba,
660 YuvChromaSubsampling::Yuv422,
661 YuvEndianness::BigEndian,
662 "I210BE",
663 "RGBA",
664 "rgba",
665 10
666);
667d_cnv!(
668 i210_to_rgb10,
669 YuvSourceChannels::Rgb,
670 YuvChromaSubsampling::Yuv422,
671 YuvEndianness::LittleEndian,
672 "I210",
673 "RGB",
674 "rgb",
675 10
676);
677#[cfg(feature = "big_endian")]
678d_cnv!(
679 i210_be_to_rgb10,
680 YuvSourceChannels::Rgb,
681 YuvChromaSubsampling::Yuv422,
682 YuvEndianness::BigEndian,
683 "I210BE",
684 "RGB",
685 "rgb",
686 10
687);
688d_cnv!(
689 i410_to_rgba10,
690 YuvSourceChannels::Rgba,
691 YuvChromaSubsampling::Yuv444,
692 YuvEndianness::LittleEndian,
693 "I410",
694 "RGBA",
695 "rgba",
696 10
697);
698#[cfg(feature = "big_endian")]
699d_cnv!(
700 i410_be_to_rgba10,
701 YuvSourceChannels::Rgba,
702 YuvChromaSubsampling::Yuv444,
703 YuvEndianness::BigEndian,
704 "I410BE",
705 "RGBA",
706 "rgba",
707 10
708);
709d_cnv!(
710 i410_to_rgb10,
711 YuvSourceChannels::Rgb,
712 YuvChromaSubsampling::Yuv444,
713 YuvEndianness::LittleEndian,
714 "I410",
715 "RGB",
716 "rgb",
717 10
718);
719#[cfg(feature = "big_endian")]
720d_cnv!(
721 i410_be_to_rgb10,
722 YuvSourceChannels::Rgb,
723 YuvChromaSubsampling::Yuv444,
724 YuvEndianness::BigEndian,
725 "I410BE",
726 "RGB",
727 "rgb",
728 10
729);
730
731d_cnv!(
732 i012_to_rgba12,
733 YuvSourceChannels::Rgba,
734 YuvChromaSubsampling::Yuv420,
735 YuvEndianness::LittleEndian,
736 "I012",
737 "RGBA",
738 "rgba",
739 12
740);
741#[cfg(feature = "big_endian")]
742d_cnv!(
743 i012_be_to_rgba12,
744 YuvSourceChannels::Rgba,
745 YuvChromaSubsampling::Yuv420,
746 YuvEndianness::BigEndian,
747 "I012BE",
748 "RGBA",
749 "rgba",
750 12
751);
752d_cnv!(
753 i012_to_rgb12,
754 YuvSourceChannels::Rgb,
755 YuvChromaSubsampling::Yuv420,
756 YuvEndianness::LittleEndian,
757 "I012",
758 "RGB",
759 "rgb",
760 12
761);
762#[cfg(feature = "big_endian")]
763d_cnv!(
764 i012_be_to_rgb12,
765 YuvSourceChannels::Rgb,
766 YuvChromaSubsampling::Yuv420,
767 YuvEndianness::BigEndian,
768 "I012BE",
769 "RGB",
770 "rgb",
771 12
772);
773d_cnv!(
774 i212_to_rgba12,
775 YuvSourceChannels::Rgba,
776 YuvChromaSubsampling::Yuv422,
777 YuvEndianness::LittleEndian,
778 "I212",
779 "RGBA",
780 "rgba",
781 12
782);
783#[cfg(feature = "big_endian")]
784d_cnv!(
785 i212_be_to_rgba12,
786 YuvSourceChannels::Rgba,
787 YuvChromaSubsampling::Yuv422,
788 YuvEndianness::BigEndian,
789 "I212BE",
790 "RGBA",
791 "rgba",
792 12
793);
794d_cnv!(
795 i212_to_rgb12,
796 YuvSourceChannels::Rgb,
797 YuvChromaSubsampling::Yuv422,
798 YuvEndianness::LittleEndian,
799 "I212",
800 "RGB",
801 "rgb",
802 12
803);
804#[cfg(feature = "big_endian")]
805d_cnv!(
806 i212_be_to_rgb12,
807 YuvSourceChannels::Rgb,
808 YuvChromaSubsampling::Yuv422,
809 YuvEndianness::BigEndian,
810 "I212BE",
811 "RGB",
812 "rgb",
813 12
814);
815d_cnv!(
816 i412_to_rgba12,
817 YuvSourceChannels::Rgba,
818 YuvChromaSubsampling::Yuv444,
819 YuvEndianness::LittleEndian,
820 "I412",
821 "RGBA",
822 "rgba",
823 12
824);
825#[cfg(feature = "big_endian")]
826d_cnv!(
827 i412_be_to_rgba12,
828 YuvSourceChannels::Rgba,
829 YuvChromaSubsampling::Yuv444,
830 YuvEndianness::BigEndian,
831 "I412BE",
832 "RGBA",
833 "rgba",
834 12
835);
836d_cnv!(
837 i412_to_rgb12,
838 YuvSourceChannels::Rgb,
839 YuvChromaSubsampling::Yuv444,
840 YuvEndianness::LittleEndian,
841 "I412",
842 "RGB",
843 "rgb",
844 12
845);
846#[cfg(feature = "big_endian")]
847d_cnv!(
848 i412_be_to_rgb12,
849 YuvSourceChannels::Rgb,
850 YuvChromaSubsampling::Yuv444,
851 YuvEndianness::BigEndian,
852 "I412BE",
853 "RGB",
854 "rgb",
855 12
856);
857
858d_cnv!(
861 i014_to_rgba14,
862 YuvSourceChannels::Rgba,
863 YuvChromaSubsampling::Yuv420,
864 YuvEndianness::LittleEndian,
865 "I014",
866 "RGBA",
867 "rgba",
868 14
869);
870#[cfg(feature = "big_endian")]
871d_cnv!(
872 i014_be_to_rgba14,
873 YuvSourceChannels::Rgba,
874 YuvChromaSubsampling::Yuv420,
875 YuvEndianness::BigEndian,
876 "I014BE",
877 "RGBA",
878 "rgba",
879 14
880);
881d_cnv!(
882 i014_to_rgb14,
883 YuvSourceChannels::Rgb,
884 YuvChromaSubsampling::Yuv420,
885 YuvEndianness::LittleEndian,
886 "I014",
887 "RGB",
888 "rgb",
889 14
890);
891#[cfg(feature = "big_endian")]
892d_cnv!(
893 i014_be_to_rgb14,
894 YuvSourceChannels::Rgb,
895 YuvChromaSubsampling::Yuv420,
896 YuvEndianness::BigEndian,
897 "I014BE",
898 "RGB",
899 "rgb",
900 14
901);
902
903d_cnv!(
906 i214_to_rgba14,
907 YuvSourceChannels::Rgba,
908 YuvChromaSubsampling::Yuv422,
909 YuvEndianness::LittleEndian,
910 "I214",
911 "RGBA",
912 "rgba",
913 14
914);
915#[cfg(feature = "big_endian")]
916d_cnv!(
917 i214_be_to_rgba14,
918 YuvSourceChannels::Rgba,
919 YuvChromaSubsampling::Yuv422,
920 YuvEndianness::BigEndian,
921 "I214BE",
922 "RGBA",
923 "rgba",
924 14
925);
926d_cnv!(
927 i214_to_rgb14,
928 YuvSourceChannels::Rgb,
929 YuvChromaSubsampling::Yuv422,
930 YuvEndianness::LittleEndian,
931 "I214",
932 "RGB",
933 "rgb",
934 14
935);
936#[cfg(feature = "big_endian")]
937d_cnv!(
938 i214_be_to_rgb14,
939 YuvSourceChannels::Rgb,
940 YuvChromaSubsampling::Yuv422,
941 YuvEndianness::BigEndian,
942 "I214BE",
943 "RGB",
944 "rgb",
945 14
946);
947
948d_cnv!(
951 i414_to_rgba14,
952 YuvSourceChannels::Rgba,
953 YuvChromaSubsampling::Yuv444,
954 YuvEndianness::LittleEndian,
955 "I414",
956 "RGBA",
957 "rgba",
958 14
959);
960#[cfg(feature = "big_endian")]
961d_cnv!(
962 i414_be_to_rgba14,
963 YuvSourceChannels::Rgba,
964 YuvChromaSubsampling::Yuv444,
965 YuvEndianness::BigEndian,
966 "I414BE",
967 "RGBA",
968 "rgba",
969 14
970);
971d_cnv!(
972 i414_to_rgb14,
973 YuvSourceChannels::Rgb,
974 YuvChromaSubsampling::Yuv444,
975 YuvEndianness::LittleEndian,
976 "I414",
977 "RGB",
978 "rgb",
979 14
980);
981#[cfg(feature = "big_endian")]
982d_cnv!(
983 i414_be_to_rgb14,
984 YuvSourceChannels::Rgb,
985 YuvChromaSubsampling::Yuv444,
986 YuvEndianness::BigEndian,
987 "I414BE",
988 "RGB",
989 "rgb",
990 14
991);
992
993d_cnv!(
996 i016_to_rgba16,
997 YuvSourceChannels::Rgba,
998 YuvChromaSubsampling::Yuv420,
999 YuvEndianness::LittleEndian,
1000 "I016",
1001 "RGBA",
1002 "rgba",
1003 16
1004);
1005#[cfg(feature = "big_endian")]
1006d_cnv!(
1007 i016_be_to_rgba16,
1008 YuvSourceChannels::Rgba,
1009 YuvChromaSubsampling::Yuv420,
1010 YuvEndianness::BigEndian,
1011 "I016BE",
1012 "RGBA",
1013 "rgba",
1014 16
1015);
1016d_cnv!(
1017 i016_to_rgb16,
1018 YuvSourceChannels::Rgb,
1019 YuvChromaSubsampling::Yuv420,
1020 YuvEndianness::LittleEndian,
1021 "I016",
1022 "RGB",
1023 "rgb",
1024 16
1025);
1026#[cfg(feature = "big_endian")]
1027d_cnv!(
1028 i016_be_to_rgb16,
1029 YuvSourceChannels::Rgb,
1030 YuvChromaSubsampling::Yuv420,
1031 YuvEndianness::BigEndian,
1032 "I016BE",
1033 "RGB",
1034 "rgb",
1035 16
1036);
1037
1038d_cnv!(
1041 i216_to_rgba16,
1042 YuvSourceChannels::Rgba,
1043 YuvChromaSubsampling::Yuv422,
1044 YuvEndianness::LittleEndian,
1045 "I216",
1046 "RGBA",
1047 "rgba",
1048 16
1049);
1050#[cfg(feature = "big_endian")]
1051d_cnv!(
1052 i216_be_to_rgba16,
1053 YuvSourceChannels::Rgba,
1054 YuvChromaSubsampling::Yuv422,
1055 YuvEndianness::BigEndian,
1056 "I216BE",
1057 "RGBA",
1058 "rgba",
1059 16
1060);
1061d_cnv!(
1062 i216_to_rgb16,
1063 YuvSourceChannels::Rgb,
1064 YuvChromaSubsampling::Yuv422,
1065 YuvEndianness::LittleEndian,
1066 "I216",
1067 "RGB",
1068 "rgb",
1069 16
1070);
1071#[cfg(feature = "big_endian")]
1072d_cnv!(
1073 i216_be_to_rgb16,
1074 YuvSourceChannels::Rgb,
1075 YuvChromaSubsampling::Yuv422,
1076 YuvEndianness::BigEndian,
1077 "I216BE",
1078 "RGB",
1079 "rgb",
1080 16
1081);
1082
1083d_cnv!(
1086 i416_to_rgba16,
1087 YuvSourceChannels::Rgba,
1088 YuvChromaSubsampling::Yuv444,
1089 YuvEndianness::LittleEndian,
1090 "I416",
1091 "RGBA",
1092 "rgba",
1093 16
1094);
1095#[cfg(feature = "big_endian")]
1096d_cnv!(
1097 i416_be_to_rgba16,
1098 YuvSourceChannels::Rgba,
1099 YuvChromaSubsampling::Yuv444,
1100 YuvEndianness::BigEndian,
1101 "I416BE",
1102 "RGBA",
1103 "rgba",
1104 16
1105);
1106d_cnv!(
1107 i416_to_rgb16,
1108 YuvSourceChannels::Rgb,
1109 YuvChromaSubsampling::Yuv444,
1110 YuvEndianness::LittleEndian,
1111 "I416",
1112 "RGB",
1113 "rgb",
1114 16
1115);
1116#[cfg(feature = "big_endian")]
1117d_cnv!(
1118 i416_be_to_rgb16,
1119 YuvSourceChannels::Rgb,
1120 YuvChromaSubsampling::Yuv444,
1121 YuvEndianness::BigEndian,
1122 "I416BE",
1123 "RGB",
1124 "rgb",
1125 16
1126);
1127
1128#[cfg(test)]
1129mod tests {
1130 use super::*;
1131 use crate::{rgb10_to_i010, rgb10_to_i210, rgb10_to_i410, YuvPlanarImageMut};
1132 use rand::Rng;
1133
1134 #[test]
1135 fn test_yuv444_p16_round_trip_full_range() {
1136 let image_width = 256usize;
1137 let image_height = 256usize;
1138
1139 let random_point_x = rand::rng().random_range(0..image_width);
1140 let random_point_y = rand::rng().random_range(0..image_height);
1141
1142 const CHANNELS: usize = 3;
1143
1144 let pixel_points = [
1145 [0, 0],
1146 [image_width - 1, image_height - 1],
1147 [image_width - 1, 0],
1148 [0, image_height - 1],
1149 [(image_width - 1) / 2, (image_height - 1) / 2],
1150 [image_width / 5, image_height / 5],
1151 [0, image_height / 5],
1152 [image_width / 5, 0],
1153 [image_width / 5 * 3, image_height / 5],
1154 [image_width / 5 * 3, image_height / 5 * 3],
1155 [image_width / 5, image_height / 5 * 3],
1156 [random_point_x, random_point_y],
1157 ];
1158 let mut image_rgb = vec![0u16; image_width * image_height * 3];
1159
1160 let or = rand::rng().random_range(0..1024) as u16;
1161 let og = rand::rng().random_range(0..1024) as u16;
1162 let ob = rand::rng().random_range(0..1024) as u16;
1163
1164 for point in &pixel_points {
1165 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1166 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1167 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1168 }
1169
1170 let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
1171 image_width as u32,
1172 image_height as u32,
1173 YuvChromaSubsampling::Yuv444,
1174 );
1175
1176 rgb10_to_i410(
1177 &mut planar_image,
1178 &image_rgb,
1179 image_width as u32 * CHANNELS as u32,
1180 YuvRange::Full,
1181 YuvStandardMatrix::Bt709,
1182 )
1183 .unwrap();
1184
1185 image_rgb.fill(0);
1186
1187 let fixed_planar = planar_image.to_fixed();
1188
1189 i410_to_rgb10(
1190 &fixed_planar,
1191 &mut image_rgb,
1192 image_width as u32 * CHANNELS as u32,
1193 YuvRange::Full,
1194 YuvStandardMatrix::Bt709,
1195 )
1196 .unwrap();
1197
1198 for point in &pixel_points {
1199 let x = point[0];
1200 let y = point[1];
1201 let r = image_rgb[x * CHANNELS + y * image_width * CHANNELS];
1202 let g = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1];
1203 let b = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2];
1204
1205 let diff_r = (r as i32 - or as i32).abs();
1206 let diff_g = (g as i32 - og as i32).abs();
1207 let diff_b = (b as i32 - ob as i32).abs();
1208
1209 assert!(
1210 diff_r <= 4,
1211 "Original RGB {:?}, Round-tripped RGB {:?}",
1212 [or, og, ob],
1213 [r, g, b]
1214 );
1215 assert!(
1216 diff_g <= 4,
1217 "Original RGB {:?}, Round-tripped RGB {:?}",
1218 [or, og, ob],
1219 [r, g, b]
1220 );
1221 assert!(
1222 diff_b <= 4,
1223 "Original RGB {:?}, Round-tripped RGB {:?}",
1224 [or, og, ob],
1225 [r, g, b]
1226 );
1227 }
1228 }
1229
1230 #[test]
1231 fn test_yuv444_p10_round_trip_limited_range() {
1232 let image_width = 256usize;
1233 let image_height = 256usize;
1234
1235 let random_point_x = rand::rng().random_range(0..image_width);
1236 let random_point_y = rand::rng().random_range(0..image_height);
1237
1238 const CHANNELS: usize = 3;
1239
1240 let pixel_points = [
1241 [0, 0],
1242 [image_width - 1, image_height - 1],
1243 [image_width - 1, 0],
1244 [0, image_height - 1],
1245 [(image_width - 1) / 2, (image_height - 1) / 2],
1246 [image_width / 5, image_height / 5],
1247 [0, image_height / 5],
1248 [image_width / 5, 0],
1249 [image_width / 5 * 3, image_height / 5],
1250 [image_width / 5 * 3, image_height / 5 * 3],
1251 [image_width / 5, image_height / 5 * 3],
1252 [random_point_x, random_point_y],
1253 ];
1254 let mut image_rgb = vec![0u16; image_width * image_height * 3];
1255
1256 let or = rand::rng().random_range(0..1024) as u16;
1257 let og = rand::rng().random_range(0..1024) as u16;
1258 let ob = rand::rng().random_range(0..1024) as u16;
1259
1260 for point in &pixel_points {
1261 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1262 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1263 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1264 }
1265
1266 let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
1267 image_width as u32,
1268 image_height as u32,
1269 YuvChromaSubsampling::Yuv444,
1270 );
1271
1272 rgb10_to_i410(
1273 &mut planar_image,
1274 &image_rgb,
1275 image_width as u32 * CHANNELS as u32,
1276 YuvRange::Limited,
1277 YuvStandardMatrix::Bt709,
1278 )
1279 .unwrap();
1280
1281 image_rgb.fill(0);
1282
1283 let fixed_planar = planar_image.to_fixed();
1284
1285 i410_to_rgb10(
1286 &fixed_planar,
1287 &mut image_rgb,
1288 image_width as u32 * CHANNELS as u32,
1289 YuvRange::Limited,
1290 YuvStandardMatrix::Bt709,
1291 )
1292 .unwrap();
1293
1294 for point in &pixel_points {
1295 let x = point[0];
1296 let y = point[1];
1297 let r = image_rgb[x * CHANNELS + y * image_width * CHANNELS];
1298 let g = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1];
1299 let b = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2];
1300
1301 let diff_r = (r as i32 - or as i32).abs();
1302 let diff_g = (g as i32 - og as i32).abs();
1303 let diff_b = (b as i32 - ob as i32).abs();
1304
1305 assert!(
1306 diff_r <= 280,
1307 "Diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1308 diff_r,
1309 [or, og, ob],
1310 [r, g, b]
1311 );
1312 assert!(
1313 diff_g <= 280,
1314 "Diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1315 diff_g,
1316 [or, og, ob],
1317 [r, g, b]
1318 );
1319 assert!(
1320 diff_b <= 280,
1321 "Diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1322 diff_b,
1323 [or, og, ob],
1324 [r, g, b]
1325 );
1326 }
1327 }
1328
1329 #[test]
1330 fn test_yuv422_p16_round_trip_limited_range() {
1331 let image_width = 256usize;
1332 let image_height = 256usize;
1333
1334 let random_point_x = rand::rng().random_range(0..image_width);
1335 let random_point_y = rand::rng().random_range(0..image_height);
1336
1337 const CHANNELS: usize = 3;
1338
1339 let pixel_points = [
1340 [0, 0],
1341 [image_width - 1, image_height - 1],
1342 [image_width - 1, 0],
1343 [0, image_height - 1],
1344 [(image_width - 1) / 2, (image_height - 1) / 2],
1345 [image_width / 5, image_height / 5],
1346 [0, image_height / 5],
1347 [image_width / 5, 0],
1348 [image_width / 5 * 3, image_height / 5],
1349 [image_width / 5 * 3, image_height / 5 * 3],
1350 [image_width / 5, image_height / 5 * 3],
1351 [random_point_x, random_point_y],
1352 ];
1353
1354 let mut source_rgb = vec![0u16; image_width * image_height * CHANNELS];
1355
1356 let or = rand::rng().random_range(0..1024) as u16;
1357 let og = rand::rng().random_range(0..1024) as u16;
1358 let ob = rand::rng().random_range(0..1024) as u16;
1359
1360 for point in &pixel_points {
1361 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1362 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1363 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1364
1365 let nx = (point[0] + 1).min(image_width - 1);
1366 let ny = point[1].min(image_height - 1);
1367
1368 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1369 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1370 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1371
1372 let nx = point[0].saturating_sub(1).min(image_width - 1);
1373 let ny = point[1].min(image_height - 1);
1374
1375 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1376 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1377 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1378 }
1379
1380 let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
1381 image_width as u32,
1382 image_height as u32,
1383 YuvChromaSubsampling::Yuv422,
1384 );
1385
1386 rgb10_to_i210(
1387 &mut planar_image,
1388 &source_rgb,
1389 image_width as u32 * CHANNELS as u32,
1390 YuvRange::Limited,
1391 YuvStandardMatrix::Bt709,
1392 )
1393 .unwrap();
1394
1395 let mut dest_rgb = vec![0u16; image_width * image_height * CHANNELS];
1396
1397 let fixed_planar = planar_image.to_fixed();
1398
1399 i210_to_rgb10(
1400 &fixed_planar,
1401 &mut dest_rgb,
1402 image_width as u32 * CHANNELS as u32,
1403 YuvRange::Limited,
1404 YuvStandardMatrix::Bt709,
1405 )
1406 .unwrap();
1407
1408 for point in &pixel_points {
1409 let x = point[0];
1410 let y = point[1];
1411 let px = x * CHANNELS + y * image_width * CHANNELS;
1412
1413 let r = dest_rgb[px];
1414 let g = dest_rgb[px + 1];
1415 let b = dest_rgb[px + 2];
1416
1417 let diff_r = r as i32 - or as i32;
1418 let diff_g = g as i32 - og as i32;
1419 let diff_b = b as i32 - ob as i32;
1420
1421 assert!(
1422 diff_r <= 260,
1423 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1424 diff_r,
1425 [or, og, ob],
1426 [r, g, b]
1427 );
1428 assert!(
1429 diff_g <= 260,
1430 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1431 diff_g,
1432 [or, og, ob],
1433 [r, g, b]
1434 );
1435 assert!(
1436 diff_b <= 260,
1437 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1438 diff_b,
1439 [or, og, ob],
1440 [r, g, b]
1441 );
1442 }
1443 }
1444
1445 #[test]
1446 fn test_yuv420_p16_round_trip_limited_range() {
1447 let image_width = 256usize;
1448 let image_height = 256usize;
1449
1450 let random_point_x = rand::rng().random_range(0..image_width);
1451 let random_point_y = rand::rng().random_range(0..image_height);
1452
1453 const CHANNELS: usize = 3;
1454
1455 let pixel_points = [
1456 [0, 0],
1457 [image_width - 1, image_height - 1],
1458 [image_width - 1, 0],
1459 [0, image_height - 1],
1460 [(image_width - 1) / 2, (image_height - 1) / 2],
1461 [image_width / 5, image_height / 5],
1462 [0, image_height / 5],
1463 [image_width / 5, 0],
1464 [image_width / 5 * 3, image_height / 5],
1465 [image_width / 5 * 3, image_height / 5 * 3],
1466 [image_width / 5, image_height / 5 * 3],
1467 [random_point_x, random_point_y],
1468 ];
1469
1470 let mut source_rgb = vec![0u16; image_width * image_height * CHANNELS];
1471
1472 let or = rand::rng().random_range(0..1024) as u16;
1473 let og = rand::rng().random_range(0..1024) as u16;
1474 let ob = rand::rng().random_range(0..1024) as u16;
1475
1476 for point in &pixel_points {
1477 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
1478 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
1479 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
1480
1481 let nx = (point[0] + 1).min(image_width - 1);
1482 let ny = point[1].min(image_height - 1);
1483
1484 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1485 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1486 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1487
1488 let nx = (point[0] + 1).min(image_width - 1);
1489 let ny = (point[1] + 1).min(image_height - 1);
1490
1491 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1492 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1493 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1494
1495 let nx = point[0].min(image_width - 1);
1496 let ny = (point[1] + 1).min(image_height - 1);
1497
1498 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1499 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1500 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1501
1502 let nx = point[0].saturating_sub(1).min(image_width - 1);
1503 let ny = point[1].saturating_sub(1).min(image_height - 1);
1504
1505 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1506 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1507 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1508
1509 let nx = point[0].min(image_width - 1);
1510 let ny = point[1].saturating_sub(1).min(image_height - 1);
1511
1512 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1513 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1514 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1515
1516 let nx = point[0].saturating_sub(1).min(image_width - 1);
1517 let ny = point[1].min(image_height - 1);
1518
1519 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
1520 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
1521 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
1522 }
1523
1524 let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
1525 image_width as u32,
1526 image_height as u32,
1527 YuvChromaSubsampling::Yuv420,
1528 );
1529
1530 rgb10_to_i010(
1531 &mut planar_image,
1532 &source_rgb,
1533 image_width as u32 * CHANNELS as u32,
1534 YuvRange::Limited,
1535 YuvStandardMatrix::Bt709,
1536 )
1537 .unwrap();
1538
1539 let mut dest_rgb = vec![0u16; image_width * image_height * CHANNELS];
1540
1541 let fixed_planar = planar_image.to_fixed();
1542
1543 i010_to_rgb10(
1544 &fixed_planar,
1545 &mut dest_rgb,
1546 image_width as u32 * CHANNELS as u32,
1547 YuvRange::Limited,
1548 YuvStandardMatrix::Bt709,
1549 )
1550 .unwrap();
1551
1552 for point in &pixel_points {
1553 let x = point[0];
1554 let y = point[1];
1555 let px = x * CHANNELS + y * image_width * CHANNELS;
1556
1557 let r = dest_rgb[px];
1558 let g = dest_rgb[px + 1];
1559 let b = dest_rgb[px + 2];
1560
1561 let diff_r = r as i32 - or as i32;
1562 let diff_g = g as i32 - og as i32;
1563 let diff_b = b as i32 - ob as i32;
1564
1565 assert!(
1566 diff_r <= 310,
1567 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1568 diff_r,
1569 [or, og, ob],
1570 [r, g, b]
1571 );
1572 assert!(
1573 diff_g <= 310,
1574 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1575 diff_g,
1576 [or, og, ob],
1577 [r, g, b]
1578 );
1579 assert!(
1580 diff_b <= 310,
1581 "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
1582 diff_b,
1583 [or, og, ob],
1584 [r, g, b]
1585 );
1586 }
1587 }
1588}