yuvutils_rs/
y_to_rgb.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk, 10/2024. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification,
5 * are permitted provided that the following conditions are met:
6 *
7 * 1.  Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 *
10 * 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 *
14 * 3.  Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use 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
210// Chroma subsampling always assumed as 400
211fn 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
278/// Convert YUV 400 planar format to RGB format.
279///
280/// This function takes YUV 400 planar format data with 8-bit precision,
281/// and converts it to RGB format with 8-bit per channel precision.
282///
283/// # Arguments
284///
285/// * `gray_image` - Source YUV gray image.
286/// * `rgb_data` - A mutable slice to store the converted RGB data.
287/// * `range` - The YUV range (limited or full).
288/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
289///
290/// # Panics
291///
292/// This function panics if the lengths of the planes or the input RGB data are not valid based
293/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
294///
295pub 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
305/// Convert YUV 400 planar format to BGR format.
306///
307/// This function takes YUV 400 planar format data with 8-bit precision,
308/// and converts it to BGR format with 8-bit per channel precision.
309///
310/// # Arguments
311///
312/// * `gray_image` - Source YUV gray image.
313/// * `rgb_data` - A mutable slice to store the converted BGR data.
314/// * `range` - The YUV range (limited or full).
315/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
316///
317/// # Panics
318///
319/// This function panics if the lengths of the planes or the input BGR data are not valid based
320/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
321///
322pub 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
332/// Convert YUV 400 planar format to RGBA format.
333///
334/// This function takes YUV 400 planar format data with 8-bit precision,
335/// and converts it to RGBA format with 8-bit per channel precision.
336///
337/// # Arguments
338///
339/// * `gray_image` - Source YUV gray image.
340/// * `rgba_data` - A mutable slice to store the converted RGBA data.
341/// * `range` - The YUV range (limited or full).
342/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
343///
344/// # Panics
345///
346/// This function panics if the lengths of the planes or the input BGRA data are not valid based
347/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
348///
349pub 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
359/// Convert YUV 400 planar format to BGRA format.
360///
361/// This function takes YUV 400 planar format data with 8-bit precision,
362/// and converts it to BGRA format with 8-bit per channel precision.
363///
364/// # Arguments
365///
366/// * `gray_image` - Source YUV gray image.
367/// * `bgra_data` - A mutable slice to store the converted BGRA data.
368/// * `range` - The YUV range (limited or full).
369/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
370///
371/// # Panics
372///
373/// This function panics if the lengths of the planes or the input BGRA data are not valid based
374/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
375///
376pub 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}