j2k-native 0.13.0

Pure-Rust JPEG 2000 and HTJ2K codec engine for j2k
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0

//! HTJ2K packet contributions and codeword-segment lengths.

use super::{
    classic, code_block_is_read, decode_num_coding_passes, read_lblock_increment,
    read_segment_length, resolve_code_block_inclusion, PacketResult, PacketSkips, SegmentSink,
    MAX_BITPLANE_COUNT,
};
use crate::j2c::build::{CodeBlock, CodeBlockCoding, Segment};
use crate::j2c::codestream::{CodeBlockStyle, ComponentInfo};
use crate::j2c::decode::DecompositionStorage;
use crate::j2c::progression::ProgressionData;
use crate::reader::BitReader;

#[expect(
    clippy::too_many_arguments,
    reason = "the packet, its precinct's offset among the records, and the parser state it fills stay explicit"
)]
pub(super) fn resolve_segments(
    sub_band_dx: usize,
    progression_data: &ProgressionData,
    precinct_offset: usize,
    reader: &mut BitReader<'_>,
    storage: &mut DecompositionStorage<'_>,
    component_info: &ComponentInfo,
    num_layers: u8,
    skips: &mut PacketSkips,
) -> PacketResult {
    const MAX_CODING_PASSES: u8 = 1 + 3 * (MAX_BITPLANE_COUNT - 1);

    let sub_band = &storage.sub_bands[sub_band_dx];
    let precinct_range = sub_band.precincts.clone();
    let Some(code_block_range) = storage.precincts[precinct_range.clone()]
        .get(precinct_offset)
        .map(|precinct| precinct.code_blocks.clone())
    else {
        lwarn!("progression data yielded invalid precinct index");

        return Err("HT packet references a missing precinct");
    };

    for code_block_idx in code_block_range {
        let precinct = &mut storage.precincts[precinct_range.clone()][precinct_offset];
        let code_block = &mut storage.code_blocks[code_block_idx];
        let inclusion = resolve_code_block_inclusion(
            code_block,
            precinct,
            progression_data,
            reader,
            &mut storage.tag_tree_nodes,
        )
        .ok_or("HT code-block inclusion or zero-bitplane tree is truncated")?;

        if !inclusion.included {
            continue;
        }

        // Only code-blocks the decode reads have layer records; the others'
        // contributions are checked the same way and then skipped. The
        // progression yields each packet once, so a skipped code-block can't
        // see a layer twice.
        let read = code_block_is_read(storage, sub_band_dx, &storage.code_blocks[code_block_idx]);
        let layer_num = usize::from(progression_data.layer_num);
        let code_block = &mut storage.code_blocks[code_block_idx];
        let layer = if read {
            let layer = storage.layers[code_block.layers.clone()]
                .get_mut(layer_num)
                .ok_or("HT packet references a missing quality layer")?;
            if layer.segments.is_some() {
                return Err("HT quality layer already owns segment metadata");
            }
            Some(layer)
        } else if layer_num < usize::from(num_layers) {
            None
        } else {
            return Err("HT packet references a missing quality layer");
        };

        let raw_num_passes = decode_num_coding_passes(reader)
            .ok_or("HT coding-pass count is truncated or invalid")?;

        if raw_num_passes > MAX_CODING_PASSES {
            return Err("HT coding-pass count exceeds the supported maximum");
        }

        ltrace!("HT raw number of coding passes: {}", raw_num_passes);

        if inclusion.included_first_time
            && (code_block.number_of_coding_passes != 0
                || code_block.ht_total_coding_passes != 0
                || code_block.ht_first_cleanup_pass.is_some()
                || code_block.ht_selected_set.is_some()
                || code_block.coding.is_some()
                || code_block.non_empty_layer_count != 0)
        {
            return Err("first HT inclusion has stale coding-pass state");
        }

        if let Some(layer) = layer {
            let start = storage.segments.len();
            parse_segment_lengths(
                reader,
                raw_num_passes,
                component_info.code_block_style(),
                code_block,
                &mut SegmentSink::Record {
                    segments: &mut storage.segments,
                    structural_workspace_bytes: storage.structural_workspace_bytes,
                    packet_workspace_error: &mut storage.packet_workspace_error,
                },
            )?;
            layer.segments = Some(start..storage.segments.len());
        } else {
            let mut bytes = 0;
            parse_segment_lengths(
                reader,
                raw_num_passes,
                component_info.code_block_style(),
                code_block,
                &mut SegmentSink::Skip(&mut bytes),
            )?;
            skips.push(code_block_idx, bytes)?;
        }
        code_block.non_empty_layer_count = code_block
            .non_empty_layer_count
            .checked_add(1)
            .ok_or("HT non-empty layer count overflows")?;
    }

    Ok(())
}

