j2k-native 0.13.0

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

use j2k_native::{
    encode, encode_j2k_packetization_scalar, encode_with_accelerator, DecodeSettings,
    EncodeOptions, EncodeProgressionOrder, Image, J2kEncodeStageAccelerator, J2kEncodeStageError,
    J2kEncodeStageResult, J2kPacketizationEncodeJob,
};

const ISSUE_WIDTH: u32 = 301;
const ISSUE_HEIGHT: u32 = 203;
const COMPACT_WIDTH: u32 = 129;
const COMPACT_HEIGHT: u32 = 97;

fn source_samples(width: u32, height: u32) -> Vec<u8> {
    (0..width * height * 3)
        .map(|index| u8::try_from(index * 7 % 251).expect("sample is less than 251"))
        .collect()
}

fn precincts(exponents: [(u8, u8); 4]) -> Vec<(u8, u8)> {
    let mut values = Vec::new();
    values
        .try_reserve_exact(exponents.len())
        .expect("four precinct sizes");
    values.extend(exponents);
    values
}

fn issue_options(progression_order: EncodeProgressionOrder, num_layers: u8) -> EncodeOptions {
    EncodeOptions {
        reversible: true,
        num_decomposition_levels: 3,
        code_block_width_exp: 3,
        code_block_height_exp: 3,
        progression_order,
        num_layers,
        precinct_exponents: precincts([(5, 5), (6, 6), (6, 6), (6, 6)]),
        ..EncodeOptions::default()
    }
}

fn assert_issue_round_trip(progression_order: EncodeProgressionOrder, num_layers: u8) {
    let samples = source_samples(ISSUE_WIDTH, ISSUE_HEIGHT);
    let options = issue_options(progression_order, num_layers);
    let codestream = encode(&samples, ISSUE_WIDTH, ISSUE_HEIGHT, 3, 8, false, &options)
        .unwrap_or_else(|error| panic!("{progression_order:?}, {num_layers} layers: {error}"));
    let decoded = Image::new(&codestream, &DecodeSettings::default())
        .and_then(|image| image.decode())
        .unwrap_or_else(|error| panic!("{progression_order:?}, {num_layers} layers: {error}"));

    assert_eq!(
        decoded, samples,
        "{progression_order:?}, {num_layers} layers must reproduce the source samples"
    );
}

#[test]
fn position_major_progressions_round_trip_with_differing_precinct_spans() {
    for progression_order in [EncodeProgressionOrder::Pcrl, EncodeProgressionOrder::Cprl] {
        for num_layers in [1, 3] {
            assert_issue_round_trip(progression_order, num_layers);
        }
    }
}

#[test]
fn high_bit_partial_edge_tiles_round_trip_with_position_progression() {
    let samples = (0..ISSUE_WIDTH * ISSUE_HEIGHT)
        .flat_map(|index| (index.wrapping_mul(1_000_003) & ((1 << 29) - 1)).to_le_bytes())
        .collect::<Vec<_>>();
    let options = EncodeOptions {
        reversible: true,
        num_decomposition_levels: 3,
        code_block_width_exp: 2,
        code_block_height_exp: 2,
        progression_order: EncodeProgressionOrder::Cprl,
        use_mct: false,
        tile_size: Some((128, 128)),
        precinct_exponents: precincts([(4, 4), (5, 5), (5, 5), (5, 5)]),
        ..EncodeOptions::default()
    };

    let codestream = encode(&samples, ISSUE_WIDTH, ISSUE_HEIGHT, 1, 29, false, &options)
        .expect("encode 29-bit partial-edge multi-tile CPRL codestream");
    let decoded = Image::new(&codestream, &DecodeSettings::default())
        .and_then(|image| image.decode_native())
        .expect("decode 29-bit partial-edge multi-tile CPRL codestream");

    assert_eq!((decoded.width, decoded.height), (ISSUE_WIDTH, ISSUE_HEIGHT));
    assert_eq!(decoded.bit_depth, 29);
    assert_eq!(decoded.data, samples);
}

