1use crate::yuv_error::check_rgba_destination;
30use crate::yuv_support::*;
31use crate::{YuvError, YuvGrayImage};
32#[cfg(feature = "rayon")]
33use rayon::iter::{IndexedParallelIterator, ParallelIterator};
34#[cfg(feature = "rayon")]
35use rayon::prelude::{ParallelSlice, ParallelSliceMut};
36
37type RgbFullHandler = unsafe fn(&mut [u8], &[u8], usize);
38
39type RgbLimitedHandler = unsafe fn(
40 range: &YuvChromaRange,
41 transform: &CbCrInverseTransform<i32>,
42 y_plane: &[u8],
43 rgba: &mut [u8],
44 start_cx: usize,
45 width: usize,
46);
47
48#[inline(always)]
49fn default_full_converter<const CN: u8>(rgba: &mut [u8], y_plane: &[u8], _: usize) {
50 let cn: YuvSourceChannels = CN.into();
51
52 for (y_src, rgba) in y_plane
53 .iter()
54 .zip(rgba.chunks_exact_mut(cn.get_channels_count()))
55 {
56 let r = *y_src;
57 rgba[cn.get_r_channel_offset()] = r;
58 rgba[cn.get_g_channel_offset()] = r;
59 rgba[cn.get_b_channel_offset()] = r;
60 if cn.has_alpha() {
61 rgba[cn.get_a_channel_offset()] = 255;
62 }
63 }
64}
65
66#[inline(always)]
67#[cfg(not(any(
68 all(target_arch = "aarch64", target_feature = "neon"),
69 all(target_arch = "wasm32", target_feature = "simd128")
70)))]
71unsafe fn default_limited_converter<const CN: u8, const PRECISION: i32>(
72 range: &YuvChromaRange,
73 transform: &CbCrInverseTransform<i32>,
74 y_plane: &[u8],
75 rgba: &mut [u8],
76 _: usize,
77 _: usize,
78) {
79 let cn: YuvSourceChannels = CN.into();
80 let ts = transform.cast::<i16>();
81 let bias_y = range.bias_y as i16;
82
83 for (y_src, rgba) in y_plane
84 .iter()
85 .zip(rgba.chunks_exact_mut(cn.get_channels_count()))
86 {
87 use crate::numerics::qrshr;
88
89 let y0 = *y_src as i16;
90 let y_value = (y0 - bias_y) as i32 * ts.y_coef as i32;
91
92 let r = qrshr::<PRECISION, 8>(y_value);
93 rgba[cn.get_r_channel_offset()] = r as u8;
94 rgba[cn.get_g_channel_offset()] = r as u8;
95 rgba[cn.get_b_channel_offset()] = r as u8;
96 if cn.has_alpha() {
97 rgba[cn.get_a_channel_offset()] = 255;
98 }
99 }
100}
101
102#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
103#[target_feature(enable = "sse4.1")]
104unsafe fn default_full_converter_sse4_1<const CN: u8>(
105 rgba: &mut [u8],
106 y_plane: &[u8],
107 width: usize,
108) {
109 default_full_converter::<CN>(rgba, y_plane, width);
110}
111
112#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "avx"))]
113#[target_feature(enable = "avx2")]
114unsafe fn default_full_converter_avx2<const CN: u8>(rgba: &mut [u8], y_plane: &[u8], width: usize) {
115 default_full_converter::<CN>(rgba, y_plane, width);
116}
117
118#[cfg(all(
119 any(target_arch = "x86", target_arch = "x86_64"),
120 feature = "nightly_avx512"
121))]
122#[target_feature(enable = "avx512bw")]
123unsafe fn default_full_converter_avx512<const CN: u8>(
124 rgba: &mut [u8],
125 y_plane: &[u8],
126 width: usize,
127) {
128 default_full_converter::<CN>(rgba, y_plane, width);
129}
130
131fn make_full_converter<const CN: u8>() -> RgbFullHandler {
132 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
133 {
134 #[cfg(feature = "nightly_avx512")]
135 {
136 if std::arch::is_x86_feature_detected!("avx512bw") {
137 return default_full_converter_avx512::<CN>;
138 }
139 }
140 #[cfg(feature = "avx")]
141 {
142 if std::arch::is_x86_feature_detected!("avx2") {
143 return default_full_converter_avx2::<CN>;
144 }
145 }
146 #[cfg(feature = "sse")]
147 {
148 if std::arch::is_x86_feature_detected!("sse4.1") {
149 return default_full_converter_sse4_1::<CN>;
150 }
151 }
152 }
153 default_full_converter::<CN>
154}
155
156fn make_limited_converter<const CN: u8, const PRECISION: i32>() -> RgbLimitedHandler {
157 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
158 {
159 #[cfg(feature = "rdm")]
160 {
161 if std::arch::is_aarch64_feature_detected!("rdm") {
162 use crate::neon::neon_y_to_rgb_row_rdm;
163 return neon_y_to_rgb_row_rdm::<CN>;
164 }
165 }
166 use crate::neon::neon_y_to_rgb_row;
167 neon_y_to_rgb_row::<CN>
168 }
169 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
170 {
171 #[cfg(feature = "nightly_avx512")]
172 {
173 use crate::avx512bw::avx512_y_to_rgb_row;
174 if std::arch::is_x86_feature_detected!("avx512bw") {
175 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
176 return if use_vbmi {
177 avx512_y_to_rgb_row::<CN, true>
178 } else {
179 avx512_y_to_rgb_row::<CN, false>
180 };
181 }
182 }
183 #[cfg(feature = "avx")]
184 {
185 if std::arch::is_x86_feature_detected!("avx2") {
186 use crate::avx2::avx2_y_to_rgba_row;
187 return avx2_y_to_rgba_row::<CN>;
188 }
189 }
190 #[cfg(feature = "sse")]
191 {
192 if std::arch::is_x86_feature_detected!("sse4.1") {
193 use crate::sse::sse_y_to_rgba_row;
194 return sse_y_to_rgba_row::<CN>;
195 }
196 }
197 }
198 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
199 {
200 use crate::wasm32::wasm_y_to_rgb_row;
201 return wasm_y_to_rgb_row::<CN>;
202 }
203 #[cfg(not(any(
204 all(target_arch = "aarch64", target_feature = "neon"),
205 all(target_arch = "wasm32", target_feature = "simd128")
206 )))]
207 default_limited_converter::<CN, PRECISION>
208}
209
210fn y_to_rgbx<const CN: u8>(
212 image: &YuvGrayImage<u8>,
213 rgba: &mut [u8],
214 rgba_stride: u32,
215 range: YuvRange,
216 matrix: YuvStandardMatrix,
217) -> Result<(), YuvError> {
218 let cn: YuvSourceChannels = CN.into();
219 let channels = cn.get_channels_count();
220
221 check_rgba_destination(rgba, rgba_stride, image.width, image.height, channels)?;
222 image.check_constraints()?;
223
224 let chroma_range = get_yuv_range(8, range);
225 let kr_kb = matrix.get_kr_kb();
226
227 const PRECISION: i32 = 13;
228 let inverse_transform =
229 search_inverse_transform(PRECISION, 8, range, matrix, chroma_range, kr_kb);
230
231 let y_plane = image.y_plane;
232 let y_stride = image.y_stride;
233
234 let iter;
235 let y_iter;
236 #[cfg(feature = "rayon")]
237 {
238 iter = rgba.par_chunks_exact_mut(rgba_stride as usize);
239 y_iter = y_plane.par_chunks_exact(y_stride as usize);
240 }
241 #[cfg(not(feature = "rayon"))]
242 {
243 iter = rgba.chunks_exact_mut(rgba_stride as usize);
244 y_iter = y_plane.chunks_exact(y_stride as usize);
245 }
246
247 if range == YuvRange::Limited {
248 let executor = make_limited_converter::<CN, PRECISION>();
249
250 iter.zip(y_iter).for_each(|(rgba, y_plane)| {
251 let y_plane = &y_plane[0..image.width as usize];
252 let rgba = &mut rgba[..channels * image.width as usize];
253
254 unsafe {
255 executor(
256 &chroma_range,
257 &inverse_transform,
258 y_plane,
259 rgba,
260 0,
261 image.width as usize,
262 );
263 }
264 });
265 } else {
266 let executor = make_full_converter::<CN>();
267 iter.zip(y_iter).for_each(|(rgba, y_plane)| {
268 let y_plane = &y_plane[0..image.width as usize];
269 unsafe {
270 executor(rgba, y_plane, image.width as usize);
271 }
272 });
273 }
274
275 Ok(())
276}
277
278pub fn yuv400_to_rgb(
296 gray_image: &YuvGrayImage<u8>,
297 rgb: &mut [u8],
298 rgb_stride: u32,
299 range: YuvRange,
300 matrix: YuvStandardMatrix,
301) -> Result<(), YuvError> {
302 y_to_rgbx::<{ YuvSourceChannels::Rgb as u8 }>(gray_image, rgb, rgb_stride, range, matrix)
303}
304
305pub fn yuv400_to_bgr(
323 gray_image: &YuvGrayImage<u8>,
324 bgr: &mut [u8],
325 bgr_stride: u32,
326 range: YuvRange,
327 matrix: YuvStandardMatrix,
328) -> Result<(), YuvError> {
329 y_to_rgbx::<{ YuvSourceChannels::Bgr as u8 }>(gray_image, bgr, bgr_stride, range, matrix)
330}
331
332pub fn yuv400_to_rgba(
350 gray_image: &YuvGrayImage<u8>,
351 rgba: &mut [u8],
352 rgba_stride: u32,
353 range: YuvRange,
354 matrix: YuvStandardMatrix,
355) -> Result<(), YuvError> {
356 y_to_rgbx::<{ YuvSourceChannels::Rgba as u8 }>(gray_image, rgba, rgba_stride, range, matrix)
357}
358
359pub fn yuv400_to_bgra(
377 gray_image: &YuvGrayImage<u8>,
378 bgra: &mut [u8],
379 bgra_stride: u32,
380 range: YuvRange,
381 matrix: YuvStandardMatrix,
382) -> Result<(), YuvError> {
383 y_to_rgbx::<{ YuvSourceChannels::Bgra as u8 }>(gray_image, bgra, bgra_stride, range, matrix)
384}