yuvutils_rs/rgb_to_y.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 */
29#[cfg(all(
30 any(target_arch = "x86", target_arch = "x86_64"),
31 feature = "nightly_avx512"
32))]
33use crate::avx512bw::avx512_row_rgb_to_y;
34use crate::images::YuvGrayImageMut;
35#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
36use crate::neon::neon_rgb_to_y_row;
37use crate::yuv_error::check_rgba_destination;
38use crate::yuv_support::*;
39use crate::YuvError;
40#[cfg(feature = "rayon")]
41use rayon::iter::{IndexedParallelIterator, ParallelIterator};
42#[cfg(feature = "rayon")]
43use rayon::prelude::{ParallelSlice, ParallelSliceMut};
44
45// Chroma subsampling always assumed as YUV 400
46fn rgbx_to_y<const ORIGIN_CHANNELS: u8>(
47 gray_image: &mut YuvGrayImageMut<u8>,
48 rgba: &[u8],
49 rgba_stride: u32,
50 range: YuvRange,
51 matrix: YuvStandardMatrix,
52) -> Result<(), YuvError> {
53 let source_channels: YuvSourceChannels = ORIGIN_CHANNELS.into();
54 let channels = source_channels.get_channels_count();
55
56 check_rgba_destination(
57 rgba,
58 rgba_stride,
59 gray_image.width,
60 gray_image.height,
61 channels,
62 )?;
63 gray_image.check_constraints()?;
64
65 let chroma_range = get_yuv_range(8, range);
66 let kr_kb = matrix.get_kr_kb();
67 const PRECISION: i32 = 13;
68
69 let transform = search_forward_transform(PRECISION, 8, range, matrix, chroma_range, kr_kb);
70
71 const ROUNDING_CONST_BIAS: i32 = (1 << (PRECISION - 1)) - 1;
72 let bias_y = chroma_range.bias_y as i32 * (1 << PRECISION) + ROUNDING_CONST_BIAS;
73
74 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
75 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
76 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "avx"))]
77 let use_avx = std::arch::is_x86_feature_detected!("avx2");
78 #[cfg(all(
79 any(target_arch = "x86", target_arch = "x86_64"),
80 feature = "nightly_avx512"
81 ))]
82 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
83 #[cfg(all(
84 any(target_arch = "x86", target_arch = "x86_64"),
85 feature = "nightly_avx512"
86 ))]
87 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
88 #[cfg(all(
89 any(target_arch = "x86", target_arch = "x86_64"),
90 feature = "nightly_avx512"
91 ))]
92 let avx512_dispatch = if use_vbmi {
93 avx512_row_rgb_to_y::<ORIGIN_CHANNELS, true>
94 } else {
95 avx512_row_rgb_to_y::<ORIGIN_CHANNELS, false>
96 };
97 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
98 let neon_handler = if std::arch::is_aarch64_feature_detected!("rdm") {
99 #[cfg(feature = "rdm")]
100 {
101 use crate::neon::neon_rgb_to_y_rdm;
102 neon_rgb_to_y_rdm::<ORIGIN_CHANNELS>
103 }
104 #[cfg(not(feature = "rdm"))]
105 {
106 neon_rgb_to_y_row::<ORIGIN_CHANNELS, PRECISION>
107 }
108 } else {
109 neon_rgb_to_y_row::<ORIGIN_CHANNELS, PRECISION>
110 };
111
112 let iter;
113 #[cfg(feature = "rayon")]
114 {
115 iter = gray_image
116 .y_plane
117 .borrow_mut()
118 .par_chunks_exact_mut(gray_image.y_stride as usize)
119 .zip(rgba.par_chunks_exact(rgba_stride as usize));
120 }
121 #[cfg(not(feature = "rayon"))]
122 {
123 iter = gray_image
124 .y_plane
125 .borrow_mut()
126 .chunks_exact_mut(gray_image.y_stride as usize)
127 .zip(rgba.chunks_exact(rgba_stride as usize));
128 }
129
130 iter.for_each(|(y_plane, rgba)| {
131 let mut _cx = 0usize;
132
133 let y_plane = &mut y_plane[0..gray_image.width as usize];
134
135 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
136 {
137 #[cfg(feature = "nightly_avx512")]
138 if use_avx512 {
139 let processed_offset = avx512_dispatch(
140 &transform,
141 &chroma_range,
142 y_plane,
143 rgba,
144 _cx,
145 gray_image.width as usize,
146 );
147 _cx = processed_offset;
148 }
149 #[cfg(feature = "avx")]
150 if use_avx {
151 use crate::avx2::avx2_rgb_to_y_row;
152 let processed_offset = avx2_rgb_to_y_row::<ORIGIN_CHANNELS, PRECISION>(
153 &transform,
154 &chroma_range,
155 y_plane,
156 rgba,
157 _cx,
158 gray_image.width as usize,
159 );
160 _cx = processed_offset;
161 }
162 #[cfg(feature = "sse")]
163 if use_sse {
164 use crate::sse::sse_rgb_to_y;
165 let processed_offset = sse_rgb_to_y::<ORIGIN_CHANNELS, PRECISION>(
166 &transform,
167 &chroma_range,
168 y_plane,
169 rgba,
170 _cx,
171 gray_image.width as usize,
172 );
173 _cx = processed_offset;
174 }
175 }
176
177 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
178 unsafe {
179 _cx = neon_handler(
180 &transform,
181 &chroma_range,
182 y_plane.as_mut_ptr(),
183 rgba,
184 _cx,
185 gray_image.width as usize,
186 );
187 }
188
189 for (y_dst, rgba) in y_plane
190 .iter_mut()
191 .zip(rgba.chunks_exact(channels))
192 .skip(_cx)
193 {
194 let r = rgba[source_channels.get_r_channel_offset()] as i32;
195 let g = rgba[source_channels.get_g_channel_offset()] as i32;
196 let b = rgba[source_channels.get_b_channel_offset()] as i32;
197 let y = (r * transform.yr + g * transform.yg + b * transform.yb + bias_y) >> PRECISION;
198 *y_dst = y as u8;
199 }
200 });
201
202 Ok(())
203}
204
205/// Convert RGB image data to YUV 400 planar format.
206///
207/// This function performs RGB to YUV conversion and stores the result in YUV400 planar format,
208/// with Y (luminance) plane
209///
210/// # Arguments
211///
212/// * `gray_image` - Target gray image.
213/// * `rgb` - The input RGB image data slice.
214/// * `rgb_stride` - The stride (elements per row) for the RGB image data.
215/// * `range` - The YUV range (limited or full).
216/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
217///
218/// # Panics
219///
220/// This function panics if the lengths of the planes or the input RGB data are not valid based
221/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
222///
223pub fn rgb_to_yuv400(
224 gray_image: &mut YuvGrayImageMut<u8>,
225 rgb: &[u8],
226 rgb_stride: u32,
227 range: YuvRange,
228 matrix: YuvStandardMatrix,
229) -> Result<(), YuvError> {
230 rgbx_to_y::<{ YuvSourceChannels::Rgb as u8 }>(gray_image, rgb, rgb_stride, range, matrix)
231}
232
233/// Convert RGBA image data to YUV 400 planar format.
234///
235/// This function performs RGBA to YUV conversion and stores the result in YUV400 planar format,
236/// with Y (luminance) plane
237///
238/// # Arguments
239///
240/// * `gray_image` - Target gray image.
241/// * `rgba` - The input RGBA image data slice.
242/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
243/// * `range` - The YUV range (limited or full).
244/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
245///
246/// # Panics
247///
248/// This function panics if the lengths of the planes or the input RGBA data are not valid based
249/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
250///
251pub fn rgba_to_yuv400(
252 gray_image: &mut YuvGrayImageMut<u8>,
253 rgba: &[u8],
254 rgba_stride: u32,
255 range: YuvRange,
256 matrix: YuvStandardMatrix,
257) -> Result<(), YuvError> {
258 rgbx_to_y::<{ YuvSourceChannels::Rgba as u8 }>(gray_image, rgba, rgba_stride, range, matrix)
259}
260
261/// Convert BGRA image data to YUV 400 planar format.
262///
263/// This function performs BGRA to YUV conversion and stores the result in YUV420 planar format,
264/// with Y (luminance) plane
265///
266/// # Arguments
267///
268/// * `gray_image` - Target gray image.
269/// * `bgra` - The input BGRA image data slice.
270/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
271/// * `range` - The YUV range (limited or full).
272/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
273///
274/// # Panics
275///
276/// This function panics if the lengths of the planes or the input BGRA data are not valid based
277/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
278///
279pub fn bgra_to_yuv400(
280 gray_image: &mut YuvGrayImageMut<u8>,
281 bgra: &[u8],
282 bgra_stride: u32,
283 range: YuvRange,
284 matrix: YuvStandardMatrix,
285) -> Result<(), YuvError> {
286 rgbx_to_y::<{ YuvSourceChannels::Bgra as u8 }>(gray_image, bgra, bgra_stride, range, matrix)
287}
288
289/// Convert BGR image data to YUV 400 planar format.
290///
291/// This function performs BGR to YUV conversion and stores the result in YUV400 planar format,
292/// with Y (luminance) plane
293///
294/// # Arguments
295///
296/// * `gray_image` - Target gray image.
297/// * `bgr` - The input BGR image data slice.
298/// * `bgr_stride` - The stride (components per row) for the RGB image data.
299/// * `range` - The YUV range (limited or full).
300/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
301///
302/// # Panics
303///
304/// This function panics if the lengths of the planes or the input RGB data are not valid based
305/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
306///
307pub fn bgr_to_yuv400(
308 gray_image: &mut YuvGrayImageMut<u8>,
309 rgb: &[u8],
310 rgb_stride: u32,
311 range: YuvRange,
312 matrix: YuvStandardMatrix,
313) -> Result<(), YuvError> {
314 rgbx_to_y::<{ YuvSourceChannels::Bgr as u8 }>(gray_image, rgb, rgb_stride, range, matrix)
315}