Coverage Report

Created: 2026-06-10 07:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/zune-jpeg-0.5.15/src/worker.rs
Line
Count
Source
1
/*
2
 * Copyright (c) 2023.
3
 *
4
 * This software is free software;
5
 *
6
 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7
 */
8
9
use alloc::format;
10
use core::convert::TryInto;
11
use core::cmp::min;
12
13
use zune_core::colorspace::ColorSpace;
14
15
use crate::color_convert::ycbcr_to_grayscale;
16
use crate::components::{Components, SampleRatios};
17
use crate::decoder::{ColorConvert16Ptr, MAX_COMPONENTS};
18
use crate::errors::DecodeErrors;
19
20
/// fast 0..255 * 0..255 => 0..255 rounded multiplication
21
///
22
/// Borrowed from stb
23
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
24
#[inline]
25
0
fn blinn_8x8(in_val: u8, y: u8) -> u8 {
26
0
    let t = i32::from(in_val) * i32::from(y) + 128;
27
0
    return ((t + (t >> 8)) >> 8) as u8;
28
0
}
29
30
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
31
1.99M
pub(crate) fn color_convert(
32
1.99M
    unprocessed: &[&[i16]; MAX_COMPONENTS], color_convert_16: ColorConvert16Ptr,
33
1.99M
    input_colorspace: ColorSpace, output_colorspace: ColorSpace, output: &mut [u8], width: usize,
34
1.99M
    padded_width: usize
35
1.99M
) -> Result<(), DecodeErrors> {
36
1.99M
    if input_colorspace.num_components() == 3 && input_colorspace == output_colorspace {
37
        // sort things like RGB to RGB conversion
38
1.34k
        copy_removing_padding(unprocessed, width, padded_width, output);
39
1.34k
        return Ok(());
40
1.98M
    }
41
1.98M
    if input_colorspace.num_components() == 4 && input_colorspace == output_colorspace {
42
1.85M
        copy_removing_padding_4x(unprocessed, width, padded_width, output);
43
1.85M
        return Ok(());
44
138k
    }
45
    // color convert
46
138k
    match (input_colorspace, output_colorspace) {
47
138k
        (ColorSpace::YCbCr | ColorSpace::Luma, ColorSpace::Luma) => {
48
138k
            ycbcr_to_grayscale(unprocessed[0], width, padded_width, output);
49
138k
        }
50
        (
51
            ColorSpace::YCbCr,
52
            ColorSpace::RGB | ColorSpace::RGBA | ColorSpace::BGR | ColorSpace::BGRA
53
0
        ) => {
54
0
            color_convert_ycbcr(
55
0
                unprocessed,
56
0
                width,
57
0
                padded_width,
58
0
                output_colorspace,
59
0
                color_convert_16,
60
0
                output
61
0
            );
62
0
        }
63
0
        (ColorSpace::YCCK, ColorSpace::RGB) => {
64
0
            color_convert_ycck_to_rgb::<3>(
65
0
                unprocessed,
66
0
                width,
67
0
                padded_width,
68
0
                output_colorspace,
69
0
                color_convert_16,
70
0
                output
71
0
            );
72
0
        }
73
74
0
        (ColorSpace::YCCK, ColorSpace::RGBA) => {
75
0
            color_convert_ycck_to_rgb::<4>(
76
0
                unprocessed,
77
0
                width,
78
0
                padded_width,
79
0
                output_colorspace,
80
0
                color_convert_16,
81
0
                output
82
0
            );
83
0
        }
84
0
        (ColorSpace::CMYK, ColorSpace::RGB) => {
85
0
            color_convert_cymk_to_rgb::<3>(unprocessed, width, padded_width, output);
86
0
        }
87
0
        (ColorSpace::CMYK, ColorSpace::RGBA) => {
88
0
            color_convert_cymk_to_rgb::<4>(unprocessed, width, padded_width, output);
89
0
        }
90
17
        (ColorSpace::MultiBand(n), _) => {
91
17
            if n.get() != 2 {
92
17
                return Err(DecodeErrors::Format(format!(
93
17
                    "Unknown multiband sample ({n}), please share sample"
94
17
                )));
95
0
            }
96
0
            copy_removing_padding_generic(
97
0
                unprocessed,
98
0
                width,
99
0
                padded_width,
100
0
                output,
101
0
                n.get() as usize
102
            );
103
        }
104
        (ColorSpace::Luma, ColorSpace::RGB) => {
105
            // duplicate the luma channel  three times to form RGB
106
            // Note, this may assume the direct conversion
107
            // from luma to RGB is by duplicating
108
            //
109
            // There may be a bit more complex ways
110
            // of doing it but won't get onto it
111
0
            convert_luma_to_rgb(unprocessed, width, padded_width, output)
112
        }
113
        (ColorSpace::Luma, ColorSpace::RGBA) => {
114
            // duplicate the luma channel  three times to form RGB
115
            // add 255 as alpha
116
            // Note, this may assume the direct conversion
117
            // from luma to RGB is by duplicating
118
            //
119
            // There may be a bit more complex ways
120
            // of doing it but won't get onto it
121
0
            convert_luma_to_rgba(unprocessed, width, padded_width, output)
122
        }
123
124
        // For the other components we do nothing(currently)
125
        _ => {
126
26
            let msg = format!(
127
26
                "Unimplemented colorspace mapping from {input_colorspace:?} to {output_colorspace:?}");
128
129
26
            return Err(DecodeErrors::Format(msg));
130
        }
131
    }
132
138k
    Ok(())
133
1.99M
}
134
135
0
fn convert_luma_to_rgb(
136
0
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
137
0
) {
138
0
    for (pix_w, y_w) in output
139
0
        .chunks_exact_mut(width * 3)
140
0
        .zip(mcu_block[0].chunks_exact(padded_width))
141
    {
142
0
        for (pix, c) in pix_w.chunks_exact_mut(3).zip(y_w) {
143
0
            pix[0] = *c as u8;
144
0
            pix[1] = *c as u8;
145
0
            pix[2] = *c as u8;
146
0
        }
147
    }
148
0
}
149
0
fn convert_luma_to_rgba(
150
0
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
151
0
) {
152
0
    for (pix_w, y_w) in output
153
0
        .chunks_exact_mut(width * 4)
154
0
        .zip(mcu_block[0].chunks_exact(padded_width))
155
    {
156
0
        for (pix, c) in pix_w.chunks_exact_mut(4).zip(y_w) {
157
0
            pix[0] = *c as u8;
158
0
            pix[1] = *c as u8;
159
0
            pix[2] = *c as u8;
160
0
            pix[3] = 255;
161
0
        }
162
    }
163
0
}
164
/// Copy a block to output removing padding bytes from input
165
/// if necessary
166
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
167
1.34k
fn copy_removing_padding(
168
1.34k
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
169
1.34k
) {
170
1.34k
    for (((pix_w, c_w), m_w), y_w) in output
171
1.34k
        .chunks_exact_mut(width * 3)
172
1.34k
        .zip(mcu_block[0].chunks_exact(padded_width))
173
1.34k
        .zip(mcu_block[1].chunks_exact(padded_width))
174
1.34k
        .zip(mcu_block[2].chunks_exact(padded_width))
175
    {
176
10.2k
        for (((pix, c), y), m) in pix_w.chunks_exact_mut(3).zip(c_w).zip(m_w).zip(y_w) {
177
10.2k
            pix[0] = *c as u8;
178
10.2k
            pix[1] = *y as u8;
179
10.2k
            pix[2] = *m as u8;
180
10.2k
        }
181
    }
182
1.34k
}
183
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
184
1.85M
fn copy_removing_padding_4x(
185
1.85M
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
186
1.85M
) {
187
1.85M
    for ((((pix_w, c_w), m_w), y_w), k_w) in output
188
1.85M
        .chunks_exact_mut(width * 4)
189
1.85M
        .zip(mcu_block[0].chunks_exact(padded_width))
190
1.85M
        .zip(mcu_block[1].chunks_exact(padded_width))
191
1.85M
        .zip(mcu_block[2].chunks_exact(padded_width))
192
1.85M
        .zip(mcu_block[3].chunks_exact(padded_width))
193
    {
194
229M
        for ((((pix, c), y), m), k) in pix_w
195
1.83M
            .chunks_exact_mut(4)
196
1.83M
            .zip(c_w)
197
1.83M
            .zip(m_w)
198
1.83M
            .zip(y_w)
199
1.83M
            .zip(k_w)
200
229M
        {
201
229M
            pix[0] = *c as u8;
202
229M
            pix[1] = *y as u8;
203
229M
            pix[2] = *m as u8;
204
229M
            pix[3] = *k as u8;
205
229M
        }
206
    }
207
1.85M
}
208
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
209
0
fn copy_removing_padding_generic(
210
0
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8],
211
0
    channels: usize
