yuvutils_rs/sharpyuv/
sharp_rgba_to_yuv.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#![forbid(unsafe_code)]
30
31use crate::built_coefficients::get_built_forward_transform;
32use crate::sharpyuv::SharpYuvGammaTransfer;
33use crate::yuv_error::check_rgba_destination;
34use crate::yuv_support::*;
35use crate::{YuvError, YuvPlanarImageMut};
36#[cfg(feature = "rayon")]
37use rayon::iter::{IndexedParallelIterator, ParallelIterator};
38#[cfg(feature = "rayon")]
39use rayon::prelude::{ParallelSlice, ParallelSliceMut};
40
41fn sharpen_row420<const ORIGIN_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32>(
42    y: usize,
43    rgba: &[u8],
44    y_plane: &mut [u8],
45    u_plane: &mut [u8],
46    v_plane: &mut [u8],
47    rgb_layout: &[u16],
48    rgb_layout_next_lane: &[u16],
49    gamma_map_table: &[u8; u16::MAX as usize + 1],
50    range: &YuvChromaRange,
51    transform: &CbCrForwardTransform<i32>,
52    width: usize,
53) {
54    let src_chans: YuvSourceChannels = ORIGIN_CHANNELS.into();
55    let channels = src_chans.get_channels_count();
56
57    let rounding_const_bias: i32 = (1 << (PRECISION - 1)) - 1;
58    let bias_y = range.bias_y as i32 * (1 << PRECISION) + rounding_const_bias;
59    let bias_uv = range.bias_uv as i32 * (1 << PRECISION) + rounding_const_bias;
60
61    let i_bias_y = range.bias_y as i32;
62    let i_cap_y = range.range_y as i32 + i_bias_y;
63    let i_cap_uv = i_bias_y + range.range_uv as i32;
64
65    let y_even_row = y & 1 == 0;
66
67    for (((((y_dst, u_dst), v_dst), rgba), rgb_linearized), rgb_linearized_next) in y_plane
68        .chunks_exact_mut(2)
69        .zip(u_plane.iter_mut())
70        .zip(v_plane.iter_mut())
71        .zip(rgba.chunks_exact(channels * 2))
72        .zip(rgb_layout.chunks_exact(2 * 3))
73        .zip(rgb_layout_next_lane.chunks_exact(2 * 3))
74    {
75        let r0 = rgba[src_chans.get_r_channel_offset()] as i32;
76        let g0 = rgba[src_chans.get_g_channel_offset()] as i32;
77        let b0 = rgba[src_chans.get_b_channel_offset()] as i32;
78
79        let y_0 = (r0 * transform.yr + g0 * transform.yg + b0 * transform.yb + bias_y) >> PRECISION;
80        y_dst[0] = y_0.min(i_cap_y) as u8;
81
82        let rgba_2 = &rgba[channels..channels * 2];
83
84        let r1 = rgba_2[src_chans.get_r_channel_offset()] as i32;
85        let g1 = rgba_2[src_chans.get_g_channel_offset()] as i32;
86        let b1 = rgba_2[src_chans.get_b_channel_offset()] as i32;
87
88        let y_1 = (r1 * transform.yr + g1 * transform.yg + b1 * transform.yb + bias_y) >> PRECISION;
89        y_dst[1] = y_1.min(i_cap_y) as u8;
90
91        if y_even_row {
92            let sharp_r_c = rgb_linearized[src_chans.get_r_channel_offset()];
93            let sharp_g_c = rgb_linearized[src_chans.get_g_channel_offset()];
94            let sharp_b_c = rgb_linearized[src_chans.get_b_channel_offset()];
95
96            let rgb_linearized_2 = &rgb_linearized[3..(3 + 3)];
97
98            let sharp_r_next = rgb_linearized_2[src_chans.get_r_channel_offset()];
99            let sharp_g_next = rgb_linearized_2[src_chans.get_g_channel_offset()];
100            let sharp_b_next = rgb_linearized_2[src_chans.get_b_channel_offset()];
101
102            let sharp_r_c_next_row = rgb_linearized_next[src_chans.get_r_channel_offset()];
103            let sharp_g_c_next_row = rgb_linearized_next[src_chans.get_g_channel_offset()];
104            let sharp_b_c_next_row = rgb_linearized_next[src_chans.get_b_channel_offset()];
105
106            let rgb_linearized_next_2 = &rgb_linearized_next[3..(3 + 3)];
107
108            let sharp_r_next_row = rgb_linearized_next_2[src_chans.get_r_channel_offset()];
109            let sharp_g_next_row = rgb_linearized_next_2[src_chans.get_g_channel_offset()];
110            let sharp_b_next_row = rgb_linearized_next_2[src_chans.get_b_channel_offset()];
111
112            const ROUNDING_SHARP: i32 = 1 << 3;
113
114            let interpolated_r = ((sharp_r_c as i32 * 9
115                + sharp_r_next as i32 * 3
116                + sharp_r_c_next_row as i32 * 3
117                + sharp_r_next_row as i32
118                + ROUNDING_SHARP)
119                >> 4) as u16;
120            let interpolated_g = ((sharp_g_c as i32 * 9
121                + sharp_g_next as i32 * 3
122                + sharp_g_c_next_row as i32 * 3
123                + sharp_g_next_row as i32
124                + ROUNDING_SHARP)
125                >> 4) as u16;
126            let interpolated_b = ((sharp_b_c as i32 * 9
127                + sharp_b_next as i32 * 3
128                + sharp_b_c_next_row as i32 * 3
129                + sharp_b_next_row as i32
130                + ROUNDING_SHARP)
131                >> 4) as u16;
132
133            let corrected_r = gamma_map_table[interpolated_r as usize] as i32;
134            let corrected_g = gamma_map_table[interpolated_g as usize] as i32;
135            let corrected_b = gamma_map_table[interpolated_b as usize] as i32;
136
137            let cb = (corrected_r * transform.cb_r
138                + corrected_g * transform.cb_g
139                + corrected_b * transform.cb_b
140                + bias_uv)
141                >> PRECISION;
142            let cr = (corrected_r * transform.cr_r
143                + corrected_g * transform.cr_g
144                + corrected_b * transform.cr_b
145                + bias_uv)
146                >> PRECISION;
147            *u_dst = cb.max(i_bias_y).min(i_cap_uv) as u8;
148            *v_dst = cr.max(i_bias_y).min(i_cap_uv) as u8;
149        }
150    }
151
152    let rem_rgba = rgba.chunks_exact(channels * 2).remainder();
153
154    if width & 1 != 0 && !rem_rgba.is_empty() {
155        let rgba = &rem_rgba[0..3];
156        let y_last = y_plane.last_mut().unwrap();
157        let r0 = rgba[src_chans.get_r_channel_offset()] as i32;
158        let g0 = rgba[src_chans.get_g_channel_offset()] as i32;
159        let b0 = rgba[src_chans.get_b_channel_offset()] as i32;
160
161        let y_1 = (r0 * transform.yr + g0 * transform.yg + b0 * transform.yb + bias_y) >> PRECISION;
162        *y_last = y_1.min(i_cap_y) as u8;
163
164        if y_even_row {
165            let rgba_lin = rgb_layout.chunks_exact(3).last().unwrap();
166            let rgba_lin = &rgba_lin[0..3];
167            let sharp_r_c = rgba_lin[src_chans.get_r_channel_offset()];
168            let sharp_g_c = rgba_lin[src_chans.get_g_channel_offset()];
169            let sharp_b_c = rgba_lin[src_chans.get_b_channel_offset()];
170
171            let rgba_lin_next = rgb_layout_next_lane.chunks_exact(3).last().unwrap();
172            let rgba_lin_next = &rgba_lin_next[0..3];
173            let sharp_r_c_next = rgba_lin_next[src_chans.get_r_channel_offset()];
174            let sharp_g_c_next = rgba_lin_next[src_chans.get_g_channel_offset()];
175            let sharp_b_c_next = rgba_lin_next[src_chans.get_b_channel_offset()];
176
177            const ROUNDING_SHARP: i32 = 1 << 3;
178
179            let interpolated_r =
180                ((sharp_r_c as i32 * 12 + sharp_r_c_next as i32 * 4 + ROUNDING_SHARP) >> 4) as u16;
181            let interpolated_g =
182                ((sharp_g_c as i32 * 12 + sharp_g_c_next as i32 * 4 + ROUNDING_SHARP) >> 4) as u16;
183            let interpolated_b =
184                ((sharp_b_c as i32 * 12 + sharp_b_c_next as i32 * 4 + ROUNDING_SHARP) >> 4) as u16;
185
186            let corrected_r = gamma_map_table[interpolated_r as usize] as i32;
187            let corrected_g = gamma_map_table[interpolated_g as usize] as i32;
188            let corrected_b = gamma_map_table[interpolated_b as usize] as i32;
189
190            let cb = (corrected_r * transform.cb_r
191                + corrected_g * transform.cb_g
192                + corrected_b * transform.cb_b
193                + bias_uv)
194                >> PRECISION;
195            let cr = (corrected_r * transform.cr_r
196                + corrected_g * transform.cr_g
197                + corrected_b * transform.cr_b
198                + bias_uv)
199                >> PRECISION;
200            let u_last = u_plane.last_mut().unwrap();
201            let v_last = v_plane.last_mut().unwrap();
202            *u_last = cb.max(i_bias_y).min(i_cap_uv) as u8;
203            *v_last = cr.max(i_bias_y).min(i_cap_uv) as u8;
204        }
205    }
206}
207
208fn sharpen_row422<const ORIGIN_CHANNELS: u8, const SAMPLING: u8, const PRECISION: i32>(
209    rgba: &[u8],
210    y_plane: &mut [u8],
211    u_plane: &mut [u8],
212    v_plane: &mut [u8],
213    rgb_layout: &[u16],
214    gamma_map_table: &[u8; u16::MAX as usize + 1],
215    range: &YuvChromaRange,
216    transform: &CbCrForwardTransform<i32>,
217    width: usize,
218) {
219    let src_chans: YuvSourceChannels = ORIGIN_CHANNELS.into();
220    let channels = src_chans.get_channels_count();
221
222    let rounding_const_bias: i32 = (1 << (PRECISION - 1)) - 1;
223    let bias_y = range.bias_y as i32 * (1 << PRECISION) + rounding_const_bias;
224    let bias_uv = range.bias_uv as i32 * (1 << PRECISION) + rounding_const_bias;
225
226    let i_bias_y = range.bias_y as i32;
227    let i_cap_y = range.range_y as i32 + i_bias_y;
228    let i_cap_uv = i_bias_y + range.range_uv as i32;
229
230    for ((((y_dst, u_dst), v_dst), rgba), rgb_linearized) in y_plane
231        .chunks_exact_mut(2)
232        .zip(u_plane.iter_mut())
233        .zip(v_plane.iter_mut())
234        .zip(rgba.chunks_exact(channels * 2))
235        .zip(rgb_layout.chunks_exact(2 * 3))
236    {
237        let r0 = rgba[src_chans.get_r_channel_offset()] as i32;
238        let g0 = rgba[src_chans.get_g_channel_offset()] as i32;
239        let b0 = rgba[src_chans.get_b_channel_offset()] as i32;
240
241        let sharp_r_c = rgb_linearized[src_chans.get_r_channel_offset()];
242        let sharp_g_c = rgb_linearized[src_chans.get_g_channel_offset()];
243        let sharp_b_c = rgb_linearized[src_chans.get_b_channel_offset()];
244
245        let y_0 = (r0 * transform.yr + g0 * transform.yg + b0 * transform.yb + bias_y) >> PRECISION;
246        y_dst[0] = y_0.min(i_cap_y) as u8;
247
248        let rgba_2 = &rgba[channels..channels * 2];
249
250        let r1 = rgba_2[src_chans.get_r_channel_offset()] as i32;
251        let g1 = rgba_2[src_chans.get_g_channel_offset()] as i32;
252        let b1 = rgba_2[src_chans.get_b_channel_offset()] as i32;
253
254        let y_1 = (r1 * transform.yr + g1 * transform.yg + b1 * transform.yb + bias_y) >> PRECISION;
255        y_dst[1] = y_1.min(i_cap_y) as u8;
256
257        let rgb_linearized_2 = &rgb_linearized[3..(3 + 3)];
258
259        let sharp_r_next = rgb_linearized_2[src_chans.get_r_channel_offset()];
260        let sharp_g_next = rgb_linearized_2[src_chans.get_g_channel_offset()];
261        let sharp_b_next = rgb_linearized_2[src_chans.get_b_channel_offset()];
262
263        const ROUNDING_SHARP: i32 = 1 << 3;
264
265        let interpolated_r =
266            ((sharp_r_c as i32 * 12 + sharp_r_next as i32 * 4 + ROUNDING_SHARP) >> 4) as u16;
267        let interpolated_g =
268            ((sharp_g_c as i32 * 12 + sharp_g_next as i32 * 4 + ROUNDING_SHARP) >> 4) as u16;
269        let interpolated_b =
270            ((sharp_b_c as i32 * 12 + sharp_b_next as i32 * 4 + ROUNDING_SHARP) >> 4) as u16;
271
272        let corrected_r = gamma_map_table[interpolated_r as usize] as i32;
273        let corrected_g = gamma_map_table[interpolated_g as usize] as i32;
274        let corrected_b = gamma_map_table[interpolated_b as usize] as i32;
275
276        let cb = (corrected_r * transform.cb_r
277            + corrected_g * transform.cb_g
278            + corrected_b * transform.cb_b
279            + bias_uv)
280            >> PRECISION;
281        let cr = (corrected_r * transform.cr_r
282            + corrected_g * transform.cr_g
283            + corrected_b * transform.cr_b
284            + bias_uv)
285            >> PRECISION;
286        *u_dst = cb.max(i_bias_y).min(i_cap_uv) as u8;
287        *v_dst = cr.max(i_bias_y).min(i_cap_uv) as u8;
288    }
289
290    let rem_rgba = rgba.chunks_exact(channels * 2).remainder();
291
292    if width & 1 != 0 && !rem_rgba.is_empty() {
293        let rgba = &rem_rgba[0..3];
294        let y_last = y_plane.last_mut().unwrap();
295        let r0 = rgba[src_chans.get_r_channel_offset()] as i32;
296        let g0 = rgba[src_chans.get_g_channel_offset()] as i32;
297        let b0 = rgba[src_chans.get_b_channel_offset()] as i32;
298
299        let y_1 = (r0 * transform.yr + g0 * transform.yg + b0 * transform.yb + bias_y) >> PRECISION;
300        *y_last = y_1.min(i_cap_y) as u8;
301
302        let cb = (r0 * transform.cb_r + g0 * transform.cb_g + b0 * transform.cb_b + bias_uv)
303            >> PRECISION;
304        let cr = (r0 * transform.cr_r + g0 * transform.cr_g + b0 * transform.cr_b + bias_uv)
305            >> PRECISION;
306
307        let u_last = u_plane.last_mut().unwrap();
308        let v_last = v_plane.last_mut().unwrap();
309        *u_last = cb.max(i_bias_y).min(i_cap_uv) as u8;
310        *v_last = cr.max(i_bias_y).min(i_cap_uv) as u8;
311    }
312}
313
314fn rgbx_to_sharp_yuv<const ORIGIN_CHANNELS: u8, const SAMPLING: u8>(
315    planar_image: &mut YuvPlanarImageMut<u8>,
316    rgba: &[u8],
317    rgba_stride: u32,
318    range: YuvRange,
319    matrix: YuvStandardMatrix,
320    sharp_yuv_gamma_transfer: SharpYuvGammaTransfer,
321) -> Result<(), YuvError> {
322    let chroma_subsampling: YuvChromaSubsampling = SAMPLING.into();
323    let src_chans: YuvSourceChannels = ORIGIN_CHANNELS.into();
324
325    check_rgba_destination(
326        rgba,
327        rgba_stride,
328        planar_image.width,
329        planar_image.height,
330        src_chans.get_channels_count(),
331    )?;
332    planar_image.check_constraints(chroma_subsampling)?;
333
334    let mut linear_map_table = [0u16; 256];
335    let mut gamma_map_table = [0u8; u16::MAX as usize + 1];
336
337    let linear_scale = (1. / 255.) as f32;
338    let gamma_scale = 1. / u16::MAX as f32;
339
340    for (i, item) in linear_map_table.iter_mut().enumerate() {
341        let linear = sharp_yuv_gamma_transfer.linearize(i as f32 * linear_scale);
342        *item = (linear * u16::MAX as f32) as u16;
343    }
344
345    for (i, item) in gamma_map_table.iter_mut().enumerate() {
346        let gamma = sharp_yuv_gamma_transfer.gamma(i as f32 * gamma_scale);
347        *item = (gamma * 255.) as u8;
348    }
349
350    // Always using 3 Channels ( RGB etc. ) layout since we do not need a alpha channel
351    let mut rgb_layout: Vec<u16> =
352        vec![0u16; planar_image.width as usize * planar_image.height as usize * 3];
353
354    let rgb_layout_stride_len = planar_image.width as usize * 3;
355
356    let iter_linearize;
357    #[cfg(not(feature = "rayon"))]
358    {
359        iter_linearize = rgb_layout
360            .chunks_exact_mut(rgb_layout_stride_len)
361            .zip(rgba.chunks_exact(rgba_stride as usize));
362    }
363    #[cfg(feature = "rayon")]
364    {
365        iter_linearize = rgb_layout
366            .par_chunks_exact_mut(rgb_layout_stride_len)
367            .zip(rgba.par_chunks_exact(rgba_stride as usize));
368    }
369
370    iter_linearize.for_each(|(rgb_layout_cast, src_layout)| {
371        for (dst, src) in rgb_layout_cast
372            .chunks_exact_mut(3)
373            .zip(src_layout.chunks_exact(src_chans.get_channels_count()))
374        {
375            dst[0] = linear_map_table[src[0] as usize];
376            dst[1] = linear_map_table[src[1] as usize];
377            dst[2] = linear_map_table[src[2] as usize];
378        }
379    });
380
381    let chroma_range = get_yuv_range(8, range);
382    let kr_kb = matrix.get_kr_kb();
383    let max_range_p8 = (1u32 << 8u32) - 1u32;
384    const PRECISION: i32 = 13;
385    let transform =
386        if let Some(stored_t) = get_built_forward_transform(PRECISION as u32, 8, range, matrix) {
387            stored_t
388        } else {
389            let transform_precise = get_forward_transform(
390                max_range_p8,
391                chroma_range.range_y,
392                chroma_range.range_uv,
393                kr_kb.kr,
394                kr_kb.kb,
395            );
396            transform_precise.to_integers(PRECISION as u32)
397        };
398
399    let y_iter;
400    let u_iter;
401    let v_iter;
402    let rgb_iter;
403
404    if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
405        #[cfg(feature = "rayon")]
406        {
407            y_iter = planar_image
408                .y_plane
409                .borrow_mut()
410                .par_chunks_exact_mut(planar_image.y_stride as usize * 2);
411            u_iter = planar_image
412                .u_plane
413                .borrow_mut()
414                .par_chunks_exact_mut(planar_image.u_stride as usize);
415            v_iter = planar_image
416                .v_plane
417                .borrow_mut()
418                .par_chunks_exact_mut(planar_image.v_stride as usize);
419            rgb_iter = rgba.par_chunks_exact(rgba_stride as usize * 2);
420        }
421        #[cfg(not(feature = "rayon"))]
422        {
423            y_iter = planar_image
424                .y_plane
425                .borrow_mut()
426                .chunks_exact_mut(planar_image.y_stride as usize * 2);
427            u_iter = planar_image
428                .u_plane
429                .borrow_mut()
430                .chunks_exact_mut(planar_image.u_stride as usize);
431            v_iter = planar_image
432                .v_plane
433                .borrow_mut()
434                .chunks_exact_mut(planar_image.v_stride as usize);
435            rgb_iter = rgba.chunks_exact(rgba_stride as usize * 2);
436        }
437    } else {
438        #[cfg(feature = "rayon")]
439        {
440            y_iter = planar_image
441                .y_plane
442                .borrow_mut()
443                .par_chunks_exact_mut(planar_image.y_stride as usize);
444            u_iter = planar_image
445                .u_plane
446                .borrow_mut()
447                .par_chunks_exact_mut(planar_image.u_stride as usize);
448            v_iter = planar_image
449                .v_plane
450                .borrow_mut()
451                .par_chunks_exact_mut(planar_image.v_stride as usize);
452            rgb_iter = rgba.par_chunks_exact(rgba_stride as usize);
453        }
454        #[cfg(not(feature = "rayon"))]
455        {
456            y_iter = planar_image
457                .y_plane
458                .borrow_mut()
459                .chunks_exact_mut(planar_image.y_stride as usize);
460            u_iter = planar_image
461                .u_plane
462                .borrow_mut()
463                .chunks_exact_mut(planar_image.u_stride as usize);
464            v_iter = planar_image
465                .v_plane
466                .borrow_mut()
467                .chunks_exact_mut(planar_image.v_stride as usize);
468            rgb_iter = rgba.chunks_exact(rgba_stride as usize);
469        }
470    }
471
472    let full_iter = rgb_iter.zip(y_iter).zip(u_iter).zip(v_iter);
473
474    full_iter
475        .enumerate()
476        .for_each(|(j, (((rgba, y_plane), u_plane), v_plane))| {
477            if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
478                let v_y = j * 2;
479
480                for (virtual_y, (y_plane, rgba)) in y_plane
481                    .chunks_exact_mut(planar_image.y_stride as usize)
482                    .zip(rgba.chunks_exact(rgba_stride as usize))
483                    .enumerate()
484                {
485                    let y = virtual_y + v_y;
486                    let rgb_layout_start = y * rgb_layout_stride_len;
487                    let rgb_layout_start_next = (y + 1) * rgb_layout_stride_len;
488                    let rgb_layout_lane = &rgb_layout
489                        [rgb_layout_start..((planar_image.width as usize) * 3 + rgb_layout_start)];
490                    let rgb_layout_next_lane = if y + 1 < planar_image.height as usize {
491                        &rgb_layout[rgb_layout_start_next
492                            ..((planar_image.width as usize) * 3 + rgb_layout_start_next)]
493                    } else {
494                        rgb_layout_lane
495                    };
496                    sharpen_row420::<ORIGIN_CHANNELS, SAMPLING, PRECISION>(
497                        y,
498                        &rgba[0..planar_image.width as usize * src_chans.get_channels_count()],
499                        &mut y_plane[0..planar_image.width as usize],
500                        &mut u_plane[0..(planar_image.width as usize).div_ceil(2)],
501                        &mut v_plane[0..(planar_image.width as usize).div_ceil(2)],
502                        rgb_layout_lane,
503                        rgb_layout_next_lane,
504                        &gamma_map_table,
505                        &chroma_range,
506                        &transform,
507                        planar_image.width as usize,
508                    );
509                }
510            } else {
511                let y = j;
512                let rgb_layout_start = y * rgb_layout_stride_len;
513                let rgb_layout_lane = &rgb_layout
514                    [rgb_layout_start..((planar_image.width as usize) * 3 + rgb_layout_start)];
515                sharpen_row422::<ORIGIN_CHANNELS, SAMPLING, PRECISION>(
516                    &rgba[0..planar_image.width as usize * src_chans.get_channels_count()],
517                    &mut y_plane[0..planar_image.width as usize],
518                    &mut u_plane[0..(planar_image.width as usize).div_ceil(2)],
519                    &mut v_plane[0..(planar_image.width as usize).div_ceil(2)],
520                    rgb_layout_lane,
521                    &gamma_map_table,
522                    &chroma_range,
523                    &transform,
524                    planar_image.width as usize,
525                );
526            }
527        });
528
529    // Handle last row if image is odd
530    if planar_image.height & 1 != 0 && chroma_subsampling == YuvChromaSubsampling::Yuv420 {
531        let y_iter = planar_image
532            .y_plane
533            .borrow_mut()
534            .chunks_exact_mut(planar_image.y_stride as usize)
535            .rev()
536            .take(1);
537        let u_iter = planar_image
538            .u_plane
539            .borrow_mut()
540            .chunks_exact_mut(planar_image.u_stride as usize)
541            .rev()
542            .take(1);
543        let v_iter = planar_image
544            .v_plane
545            .borrow_mut()
546            .chunks_exact_mut(planar_image.v_stride as usize)
547            .rev()
548            .take(1);
549        let rgb_iter = rgba.chunks_exact(rgba_stride as usize).rev().take(1);
550        let rgb_linearized_iter = rgb_layout
551            .chunks_exact_mut(rgb_layout_stride_len)
552            .rev()
553            .take(1);
554
555        let full_iter = rgb_iter
556            .zip(rgb_linearized_iter)
557            .zip(y_iter)
558            .zip(u_iter)
559            .zip(v_iter);
560
561        full_iter.for_each(|((((rgba, rgb_layout), y_plane), u_plane), v_plane)| {
562            let y = planar_image.height as usize - 1;
563            let rgb_layout_lane: &[u16] = rgb_layout;
564            let rgb_layout_next_lane: &[u16] = rgb_layout;
565            sharpen_row420::<ORIGIN_CHANNELS, SAMPLING, PRECISION>(
566                y,
567                &rgba[0..planar_image.width as usize * src_chans.get_channels_count()],
568                &mut y_plane[0..planar_image.width as usize],
569                &mut u_plane[0..(planar_image.width as usize).div_ceil(2)],
570                &mut v_plane[0..(planar_image.width as usize).div_ceil(2)],
571                rgb_layout_lane,
572                rgb_layout_next_lane,
573                &gamma_map_table,
574                &chroma_range,
575                &transform,
576                planar_image.width as usize,
577            );
578        });
579    }
580
581    Ok(())
582}
583
584/// Convert RGB image data to YUV 422 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
585///
586/// This function performs RGB to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV422 planar format using bi-linear interpolation and gamma correction,
587/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
588///
589/// # Arguments
590///
591/// * `planar_image` - Target planar image.
592/// * `rgb` - The input RGB image data slice.
593/// * `rgb_stride` - The stride (components per row) for the RGB image data.
594/// * `range` - The YUV range (limited or full).
595/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
596///
597/// # Panics
598///
599/// This function panics if the lengths of the planes or the input RGB data are not valid based
600/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
601///
602pub fn rgb_to_sharp_yuv422(
603    planar_image: &mut YuvPlanarImageMut<u8>,
604    rgb: &[u8],
605    rgb_stride: u32,
606    range: YuvRange,
607    matrix: YuvStandardMatrix,
608    gamma_transfer: SharpYuvGammaTransfer,
609) -> Result<(), YuvError> {
610    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Rgb as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
611        planar_image,
612        rgb,
613        rgb_stride,
614        range,
615        matrix,
616        gamma_transfer,
617    )
618}
619
620/// Convert BGR image data to YUV 422 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
621///
622/// This function performs BGR to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV422 planar format using bi-linear interpolation and gamma correction,
623/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
624///
625/// # Arguments
626///
627/// * `planar_image` - Target planar image.
628/// * `bgr` - The input BGR image data slice.
629/// * `bgr_stride` - The stride (components per row) for the BGR image data.
630/// * `range` - The YUV range (limited or full).
631/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
632///
633/// # Panics
634///
635/// This function panics if the lengths of the planes or the input BGR data are not valid based
636/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
637///
638pub fn bgr_to_sharp_yuv422(
639    planar_image: &mut YuvPlanarImageMut<u8>,
640    bgr: &[u8],
641    bgr_stride: u32,
642    range: YuvRange,
643    matrix: YuvStandardMatrix,
644    gamma_transfer: SharpYuvGammaTransfer,
645) -> Result<(), YuvError> {
646    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Bgr as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
647        planar_image,
648        bgr,
649        bgr_stride,
650        range,
651        matrix,
652        gamma_transfer,
653    )
654}
655
656/// Convert RGBA image data to YUV 422 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
657///
658/// This function performs RGBA to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV422 planar format using bi-linear interpolation and gamma correction,
659/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
660///
661/// # Arguments
662///
663/// * `planar_image` - Target planar image.
664/// * `rgba` - The input RGBA image data slice.
665/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
666/// * `range` - The YUV range (limited or full).
667/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
668///
669/// # Panics
670///
671/// This function panics if the lengths of the planes or the input RGBA data are not valid based
672/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
673///
674pub fn rgba_to_sharp_yuv422(
675    planar_image: &mut YuvPlanarImageMut<u8>,
676    rgba: &[u8],
677    rgba_stride: u32,
678    range: YuvRange,
679    matrix: YuvStandardMatrix,
680    gamma_transfer: SharpYuvGammaTransfer,
681) -> Result<(), YuvError> {
682    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Rgba as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
683        planar_image,
684        rgba,
685        rgba_stride,
686        range,
687        matrix,
688        gamma_transfer,
689    )
690}
691
692/// Convert BGRA image data to YUV 422 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
693///
694/// This function performs BGRA to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV422 planar format using bi-linear interpolation and gamma correction,
695/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
696///
697/// # Arguments
698///
699/// * `planar_image` - Target planar image.
700/// * `bgra` - The input BGRA image data slice.
701/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
702/// * `range` - The YUV range (limited or full).
703/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
704///
705/// # Panics
706///
707/// This function panics if the lengths of the planes or the input BGRA data are not valid based
708/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
709///
710pub fn bgra_to_sharp_yuv422(
711    planar_image: &mut YuvPlanarImageMut<u8>,
712    bgra: &[u8],
713    bgra_stride: u32,
714    range: YuvRange,
715    matrix: YuvStandardMatrix,
716    gamma_transfer: SharpYuvGammaTransfer,
717) -> Result<(), YuvError> {
718    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Bgra as u8 }, { YuvChromaSubsampling::Yuv422 as u8 }>(
719        planar_image,
720        bgra,
721        bgra_stride,
722        range,
723        matrix,
724        gamma_transfer,
725    )
726}
727
728/// Convert RGB image data to YUV 420 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
729///
730/// This function performs RGB to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV420 planar format using bi-linear interpolation and gamma correction,
731/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
732///
733/// # Arguments
734///
735/// * `y_plane` - A mutable slice to store the Y (luminance) plane data.
736/// * `y_stride` - The stride (components per row) for the Y plane.
737/// * `u_plane` - A mutable slice to store the U (chrominance) plane data.
738/// * `u_stride` - The stride (components per row) for the U plane.
739/// * `v_plane` - A mutable slice to store the V (chrominance) plane data.
740/// * `v_stride` - The stride (components per row) for the V plane.
741/// * `rgb` - The input RGB image data slice.
742/// * `rgb_stride` - The stride (components per row) for the RGB image data.
743/// * `width` - The width of the image in pixels.
744/// * `height` - The height of the image in pixels.
745/// * `range` - The YUV range (limited or full).
746/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
747///
748/// # Panics
749///
750/// This function panics if the lengths of the planes or the input RGB data are not valid based
751/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
752///
753pub fn rgb_to_sharp_yuv420(
754    planar_image: &mut YuvPlanarImageMut<u8>,
755    rgb: &[u8],
756    rgb_stride: u32,
757    range: YuvRange,
758    matrix: YuvStandardMatrix,
759    gamma_transfer: SharpYuvGammaTransfer,
760) -> Result<(), YuvError> {
761    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Rgb as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
762        planar_image,
763        rgb,
764        rgb_stride,
765        range,
766        matrix,
767        gamma_transfer,
768    )
769}
770
771/// Convert BGR image data to YUV 420 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
772///
773/// This function performs BGR to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV420 planar format using bi-linear interpolation and gamma correction,
774/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
775///
776/// # Arguments
777///
778/// * `planar_image` - Target planar image.
779/// * `bgr` - The input BGR image data slice.
780/// * `bgr_stride` - The stride (components per row) for the BGR image data.
781/// * `range` - The YUV range (limited or full).
782/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
783///
784/// # Panics
785///
786/// This function panics if the lengths of the planes or the input RGB data are not valid based
787/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
788///
789pub fn bgr_to_sharp_yuv420(
790    planar_image: &mut YuvPlanarImageMut<u8>,
791    bgr: &[u8],
792    bgr_stride: u32,
793    range: YuvRange,
794    matrix: YuvStandardMatrix,
795    gamma_transfer: SharpYuvGammaTransfer,
796) -> Result<(), YuvError> {
797    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Bgr as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
798        planar_image,
799        bgr,
800        bgr_stride,
801        range,
802        matrix,
803        gamma_transfer,
804    )
805}
806
807/// Convert RGBA image data to YUV 420 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
808///
809/// This function performs RGBA to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV420 planar format using bi-linear interpolation and gamma correction,
810/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
811///
812/// # Arguments
813///
814/// * `planar_image` - Target planar image.
815/// * `rgba` - The input RGBA image data slice.
816/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
817/// * `range` - The YUV range (limited or full).
818/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
819///
820/// # Panics
821///
822/// This function panics if the lengths of the planes or the input RGBA data are not valid based
823/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
824///
825pub fn rgba_to_sharp_yuv420(
826    planar_image: &mut YuvPlanarImageMut<u8>,
827    rgba: &[u8],
828    rgba_stride: u32,
829    range: YuvRange,
830    matrix: YuvStandardMatrix,
831    gamma_transfer: SharpYuvGammaTransfer,
832) -> Result<(), YuvError> {
833    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Rgba as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
834        planar_image,
835        rgba,
836        rgba_stride,
837        range,
838        matrix,
839        gamma_transfer,
840    )
841}
842
843/// Convert BGRA image data to YUV 420 planar format using bi-linear interpolation and gamma correction ( sharp YUV algorithm ).
844///
845/// This function performs BGRA to YUV conversion using bi-linear interpolation and gamma correction and stores the result in YUV420 planar format using bi-linear interpolation and gamma correction,
846/// with separate planes for Y (luminance), U (chrominance), and V (chrominance) components.
847///
848/// # Arguments
849///
850/// * `planar_image` - Target planar image.
851/// * `bgra` - The input BGRA image data slice.
852/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
853/// * `range` - The YUV range (limited or full).
854/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
855///
856/// # Panics
857///
858/// This function panics if the lengths of the planes or the input BGRA data are not valid based
859/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
860///
861pub fn bgra_to_sharp_yuv420(
862    planar_image: &mut YuvPlanarImageMut<u8>,
863    bgra: &[u8],
864    bgra_stride: u32,
865    range: YuvRange,
866    matrix: YuvStandardMatrix,
867    gamma_transfer: SharpYuvGammaTransfer,
868) -> Result<(), YuvError> {
869    rgbx_to_sharp_yuv::<{ YuvSourceChannels::Bgra as u8 }, { YuvChromaSubsampling::Yuv420 as u8 }>(
870        planar_image,
871        bgra,
872        bgra_stride,
873        range,
874        matrix,
875        gamma_transfer,
876    )
877}