#[test]
fn tile_origins_preserve_custom_precincts_layers_and_progressions() {
    let (width, height) = (130, 99);
    let samples = source_samples(width, height);
    for (progression_order, num_layers, use_ht_block_coding) in [
        (EncodeProgressionOrder::Lrcp, 1, false),
        (EncodeProgressionOrder::Rlcp, 3, true),
        (EncodeProgressionOrder::Rpcl, 2, false),
        (EncodeProgressionOrder::Pcrl, 1, true),
        (EncodeProgressionOrder::Cprl, 3, false),
    ] {
        let options = EncodeOptions {
            num_decomposition_levels: 3,
            code_block_width_exp: 1,
            code_block_height_exp: 1,
            tile_size: Some((39, 35)),
            precinct_exponents: vec![(3, 3), (4, 4), (4, 4), (4, 4)],
            progression_order,
            num_layers,
            use_ht_block_coding,
            write_sop: true,
            write_plt: true,
            ..EncodeOptions::default()
        };
        let codestream = encode(&samples, width, height, 3, 8, false, &options)
            .unwrap_or_else(|error| panic!("{progression_order:?}: {error}"));
        let decoded = Image::new(&codestream, &DecodeSettings::strict())
            .and_then(|image| image.decode())
            .unwrap_or_else(|error| panic!("{progression_order:?}: {error}"));
        assert_eq!(decoded, samples, "{progression_order:?}");
    }
}

#[derive(Default)]
struct DescriptorCapturingAccelerator {
    descriptors: Vec<(u16, u32, u64, u8)>,
}

impl J2kEncodeStageAccelerator for DescriptorCapturingAccelerator {
    fn encode_packetization(
        &mut self,
        job: J2kPacketizationEncodeJob<'_>,
    ) -> J2kEncodeStageResult<Option<Vec<u8>>> {
        self.descriptors = job
            .packet_descriptors
            .iter()
            .map(|descriptor| {
                (
                    descriptor.component,
                    descriptor.resolution,
                    descriptor.precinct,
                    descriptor.layer,
                )
            })
            .collect();
        encode_j2k_packetization_scalar(job)
            .map(Some)
            .map_err(|source| {
                J2kEncodeStageError::backend(
                    "descriptor capture test",
                    "scalar packetization",
                    source,
                )
            })
    }
}

#[test]
fn packetization_accelerator_observes_reference_grid_progression_order() {
    let samples = source_samples(COMPACT_WIDTH, COMPACT_HEIGHT);
    let options = issue_options(EncodeProgressionOrder::Pcrl, 1);
    let mut accelerator = DescriptorCapturingAccelerator::default();
    let codestream = encode_with_accelerator(
        &samples,
        COMPACT_WIDTH,
        COMPACT_HEIGHT,
        3,
        8,
        false,
        &options,
        &mut accelerator,
    )
    .expect("encode PCRL through packetization accelerator");

    let expected_prefix = [
        (0, 0, 0, 0),
        (0, 1, 0, 0),
        (0, 2, 0, 0),
        (0, 3, 0, 0),
        (1, 0, 0, 0),
        (1, 1, 0, 0),
        (1, 2, 0, 0),
        (1, 3, 0, 0),
        (2, 0, 0, 0),
        (2, 1, 0, 0),
        (2, 2, 0, 0),
        (2, 3, 0, 0),
        (0, 3, 1, 0),
    ];
    assert!(accelerator.descriptors.starts_with(&expected_prefix));

    let decoded = Image::new(&codestream, &DecodeSettings::default())
        .and_then(|image| image.decode())
        .expect("decode accelerator packetization codestream");
    assert_eq!(decoded, samples);
}

#[test]
fn tiles_cross_implicit_precinct_boundaries_on_both_axes() {
    for (width, height, tile) in [(65_537, 4, (32_769, 4)), (4, 65_537, (4, 32_769))] {
        let pixels = source_samples(width, height);
        for use_ht_block_coding in [false, true] {
            for num_decomposition_levels in [0, 1] {
                let options = EncodeOptions {
                    tile_size: Some(tile),
                    num_decomposition_levels,
                    use_ht_block_coding,
                    progression_order: EncodeProgressionOrder::Pcrl,
                    num_layers: 2,
                    ..EncodeOptions::default()
                };
                let encoded = encode(&pixels, width, height, 3, 8, false, &options)
                    .expect("encode tiles spanning the implicit 32768 precinct grid");
                let decoded = Image::new(&encoded, &DecodeSettings::strict())
                    .and_then(|image| image.decode())
                    .expect("decode all implicit precincts");
                assert_eq!(decoded, pixels);
            }
        }
    }
}