212
0
) {
213
0
    match channels {
214
        // just do 2 for now
215
        2 => {
216
0
            for ((pix_w, y_w), k_w) in output
217
0
                .chunks_exact_mut(width * channels)
218
0
                .zip(mcu_block[0].chunks_exact(padded_width))
219
0
                .zip(mcu_block[1].chunks_exact(padded_width))
220
            {
221
0
                for ((pix, c), k) in pix_w.chunks_exact_mut(2).zip(y_w).zip(k_w) {
222
0
                    pix[0] = *c as u8;
223
0
                    pix[1] = *k as u8;
224
0
                }
225
            }
226
        }
227
0
        _ => unreachable!()
228
    }
229
0
}
230
/// Convert YCCK image to rgb
231
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
232
0
fn color_convert_ycck_to_rgb<const NUM_COMPONENTS: usize>(
233
0
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize,
234
0
    output_colorspace: ColorSpace, color_convert_16: ColorConvert16Ptr, output: &mut [u8]
235
0
) {
236
0
    color_convert_ycbcr(
237
0
        mcu_block,
238
0
        width,
239
0
        padded_width,
240
0
        output_colorspace,
241
0
        color_convert_16,
242
0
        output
243
    );
244
0
    for (pix_w, m_w) in output
245
0
        .chunks_exact_mut(width * 3)
246
0
        .zip(mcu_block[3].chunks_exact(padded_width))
247
    {
248
0
        for (pix, m) in pix_w.chunks_exact_mut(NUM_COMPONENTS).zip(m_w) {
249
0
            let m = (*m) as u8;
250
0
            pix[0] = blinn_8x8(255 - pix[0], m);
251
0
            pix[1] = blinn_8x8(255 - pix[1], m);
252
0
            pix[2] = blinn_8x8(255 - pix[2], m);
253
0
        }
254
    }
255
0
}
Unexecuted instantiation: zune_jpeg::worker::color_convert_ycck_to_rgb::<3>
Unexecuted instantiation: zune_jpeg::worker::color_convert_ycck_to_rgb::<4>
256
257
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
258
0
fn color_convert_cymk_to_rgb<const NUM_COMPONENTS: usize>(
259
0
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize, output: &mut [u8]
260
0
) {
261
0
    for ((((pix_w, c_w), m_w), y_w), k_w) in output
262
0
        .chunks_exact_mut(width * NUM_COMPONENTS)
263
0
        .zip(mcu_block[0].chunks_exact(padded_width))
264
0
        .zip(mcu_block[1].chunks_exact(padded_width))
265
0
        .zip(mcu_block[2].chunks_exact(padded_width))
266
0
        .zip(mcu_block[3].chunks_exact(padded_width))
267
    {
268
0
        for ((((pix, c), m), y), k) in pix_w
269
0
            .chunks_exact_mut(3)
270
0
            .zip(c_w)
271
0
            .zip(m_w)
272
0
            .zip(y_w)
273
0
            .zip(k_w)
274
0
        {
275
0
            let c = *c as u8;
276
0
            let m = *m as u8;
277
0
            let y = *y as u8;
278
0
            let k = *k as u8;
279
0
280
0
            pix[0] = blinn_8x8(c, k);
281
0
            pix[1] = blinn_8x8(m, k);
282
0
            pix[2] = blinn_8x8(y, k);
283
0
        }
284
    }
285
0
}
Unexecuted instantiation: zune_jpeg::worker::color_convert_cymk_to_rgb::<3>
Unexecuted instantiation: zune_jpeg::worker::color_convert_cymk_to_rgb::<4>
286
287
/// Do color-conversion for interleaved MCU
288
#[allow(
289
    clippy::similar_names,
