j2k-jpeg 0.7.2

JPEG inspect/decode and fallback encode support for j2k
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

use super::{
    super::{is_ycbcr_420, scaled_dimensions, OutputScratch, PreparedDecodePlan},
    four_component::fill_four_component_rgb_row,
    types::{StripeEmit, StripeNeighbors},
    upsample::{
        upsample_420_pair, upsample_component_row_stripe, Stripe420PairSpec, Stripe420PairUpsample,
        StripeComponentUpsample, StripeComponentUpsampleSpec,
    },
};
use crate::{error::JpegError, info::ColorSpace, output::OutputWriter};

#[expect(
    clippy::too_many_lines,
    reason = "stripe emission keeps format-specific row construction and writer calls in output order"
)]
#[expect(
    clippy::cast_possible_truncation,
    reason = "stripe row indices are bounded by validated u32 JPEG dimensions before writer dispatch"
)]
pub(in crate::entropy::sequential) fn emit_stripe<W: OutputWriter>(
    plan: &PreparedDecodePlan,
    writer: &mut W,
    output_scratch: &mut OutputScratch<'_>,
    emit: StripeEmit<'_>,
) -> Result<(), JpegError> {
    let StripeEmit {
        prev,
        curr,
        next,
        stripe_index,
        source_width,
        downscale,
    } = emit;
    let max_v = u32::from(plan.sampling.max_v);
    let mcu_height_px = downscale.output_block_size() * max_v;
    let y_start = stripe_index * mcu_height_px;
    let (_, scaled_height) = scaled_dimensions(plan.dimensions, downscale);
    let y_end = (y_start + mcu_height_px).min(scaled_height);
    let stripe_rows = (y_end - y_start) as usize;

    if stripe_rows == 0 {
        return Ok(());
    }

    let width = source_width;
    let neighbors = StripeNeighbors { prev, curr, next };
    match plan.color_space {
        ColorSpace::Grayscale => {
            for local_y in 0..stripe_rows {
                let y_row = &curr.row(0, local_y)[..width];
                writer.write_gray_row(y_start + local_y as u32, y_row)?;
            }
        }
        ColorSpace::YCbCr => {
            let (cb_h, cb_v) = plan
                .sampling
                .component(1)
                .map(|(h, v)| (u32::from(h), u32::from(v)))
                .ok_or(JpegError::UnsupportedComponentCount { count: 1 })?;
            let (cr_h, cr_v) = plan
                .sampling
                .component(2)
                .map(|(h, v)| (u32::from(h), u32::from(v)))
                .ok_or(JpegError::UnsupportedComponentCount { count: 2 })?;

            let max_h = u32::from(plan.sampling.max_h);
            let max_v = u32::from(plan.sampling.max_v);

            if is_ycbcr_420(plan) {
                let OutputScratch::YCbCr420(scratch) = output_scratch else {
                    unreachable!("4:2:0 YCbCr requires dedicated scratch");
                };
                debug_assert!(
                    max_h == 2 && max_v == 2 && cb_h == 1 && cb_v == 1 && cr_h == 1 && cr_v == 1
                );

                let mut local_y = 0usize;
                while local_y < stripe_rows {
                    let y_top = &curr.row(0, local_y)[..width];
                    let next_local_y = local_y + 1;
                    let y_bottom =
                        (next_local_y < stripe_rows).then(|| &curr.row(0, next_local_y)[..width]);

                    upsample_420_pair(Stripe420PairUpsample {
                        neighbors,
                        spec: Stripe420PairSpec {
                            plane_idx: 1,
                            local_y_out: local_y as u32,
                            width,
                        },
                        top: &mut scratch.cb_top,
                        bot: &mut scratch.cb_bot,
                    });
                    upsample_420_pair(Stripe420PairUpsample {
                        neighbors,
                        spec: Stripe420PairSpec {
                            plane_idx: 2,
                            local_y_out: local_y as u32,
                            width,
                        },
                        top: &mut scratch.cr_top,
                        bot: &mut scratch.cr_bot,
                    });

                    writer.write_ycbcr_row(
                        y_start + local_y as u32,
                        y_top,
                        &scratch.cb_top,
                        &scratch.cr_top,
                    )?;
                    if let Some(y_bottom) = y_bottom {
                        writer.write_ycbcr_row(
                            y_start + next_local_y as u32,
                            y_bottom,
                            &scratch.cb_bot,
                            &scratch.cr_bot,
                        )?;
                    }
                    local_y += 2;
                }
            } else {
                let OutputScratch::YCbCrGeneric(scratch) = output_scratch else {
                    unreachable!("generic YCbCr requires reusable row scratch");
                };

                for local_y in 0..stripe_rows {
                    let y_row = &curr.row(0, local_y)[..width];
                    upsample_component_row_stripe(StripeComponentUpsample {
                        neighbors,
                        spec: StripeComponentUpsampleSpec {
                            plane_idx: 1,
                            comp_h: cb_h,
                            comp_v: cb_v,
                            max_h,
                            max_v,
                            local_y_out: local_y as u32,
                            width,
                        },
                        out: &mut scratch.cb_up,
                    });
                    upsample_component_row_stripe(StripeComponentUpsample {
                        neighbors,
                        spec: StripeComponentUpsampleSpec {
                            plane_idx: 2,
                            comp_h: cr_h,
                            comp_v: cr_v,
                            max_h,
                            max_v,
                            local_y_out: local_y as u32,
                            width,
                        },
                        out: &mut scratch.cr_up,
                    });
                    writer.write_ycbcr_row(
                        y_start + local_y as u32,
                        y_row,
                        &scratch.cb_up,
                        &scratch.cr_up,
                    )?;
                }
            }
        }
        ColorSpace::Rgb => {
            let (r_h, r_v) = plan
                .sampling
                .component(0)
                .map(|(h, v)| (u32::from(h), u32::from(v)))
                .ok_or(JpegError::UnsupportedComponentCount { count: 0 })?;
            let (g_h, g_v) = plan
                .sampling
                .component(1)
                .map(|(h, v)| (u32::from(h), u32::from(v)))
                .ok_or(JpegError::UnsupportedComponentCount { count: 1 })?;
            let (b_h, b_v) = plan
                .sampling
                .component(2)
                .map(|(h, v)| (u32::from(h), u32::from(v)))
                .ok_or(JpegError::UnsupportedComponentCount { count: 2 })?;

            let max_h = u32::from(plan.sampling.max_h);
            let max_v = u32::from(plan.sampling.max_v);

            let OutputScratch::RgbGeneric(scratch) = output_scratch else {
                unreachable!("RGB decode requires reusable row scratch");
            };

            for local_y in 0..stripe_rows {
                upsample_component_row_stripe(StripeComponentUpsample {
                    neighbors,
                    spec: StripeComponentUpsampleSpec {
                        plane_idx: 0,
                        comp_h: r_h,
                        comp_v: r_v,
                        max_h,
                        max_v,
                        local_y_out: local_y as u32,
                        width,
                    },
                    out: &mut scratch.r,
                });
                upsample_component_row_stripe(StripeComponentUpsample {
                    neighbors,
                    spec: StripeComponentUpsampleSpec {
                        plane_idx: 1,
                        comp_h: g_h,
                        comp_v: g_v,
                        max_h,
                        max_v,
                        local_y_out: local_y as u32,
                        width,
                    },
                    out: &mut scratch.g,
                });
                upsample_component_row_stripe(StripeComponentUpsample {
                    neighbors,
                    spec: StripeComponentUpsampleSpec {
                        plane_idx: 2,
                        comp_h: b_h,
                        comp_v: b_v,
                        max_h,
                        max_v,
                        local_y_out: local_y as u32,
                        width,
                    },
                    out: &mut scratch.b,
                });
                writer.write_rgb_row(
                    y_start + local_y as u32,
                    &scratch.r,
                    &scratch.g,
                    &scratch.b,
                )?;
            }
        }
        ColorSpace::Cmyk | ColorSpace::Ycck => {
            let OutputScratch::RgbGeneric(scratch) = output_scratch else {
                unreachable!("CMYK/YCCK decode requires reusable row scratch");
            };
            for local_y in 0..stripe_rows {
                fill_four_component_rgb_row(
                    plan,
                    prev,
                    curr,
                    next,
                    local_y as u32,
                    width,
                    scratch,
                )?;
                writer.write_rgb_row(
                    y_start + local_y as u32,
                    &scratch.r[..width],
                    &scratch.g[..width],
                    &scratch.b[..width],
                )?;
            }
        }
    }
    Ok(())
}