#[expect(
    clippy::too_many_lines,
    reason = "the ordered HT packet contribution state machine stays cohesive across placeholder, cleanup, and refinement passes"
)]
fn parse_segment_lengths(
    reader: &mut BitReader<'_>,
    raw_num_passes: u8,
    style: CodeBlockStyle,
    code_block: &mut CodeBlock,
    sink: &mut SegmentSink<'_, '_>,
) -> PacketResult {
    const MAX_CODING_PASSES: u8 = 1 + 3 * (MAX_BITPLANE_COUNT - 1);

    let total_after = code_block
        .ht_total_coding_passes
        .checked_add(raw_num_passes)
        .ok_or("HT cumulative coding-pass count overflows")?;
    if total_after > MAX_CODING_PASSES {
        return Err("HT cumulative coding-pass count exceeds the supported maximum");
    }

    code_block.l_block = code_block
        .l_block
        .checked_add(read_lblock_increment(reader).ok_or("HT Lblock increment is truncated")?)
        .ok_or("HT Lblock increment overflows")?;

    if code_block.coding == Some(CodeBlockCoding::Classic) {
        return classic::parse_contribution_lengths(
            reader,
            raw_num_passes,
            None,
            code_block,
            style,
            sink,
        );
    }

    let mut push_segment = |idx: u8, coding_passes: u8, data_length: u32| -> PacketResult {
        sink.push(Segment {
            idx,
            coding_pases: coding_passes,
            data_length,
            data: &[],
        })
        .ok_or("HT segment metadata allocation failed")
    };

    if code_block.ht_first_cleanup_pass.is_none() {
        let refinement_passes = total_after.saturating_sub(1) % 3;
        let possible_cleanup_passes = raw_num_passes.saturating_sub(refinement_passes);
        let first_contribution_passes = if possible_cleanup_passes == 0 {
            raw_num_passes
        } else {
            possible_cleanup_passes
        };
        let mut cleanup_length =
            read_segment_length(reader, code_block.l_block, first_contribution_passes)?;

        if style.allows_mixed_block_coding() && code_block.coding.is_none() {
            let candidate_bits = code_block
                .l_block
                .checked_add(first_contribution_passes.ilog2())
                .ok_or("mixed segment-length field width overflows")?;
            let classic_bits = code_block
                .l_block
                .checked_add(raw_num_passes.ilog2())
                .ok_or("mixed classic segment-length field width overflows")?;
            let mut bits_read = candidate_bits;
            let possible_ht_cleanup = possible_cleanup_passes != 0;
            let ht_discriminator = possible_ht_cleanup
                && code_block.l_block > 3
                && cleanup_length > 1
                && candidate_bits <= 32
                && cleanup_length & (1_u32 << (candidate_bits - 1)) == 0;

            while !ht_discriminator && bits_read < classic_bits {
                cleanup_length = cleanup_length
                    .checked_shl(1)
                    .ok_or("mixed classic segment length overflows")?
                    | reader
                        .read_bits_with_stuffing(1)
                        .ok_or("mixed classic segment length is truncated")?;
                bits_read += 1;
            }

            if cleanup_length != 0 && !ht_discriminator {
                code_block.coding = Some(CodeBlockCoding::Classic);
                code_block.number_of_coding_passes = code_block.ht_total_coding_passes;
                return classic::parse_contribution_lengths(
                    reader,
                    raw_num_passes,
                    Some(cleanup_length),
                    code_block,
                    style,
                    sink,
                );
            }
            if ht_discriminator {
                code_block.coding = Some(CodeBlockCoding::HighThroughput);
            }
        } else {
            code_block.coding = Some(CodeBlockCoding::HighThroughput);
        }

        if cleanup_length == 0 {
            if !style.allows_mixed_block_coding() && first_contribution_passes < raw_num_passes {
                let extra_bits = raw_num_passes
                    .ilog2()
                    .checked_sub(first_contribution_passes.ilog2())
                    .ok_or("HT placeholder length width is inconsistent")?;
                if extra_bits != 0 {
                    cleanup_length = reader
                        .read_bits_with_stuffing(
                            u8::try_from(extra_bits)
                                .map_err(|_| "HT placeholder length field is too wide")?,
                        )
                        .ok_or("HT placeholder length is truncated")?;
                }
            }
            if cleanup_length != 0 {
                return Err("HT placeholder contribution has a non-zero length");
            }
            code_block.ht_total_coding_passes = total_after;
            return Ok(());
        }

        if !(2..65_535).contains(&cleanup_length) {
            return Err("HT cleanup segment length is invalid");
        }

        let cleanup_pass = code_block
            .ht_total_coding_passes
            .checked_add(possible_cleanup_passes)
            .and_then(|value| value.checked_sub(1))
            .ok_or("HT first cleanup pass index overflows")?;
        if cleanup_pass % 3 != 0 {
            return Err("HT first cleanup pass is not aligned to an HT set");
        }
        let placeholder_bitplanes = cleanup_pass / 3;
        code_block.missing_bit_planes = code_block
            .missing_bit_planes
            .checked_add(placeholder_bitplanes)
            .ok_or("HT placeholder bit-plane count overflows")?;
        code_block.ht_first_cleanup_pass = Some(cleanup_pass);
        code_block.ht_selected_set = Some(0);
        code_block.number_of_coding_passes = 1;
        push_segment(0, possible_cleanup_passes, cleanup_length)?;
        ltrace!("HT cleanup length {}", cleanup_length);

        if refinement_passes != 0 {
            let refinement_length =
                read_segment_length(reader, code_block.l_block, refinement_passes)?;
            if refinement_length >= 2_047 {
                return Err("HT refinement segment length is invalid");
            }
            push_segment(1, refinement_passes, refinement_length)?;
            code_block.number_of_coding_passes = 1 + refinement_passes;
            ltrace!("HT refinement length {}", refinement_length);
        }

        code_block.ht_total_coding_passes = total_after;
        return Ok(());
    }

    let first_cleanup_pass = code_block
        .ht_first_cleanup_pass
        .ok_or("HT cleanup state is inconsistent")?;
    let mut next_pass = code_block.ht_total_coding_passes;
    let mut remaining = raw_num_passes;
    while remaining != 0 {
        let relative_pass = next_pass
            .checked_sub(first_cleanup_pass)
            .ok_or("HT pass precedes the first cleanup pass")?;
        let pass_in_set = relative_pass % 3;
        let set_index = relative_pass / 3;
        let is_cleanup = pass_in_set == 0;
        let available_in_segment = if pass_in_set == 1 { 2 } else { 1 };
        let contribution_passes = remaining.min(available_in_segment);
        let length = read_segment_length(reader, code_block.l_block, contribution_passes)?;
        let segment_index = set_index
            .checked_mul(2)
            .and_then(|value| value.checked_add(u8::from(!is_cleanup)))
            .ok_or("HT segment index overflows")?;

        if is_cleanup {
            if length == 1 || length >= 65_535 {
                return Err("HT cleanup segment length is invalid");
            }
            if length != 0 {
                let previous_set = code_block
                    .ht_selected_set
                    .ok_or("HT selected-set state is inconsistent")?;
                code_block.missing_bit_planes = code_block
                    .missing_bit_planes
                    .checked_add(
                        set_index
                            .checked_sub(previous_set)
                            .ok_or("HT selected-set order is invalid")?,
                    )
                    .ok_or("HT selected bit-plane count overflows")?;
                code_block.ht_selected_set = Some(set_index);
                code_block.number_of_coding_passes = 1;
                push_segment(segment_index, contribution_passes, length)?;
                ltrace!("HT cleanup length {}", length);
            }
        } else {
            if length >= 2_047 {
                return Err("HT refinement segment length is invalid");
            }
            if code_block.ht_selected_set == Some(set_index) {
                push_segment(segment_index, contribution_passes, length)?;
                code_block.number_of_coding_passes = pass_in_set + contribution_passes;
                ltrace!("HT refinement length {}", length);
            } else if length != 0 {
                return Err("empty HT set has a non-zero refinement length");
            }
        }

        next_pass = next_pass
            .checked_add(contribution_passes)
            .ok_or("HT cumulative coding-pass count overflows")?;
        remaining -= contribution_passes;
    }

    code_block.ht_total_coding_passes = total_after;
    Ok(())
}

#[cfg(test)]
mod tests;