1use alloc::format;
10use core::convert::TryInto;
11
12use zune_core::colorspace::ColorSpace;
13
14use crate::color_convert::ycbcr_to_grayscale;
15use crate::components::{Components, SampleRatios};
16use crate::decoder::{ColorConvert16Ptr, MAX_COMPONENTS};
17use crate::errors::DecodeErrors;
18
19#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
23#[inline]
24fn blinn_8x8(in_val: u8, y: u8) -> u8 {
25    let t = i32::from(in_val) * i32::from(y) + 128;
26    return ((t + (t >> 8)) >> 8) as u8;
27}
28
29#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
30pub(crate) fn color_convert(
31    unprocessed: &[&[i16]; MAX_COMPONENTS], color_convert_16: ColorConvert16Ptr,
32    input_colorspace: ColorSpace, output_colorspace: ColorSpace, output: &mut [u8], width: usize,
33    padded_width: usize
34) -> Result<(), DecodeErrors> {
36    if input_colorspace.num_components() == 3 && input_colorspace == output_colorspace {
39        copy_removing_padding(unprocessed, width, padded_width, output);
41        return Ok(());
42    }
43    if input_colorspace.num_components() == 4 && input_colorspace == output_colorspace {
44        copy_removing_padding_4x(unprocessed, width, padded_width, output);
45        return Ok(());
46    }
47    match (input_colorspace, output_colorspace) {
49        (ColorSpace::YCbCr | ColorSpace::Luma, ColorSpace::Luma) => {
50            ycbcr_to_grayscale(unprocessed[0], width, padded_width, output);
51        }
52        (
53            ColorSpace::YCbCr,
54            ColorSpace::RGB | ColorSpace::RGBA | ColorSpace::BGR | ColorSpace::BGRA
55        ) => {
56            color_convert_ycbcr(
57                unprocessed,
58                width,
59                padded_width,
60                output_colorspace,
61                color_convert_16,
62                output
63            );
64        }
65        (ColorSpace::YCCK, ColorSpace::RGB) => {
66            color_convert_ycck_to_rgb::<3>(
67                unprocessed,
68                width,
69                padded_width,
70                output_colorspace,
71                color_convert_16,
72                output
73            );
74        }
75
76        (ColorSpace::YCCK, ColorSpace::RGBA) => {
77            color_convert_ycck_to_rgb::<4>(
78                unprocessed,
79                width,
80                padded_width,
81                output_colorspace,
82                color_convert_16,
83                output
84            );
85        }
86        (ColorSpace::CMYK, ColorSpace::RGB) => {
87            color_convert_cymk_to_rgb::<3>(unprocessed, width, padded_width, output);
88        }
89        (ColorSpace::CMYK, ColorSpace::RGBA) => {
90            color_convert_cymk_to_rgb::<4>(unprocessed, width, padded_width, output);
91        }
92        _ => {
94            let msg = format!(
95                "Unimplemented colorspace mapping from {input_colorspace:?} to {output_colorspace:?}");
96
97            return Err(DecodeErrors::Format(msg));
98        }
99    }
100    Ok(())
101}
102
103#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
106fn copy_removing_padding(
107    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
108) {
109    for (((pix_w, c_w), m_w), y_w) in output
110        .chunks_exact_mut(width * 3)
111        .zip(mcu_block[0].chunks_exact(padded_width))
112        .zip(mcu_block[1].chunks_exact(padded_width))
113        .zip(mcu_block[2].chunks_exact(padded_width))
114    {
115        for (((pix, c), y), m) in pix_w.chunks_exact_mut(3).zip(c_w).zip(m_w).zip(y_w) {
116            pix[0] = *c as u8;
117            pix[1] = *y as u8;
118            pix[2] = *m as u8;
119        }
120    }
121}
122fn copy_removing_padding_4x(
123    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
124) {
125    for ((((pix_w, c_w), m_w), y_w), k_w) in output
126        .chunks_exact_mut(width * 4)
127        .zip(mcu_block[0].chunks_exact(padded_width))
128        .zip(mcu_block[1].chunks_exact(padded_width))
129        .zip(mcu_block[2].chunks_exact(padded_width))
130        .zip(mcu_block[3].chunks_exact(padded_width))
131    {
132        for ((((pix, c), y), m), k) in pix_w
133            .chunks_exact_mut(4)
134            .zip(c_w)
135            .zip(m_w)
136            .zip(y_w)
137            .zip(k_w)
138        {
139            pix[0] = *c as u8;
140            pix[1] = *y as u8;
141            pix[2] = *m as u8;
142            pix[3] = *k as u8;
143        }
144    }
145}
146#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
148fn color_convert_ycck_to_rgb<const NUM_COMPONENTS: usize>(
149    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize,
150    output_colorspace: ColorSpace, color_convert_16: ColorConvert16Ptr, output: &mut [u8]
151) {
152    color_convert_ycbcr(
153        mcu_block,
154        width,
155        padded_width,
156        output_colorspace,
157        color_convert_16,
158        output
159    );
160    for (pix_w, m_w) in output
161        .chunks_exact_mut(width * 3)
162        .zip(mcu_block[3].chunks_exact(padded_width))
163    {
164        for (pix, m) in pix_w.chunks_exact_mut(NUM_COMPONENTS).zip(m_w) {
165            let m = (*m) as u8;
166            pix[0] = blinn_8x8(255 - pix[0], m);
167            pix[1] = blinn_8x8(255 - pix[1], m);
168            pix[2] = blinn_8x8(255 - pix[2], m);
169        }
170    }
171}
172
173#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
174fn color_convert_cymk_to_rgb<const NUM_COMPONENTS: usize>(
175    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
176) {
177    for ((((pix_w, c_w), m_w), y_w), k_w) in output
178        .chunks_exact_mut(width * NUM_COMPONENTS)
179        .zip(mcu_block[0].chunks_exact(padded_width))
180        .zip(mcu_block[1].chunks_exact(padded_width))
181        .zip(mcu_block[2].chunks_exact(padded_width))
182        .zip(mcu_block[3].chunks_exact(padded_width))
183    {
184        for ((((pix, c), m), y), k) in pix_w
185            .chunks_exact_mut(3)
186            .zip(c_w)
187            .zip(m_w)
188            .zip(y_w)
189            .zip(k_w)
190        {
191            let c = *c as u8;
192            let m = *m as u8;
193            let y = *y as u8;
194            let k = *k as u8;
195
196            pix[0] = blinn_8x8(c, k);
197            pix[1] = blinn_8x8(m, k);
198            pix[2] = blinn_8x8(y, k);
199        }
200    }
201}
202
203#[allow(
205    clippy::similar_names,
206    clippy::too_many_arguments,
207    clippy::needless_pass_by_value,
208    clippy::unwrap_used
209)]
210fn color_convert_ycbcr(
211    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize,
212    output_colorspace: ColorSpace, color_convert_16: ColorConvert16Ptr, output: &mut [u8]
213) {
214    let num_components = output_colorspace.num_components();
215
216    let stride = width * num_components;
217    let mut temp = [0; 64];
219    for (((y_width, cb_width), cr_width), out) in mcu_block[0]
222        .chunks_exact(padded_width)
223        .zip(mcu_block[1].chunks_exact(padded_width))
224        .zip(mcu_block[2].chunks_exact(padded_width))
225        .zip(output.chunks_exact_mut(stride))
226    {
227        if width < 16 {
228            let mut y_out = [0; 16];
230            let mut cb_out = [0; 16];
231            let mut cr_out = [0; 16];
232            y_out[0..y_width.len()].copy_from_slice(y_width);
234            cb_out[0..cb_width.len()].copy_from_slice(cb_width);
235            cr_out[0..cr_width.len()].copy_from_slice(cr_width);
236            (color_convert_16)(&y_out, &cb_out, &cr_out, &mut temp, &mut 0);
240            out[0..width * num_components].copy_from_slice(&temp[0..width * num_components]);
242            continue;
244        }
245
246        for (((y, cb), cr), out_c) in y_width
248            .chunks_exact(16)
249            .zip(cb_width.chunks_exact(16))
250            .zip(cr_width.chunks_exact(16))
251            .zip(out.chunks_exact_mut(16 * num_components))
252        {
253            (color_convert_16)(
254                y.try_into().unwrap(),
255                cb.try_into().unwrap(),
256                cr.try_into().unwrap(),
257                out_c,
258                &mut 0
259            );
260        }
261        for ((y, cb), cr) in y_width[width - 16..]
266            .chunks_exact(16)
267            .zip(cb_width[width - 16..].chunks_exact(16))
268            .zip(cr_width[width - 16..].chunks_exact(16))
269            .take(1)
270        {
271            (color_convert_16)(
272                y.try_into().unwrap(),
273                cb.try_into().unwrap(),
274                cr.try_into().unwrap(),
275                &mut temp,
276                &mut 0
277            );
278        }
279
280        let rem = out[(width - 16) * num_components..]
281            .chunks_exact_mut(16 * num_components)
282            .next()
283            .unwrap();
284
285        rem.copy_from_slice(&temp[0..rem.len()]);
286    }
287}
288pub(crate) fn upsample(
289    component: &mut Components, mcu_height: usize, i: usize, upsampler_scratch_space: &mut [i16],
290    has_vertical_sample: bool
291) {
292    match component.sample_ratio {
293        SampleRatios::V | SampleRatios::HV => {
294            let mut dest_start = 0;
318            let stride_bytes_written = component.width_stride * component.sample_ratio.sample();
319
320            if i > 0 {
321                let stride = component.width_stride;
326
327                let dest = &mut component.first_row_upsample_dest[0..stride_bytes_written];
328
329                let row = &component.row[..];
331                let row_up = &component.row_up[..];
332                let row_down = &component.raw_coeff[0..stride];
333                (component.up_sampler)(row, row_up, row_down, upsampler_scratch_space, dest);
334            }
335
336            let mut upsample = true;
344
345            let stride = component.width_stride * component.vertical_sample;
346            let stop_offset = component.raw_coeff.len() / component.width_stride;
347            for (pos, curr_row) in component
348                .raw_coeff
349                .chunks_exact(component.width_stride)
350                .enumerate()
351            {
352                let mut dest: &mut [i16] = &mut [];
353                let mut row_up: &[i16] = &[];
354                let mut row_down: &[i16] = &[];
356
357                if i == 0 && pos == 0 {
360                    row_up = &component.raw_coeff[pos * stride..(pos + 1) * stride];
363                    row_down = &component.raw_coeff[(pos + 1) * stride..(pos + 2) * stride];
364                } else if i > 0 && pos == 0 {
365                    row_up = &component.row[..];
367                    row_down = &component.raw_coeff[(pos + 1) * stride..(pos + 2) * stride];
368                } else if i == mcu_height.saturating_sub(1) && pos == stop_offset - 1 {
369                    row_up = &component.raw_coeff[(pos - 1) * stride..pos * stride];
371                    row_down = &component.raw_coeff[pos * stride..(pos + 1) * stride];
372                } else if pos > 0 && pos < stop_offset - 1 {
373                    row_up = &component.raw_coeff[(pos - 1) * stride..pos * stride];
376                    row_down = &component.raw_coeff[(pos + 1) * stride..(pos + 2) * stride];
377                } else if pos == stop_offset - 1 {
378                    let prev_row = &component.raw_coeff[(pos - 1) * stride..pos * stride];
386
387                    component.row_up.copy_from_slice(prev_row);
388                    component.row.copy_from_slice(curr_row);
389                    upsample = false;
390                } else {
391                    unreachable!("Uh oh!");
392                }
393                if upsample {
394                    dest =
395                        &mut component.upsample_dest[dest_start..dest_start + stride_bytes_written];
396                    dest_start += stride_bytes_written;
397                }
398
399                if upsample {
400                    (component.up_sampler)(
402                        curr_row,
403                        row_up,
404                        row_down,
405                        upsampler_scratch_space,
406                        dest
407                    );
408                }
409            }
410        }
411        SampleRatios::H => {
412            assert_eq!(component.raw_coeff.len() * 2, component.upsample_dest.len());
413
414            let raw_coeff = &component.raw_coeff;
415            let dest_coeff = &mut component.upsample_dest;
416
417            if has_vertical_sample {
418                let length = component.first_row_upsample_dest.len();
439                component
440                    .first_row_upsample_dest
441                    .copy_from_slice(&dest_coeff.rchunks_exact(length).next().unwrap());
442            }
443            for (single_row, output_stride) in raw_coeff
445                .chunks_exact(component.width_stride)
446                .zip(dest_coeff.chunks_exact_mut(component.width_stride * 2))
447            {
448                (component.up_sampler)(single_row, &[], &[], &mut [], output_stride);
451            }
452        }
453        SampleRatios::Generic(h, v) => {
454            let raw_coeff = &component.raw_coeff;
455            let dest_coeff = &mut component.upsample_dest;
456
457
458            for (single_row, output_stride) in raw_coeff
459                .chunks_exact(component.width_stride)
460                .zip(dest_coeff.chunks_exact_mut(component.width_stride * h*v))
461            {
462                (component.up_sampler)(single_row, &[], &[], &mut [], output_stride);
465            }
466        }
467        SampleRatios::None => {}
468    };
469}