j2k-jpeg 0.6.1

JPEG inspect/decode and fallback encode support for j2k
Documentation
use j2k_jpeg::adapter::{assemble_jpeg_baseline_frame, baseline_encode_tables};
use j2k_jpeg::{
    encode_jpeg_baseline, JpegBackend, JpegEncodeOptions, JpegSamples, JpegSubsampling,
};

#[test]
fn baseline_encode_helper_assembles_same_frame_as_cpu_encoder() {
    let pixels = j2k_test_support::patterned_rgb8(2, 2);
    let options = JpegEncodeOptions {
        quality: 87,
        subsampling: JpegSubsampling::Ybr444,
        restart_interval: Some(4),
        backend: JpegBackend::Cpu,
    };
    let encoded = encode_jpeg_baseline(
        JpegSamples::Rgb8 {
            data: &pixels,
            width: 2,
            height: 2,
        },
        options,
    )
    .expect("cpu encode");

    let entropy = entropy_payload(&encoded.data);
    let tables = baseline_encode_tables(options).expect("baseline tables");
    let assembled = assemble_jpeg_baseline_frame(entropy, 2, 2, &tables, options, JpegBackend::Cpu)
        .expect("assemble frame");

    assert_eq!(assembled, encoded);
}

#[test]
fn baseline_encode_tables_expose_sampling_for_adapters() {
    let tables = baseline_encode_tables(JpegEncodeOptions {
        subsampling: JpegSubsampling::Ybr420,
        ..JpegEncodeOptions::default()
    })
    .expect("baseline tables");

    assert_eq!(tables.sampling.components, 3);
    assert_eq!(tables.sampling.h, [2, 1, 1]);
    assert_eq!(tables.sampling.v, [2, 1, 1]);
    assert_eq!(tables.sampling.max_h, 2);
    assert_eq!(tables.sampling.max_v, 2);
}

fn entropy_payload(data: &[u8]) -> &[u8] {
    let mut offset = 0usize;
    while offset + 4 <= data.len() {
        assert_eq!(data[offset], 0xff, "expected marker at offset {offset}");
        let marker = data[offset + 1];
        offset += 2;
        if marker == 0xd8 {
            continue;
        }
        if marker == 0xda {
            let len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
            let entropy_start = offset + len;
            let entropy_end = data
                .windows(2)
                .enumerate()
                .skip(entropy_start)
                .find_map(|(idx, marker)| (marker == [0xff, 0xd9]).then_some(idx))
                .expect("EOI marker");
            return &data[entropy_start..entropy_end];
        }
        let len = u16::from_be_bytes([data[offset], data[offset + 1]]) as usize;
        offset += len;
    }
    panic!("SOS marker not found");
}