290
    clippy::too_many_arguments,
291
    clippy::needless_pass_by_value,
292
    clippy::unwrap_used
293
)]
294
0
fn color_convert_ycbcr(
295
0
    mcu_block: &[&[i16]; MAX_COMPONENTS], width: usize, padded_width: usize,
296
0
    output_colorspace: ColorSpace, color_convert_16: ColorConvert16Ptr, output: &mut [u8]
297
0
) {
298
0
    let num_components = output_colorspace.num_components();
299
300
0
    let stride = width * num_components;
301
    // Allocate temporary buffer for small widths less than  16.
302
0
    let mut temp = [0; 64];
303
    // We need to chunk per width to ensure we can discard extra values at the end of the width.
304
    // Since the encoder may pad bits to ensure the width is a multiple of 8.
305
0
    for (((y_width, cb_width), cr_width), out) in mcu_block[0]
306
0
        .chunks_exact(padded_width)
307
0
        .zip(mcu_block[1].chunks_exact(padded_width))
308
0
        .zip(mcu_block[2].chunks_exact(padded_width))
309
0
        .zip(output.chunks_exact_mut(stride))
310
    {
311
0
        if width < 16 {
312
            // allocate temporary buffers for the values received from idct
313
0
            let mut y_out = [0; 16];
314
0
            let mut cb_out = [0; 16];
315
0
            let mut cr_out = [0; 16];
316
            // copy those small widths to that buffer
317
            // Use a min with 16 to prevent some panics, see https://github.com/etemesi254/zune-image/issues/331
318
0
            y_out[0..min(y_width.len(), 16)].copy_from_slice(&y_width[0..min(y_width.len(), 16)]);
319
0
            cb_out[0..min(cb_width.len(), 16)]
320
0
                .copy_from_slice(&cb_width[0..min(cb_width.len(), 16)]);
321
0
            cr_out[0..min(cr_width.len(), 16)]
322
0
                .copy_from_slice(&cr_width[0..min(cr_width.len(), 16)]);
323
            // we handle widths less than 16 a bit differently, allocating a temporary
324
            // buffer and writing to that and then flushing to the out buffer
325
            // because of the optimizations applied below,
326
0
            (color_convert_16)(&y_out, &cb_out, &cr_out, &mut temp, &mut 0);
327
            // copy to stride
328
0
            out[0..width * num_components].copy_from_slice(&temp[0..width * num_components]);
329
            // next
330
0
            continue;
331
0
        }
332
333
        // Chunk in outputs of 16 to pass to color_convert as an array of 16 i16's.
334
0
        for (((y, cb), cr), out_c) in y_width
335
0
            .chunks_exact(16)
336
0
            .zip(cb_width.chunks_exact(16))
337
0
            .zip(cr_width.chunks_exact(16))
338
0
            .zip(out.chunks_exact_mut(16 * num_components))
339
0
        {
340
0
            (color_convert_16)(
341
0
                y.try_into().unwrap(),
342
0
                cb.try_into().unwrap(),
343
0
                cr.try_into().unwrap(),
344
0
                out_c,
345
0
                &mut 0
346
0
            );
347
0
        }
348
        //we have more pixels in the end that can't be handled by the main loop.
349
        //move pointer back a little bit to get last 16 bytes,
350
        //color convert, and overwrite
351
        //This means some values will be color converted twice.
352
0
        for ((y, cb), cr) in y_width[width - 16..]
353
0
            .chunks_exact(16)
354
0
            .zip(cb_width[width - 16..].chunks_exact(16))
355
0
            .zip(cr_width[width - 16..].chunks_exact(16))
356
0
            .take(1)
357
0
        {
358
0
            (color_convert_16)(
359
0
                y.try_into().unwrap(),
360
0
                cb.try_into().unwrap(),
361
0
                cr.try_into().unwrap(),
362
0
                &mut temp,
363
0
                &mut 0
364
0
            );
365
0
        }
366
367
0
        let rem = out[(width - 16) * num_components..]
368
0
            .chunks_exact_mut(16 * num_components)
369
0
            .next()
370
0
            .unwrap();
371
372
0
        rem.copy_from_slice(&temp[0..rem.len()]);
373
    }
374
0
}
375
440k
pub(crate) fn upsample(
376
440k
    component: &mut Components, mcu_height: usize, i: usize, upsampler_scratch_space: &mut [i16],
377
440k
    has_vertical_sample: bool
378
440k
) -> Result<(), DecodeErrors> {
379
440k
    match component.sample_ratio {
380
        SampleRatios::V | SampleRatios::HV => {
381
            /*
382
            When upsampling vertically sampled images, we have a certain problem
383
            which is that we do not have all MCU's decoded, this usually sucks at boundaries
384
            e.g we can't upsample the last mcu row, since the row_down currently doesn't exist
385
386
            To solve this we need to do two things
387
388
            1. Carry over coefficients when we lack enough data to upsample
389
            2. Upsample when we have enough data
390
391
            To achieve (1), we store a previous row, and the current row in components themselves
392
            which will later be used to make (2)
393
394
            To achieve (2), we take the stored previous row(second last MCU row),
395
            current row(last mcu row) and row down(first row of newly decoded MCU)
396
397
            and upsample that and store it in first_row_upsample_dest, this contains
398
            up-sampled coefficients for the last for the previous decoded mcu row.
399
400
            The caller is then expected to process first_row_upsample_dest before processing data
401
            in component.upsample_dest which stores the up-sampled components excluding the last row
402
            */
403
404
268k
            let mut dest_start = 0;
405
268k
            let stride_bytes_written = component.width_stride * component.sample_ratio.sample();
406
407
268k
            if i > 0 {
408
263k
                // Handle the last MCU of the previous row
409
263k
                // This wasn't up-sampled as we didn't have the row_down
410
263k
                // so we do it now
411
263k
412
263k
                let stride = component.width_stride;
413
263k
414
263k
                let dest = &mut component.first_row_upsample_dest[0..stride_bytes_written];
415
263k
416
263k
                // get current row
417
263k
                let row = &component.row[..];
418
263k
                let row_up = &component.row_up[..];
419
263k
                let row_down = &component.raw_coeff[0..stride];
420
263k
                (component.up_sampler)(row, row_up, row_down, upsampler_scratch_space, dest);
421
263k
            }
422
423
            // we have the Y component width stride.
424
            // this may be higher than the actual width,(2x because vertical sampling)
425
            //
426
            // This will not upsample the last row
427
428
            // if false, do not upsample.
429
            // set to false on the last row of an mcu
430
268k
            let mut upsample = true;
431
432
268k
            let stride = component.width_stride * component.vertical_sample;
433
268k
            let stop_offset = component.raw_coeff.len() / component.width_stride;
434
435
268k
            if component.raw_coeff.len() != stop_offset * stride {
436
                // slice would panic below
437
0
                return Err(DecodeErrors::FormatStatic(
438
0
                    "Invalid component dimensions, would panic"
439
0
                ));
440
268k
            }
441
2.15M
            for (pos, curr_row) in component
442
268k
                .raw_coeff
443
268k
                .chunks_exact(component.width_stride)
444
268k
                .enumerate()
445
            {
446
2.15M
                let mut dest: &mut [i16] = &mut [];
447
2.15M
                let mut row_up: &[i16] = &[];
448
                // row below current sample
449
2.15M
                let mut row_down: &[i16] = &[];
450
451
                // Order of ifs matters
452
453
2.15M
                if i == 0 && pos == 0 {
454
4.92k
                    // first IMAGE row, row_up is the same as current row
455
4.92k
                    // row_down is the row below.
456
4.92k
                    row_up = &component.raw_coeff[pos * stride..(pos + 1) * stride];
457
4.92k
                    row_down = &component.raw_coeff[(pos + 1) * stride..(pos + 2) * stride];
458
2.14M
                } else if i > 0 && pos == 0 {
459
263k
                    // first row of a new mcu, previous row was copied so use that
460
263k
                    row_up = &component.row[..];
461
263k
                    row_down = &component.raw_coeff[(pos + 1) * stride..(pos + 2) * stride];
462
1.88M
                } else if i == mcu_height.saturating_sub(1) && pos == stop_offset - 1 {
463
859
                    // last IMAGE row, adjust pointer to use previous row and current row
464
859
                    row_up = &component.raw_coeff[(pos - 1) * stride..pos * stride];
465
859
                    row_down = &component.raw_coeff[pos * stride..(pos + 1) * stride];
466
1.88M
                } else if pos > 0 && pos < stop_offset - 1 {
467
1.61M
                    // other rows, get row up and row down relative to our current row
468
1.61M
                    // ignore last row of each mcu
469
1.61M
                    row_up = &component.raw_coeff[(pos - 1) * stride..pos * stride];
470
1.61M
                    row_down = &component.raw_coeff[(pos + 1) * stride..(pos + 2) * stride];
471
1.61M
                } else if pos == stop_offset - 1 {
472
267k
                    // last MCU in a row
473
267k
                    //
474
267k
                    // we need a row at the next MCU but we haven't decoded that MCU yet
475
267k
                    // so we should save this and when we have the next MCU,
476
267k
                    // do the upsampling
477
267k
478
267k
                    // store the current row and previous row in a buffer
479
267k
                    let prev_row = &component.raw_coeff[(pos - 1) * stride..pos * stride];
480
267k
481
267k
                    component.row_up.copy_from_slice(prev_row);
482
267k
                    component.row.copy_from_slice(curr_row);
483
267k
                    upsample = false;
484
267k
                } else {
485
0
                    unreachable!("Uh oh!");
486
                }
487
2.15M
                if upsample {
488
1.88M
                    dest =
489
1.88M
                        &mut component.upsample_dest[dest_start..dest_start + stride_bytes_written];
490
1.88M
                    dest_start += stride_bytes_written;
491
1.88M
                }
492
493
2.15M
                if upsample {
494
1.88M
                    // upsample
495
1.88M
                    (component.up_sampler)(
496
1.88M
                        curr_row,
497
1.88M
                        row_up,
498
1.88M
                        row_down,
499
1.88M
                        upsampler_scratch_space,
500
1.88M
                        dest
501
1.88M
                    );
502
1.88M
                }
503
            }
504
        }
505
        SampleRatios::H => {
506
            //assert_eq!(component.raw_coeff.len() * 2, component.upsample_dest.len());
507
            // Before it was an assert, but numerous and numerous and numerous
508
            // bug fixes and ad hoc solutions later, I have now just decided  to keep it as a resize
509
2.90k
            component
510
2.90k
                .upsample_dest
511
2.90k
                .resize(component.raw_coeff.len() * 2, 0);
512
513
2.90k
            let raw_coeff = &component.raw_coeff;
514
2.90k
            let dest_coeff = &mut component.upsample_dest;
515
516
2.90k
            if has_vertical_sample {
517
519
                /*
518
519
                There have been images that have the following configurations.
519
519
520
519
                Component ID:Y    HS:2 VS:2 QT:0
521
519
                Component ID:Cb   HS:1 VS:1 QT:1
522
519
                Component ID:Cr   HS:1 VS:2 QT:1
523
519
524
519
                This brings out a nasty case of misaligned sampling factors. Cr will need to save a row because
525
519
                of the way we process boundaries but Cb won't since Cr is horizontally sampled while Cb is
526
519
                HV sampled with respect to the image sampling factors.
527
519
528
519
                So during decoding of one MCU, we could only do 7 and not 8 rows, but the SampleRatio::H never had to
529
519
                save a single line, since it doesn't suffer from boundary issues.
530
519
531
519
                Now this takes care of that, saving the last MCU row in case it will be needed.
532
519
                We save the previous row before up-sampling this row because the boundary issue is in
533
519
                the last MCU row of the previous MCU.
534
519
535
519
                PS(cae): I can't add the image to the repo as it is nsfw, but can send if required
536
519
                */
537
519
                let length = component.first_row_upsample_dest.len();
538
519
                component
539
519
                    .first_row_upsample_dest
540
519
                    .copy_from_slice(&dest_coeff.rchunks_exact(length).next().unwrap());
541
2.38k
            }
542
            // up-sample each row
543
74.2k
            for (single_row, output_stride) in raw_coeff
544
2.90k
                .chunks_exact(component.width_stride)
545
2.90k
                .zip(dest_coeff.chunks_exact_mut(component.width_stride * 2))
546
74.2k
            {
547
74.2k
                // upsample using the fn pointer, should only be H, so no need for
548
74.2k
                // row up and row down
549
74.2k
                (component.up_sampler)(single_row, &[], &[], &mut [], output_stride);
550
74.2k
            }
551
        }
552
58.4k
        SampleRatios::Generic(h, v) => {
553
58.4k
            let raw_coeff = &component.raw_coeff;
554
58.4k
            let dest_coeff = &mut component.upsample_dest;
555
556
            //let size =  component.width_stride.div_ceil(v);
557
558
            // for (single_row, output_stride) in raw_coeff
559
            //     .chunks_exact(size)
560
            //     .zip(dest_coeff.chunks_exact_mut(component.width_stride * h))
561
            // {
562
            //     (component.up_sampler)(single_row, &[], &[], &mut [], output_stride);
563
            //
564
            // }
565
467k
            for (single_row, output_stride) in raw_coeff
566
58.4k
                .chunks_exact(component.width_stride)
567
58.4k
                .zip(dest_coeff.chunks_exact_mut(component.width_stride * h * v))
568
            {
569
1.86M
                for row in output_stride.chunks_exact_mut(component.width_stride * h) {
570
1.86M
                    (component.up_sampler)(single_row, &[], &[], &mut [], row);
571
1.86M
                }
572
            }
573
        }
574
110k
        SampleRatios::None => {}
575
    };
576
440k
    Ok(())
577
440k
}