Skip to main content

j2k_native/j2c/
encode.rs

1//! Top-level JPEG 2000 encode orchestration.
2//!
3//! Coordinates the full encoding pipeline:
4//!   pixels → MCT → DWT → quantize → EBCOT T1 → T2 → codestream
5//!
6//! Supports both lossless (5-3 reversible) and lossy (9-7 irreversible) encoding.
7
8use alloc::vec;
9use alloc::vec::Vec;
10use core::cmp::Ordering;
11use core::ops::Range;
12
13#[cfg(feature = "parallel")]
14use rayon::prelude::*;
15
16use super::bitplane_encode;
17use super::build::SubBandType;
18use super::codestream::CodeBlockStyle;
19use super::codestream_write::{self, BlockCodingMode, EncodeParams};
20use super::fdwt::{self, DwtDecomposition};
21use super::forward_mct;
22use super::ht_block_encode;
23use super::packet_encode::{self, CodeBlockPacketData, ResolutionPacket, SubbandPrecinct};
24pub use super::quantize::irreversible_quantization_step_for_subband;
25use super::quantize::{self, QuantStepSize};
26use crate::math::{floor_f32, log2_f32};
27use crate::profile;
28use crate::{
29    CpuOnlyJ2kEncodeStageAccelerator, EncodedHtJ2kCodeBlock, EncodedJ2kCodeBlock,
30    IrreversibleQuantizationSubbandScales, J2kDeinterleaveToF32Job, J2kEncodeStageAccelerator,
31    J2kForwardDwt53Job, J2kForwardDwt53Level, J2kForwardDwt53Output, J2kForwardDwt97Job,
32    J2kForwardDwt97Level, J2kForwardDwt97Output, J2kForwardIctJob, J2kForwardRctJob,
33    J2kHtSubbandEncodeJob, J2kHtj2kTileEncodeJob, J2kPacketizationBlockCodingMode,
34    J2kPacketizationCodeBlock, J2kPacketizationEncodeJob, J2kPacketizationPacketDescriptor,
35    J2kPacketizationResolution, J2kPacketizationSubband, J2kQuantizeSubbandJob, J2kSubBandType,
36    J2kTier1CodeBlockEncodeJob, PrecomputedHtj2k53Component, PrecomputedHtj2k53Image,
37    PrecomputedHtj2k97Component, PrecomputedHtj2k97Image, PreencodedHtj2k97CodeBlock,
38    PreencodedHtj2k97CompactCodeBlock, PreencodedHtj2k97CompactComponent,
39    PreencodedHtj2k97CompactImage, PreencodedHtj2k97CompactResolution,
40    PreencodedHtj2k97CompactSubband, PreencodedHtj2k97Component, PreencodedHtj2k97Image,
41    PreencodedHtj2k97Resolution, PreencodedHtj2k97Subband, PrequantizedHtj2k97Component,
42    PrequantizedHtj2k97Image, PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
43};
44use crate::{DecodeSettings, Image};
45
46const HT_CPU_PARALLEL_FALLBACK_MIN_JOBS: usize = 4;
47
48/// Encoding options for JPEG 2000.
49#[derive(Debug, Clone)]
50pub struct EncodeOptions {
51    /// Number of decomposition levels (default: 5).
52    pub num_decomposition_levels: u8,
53    /// Use reversible (lossless) transform (default: true).
54    pub reversible: bool,
55    /// Code-block width exponent minus 2 (default: 4, meaning 2^6=64).
56    pub code_block_width_exp: u8,
57    /// Code-block height exponent minus 2 (default: 4, meaning 2^6=64).
58    pub code_block_height_exp: u8,
59    /// Number of guard bits (default: 1 for reversible, 2 for irreversible).
60    pub guard_bits: u8,
61    /// Encode using HT block coding (HTJ2K / Part 15) instead of classic EBCOT.
62    pub use_ht_block_coding: bool,
63    /// Packet progression order to write in COD and use for packetization.
64    pub progression_order: EncodeProgressionOrder,
65    /// Write a TLM marker segment for the single tile-part.
66    pub write_tlm: bool,
67    /// Write PLT packet-length marker segments in the tile-part header.
68    pub write_plt: bool,
69    /// Write PLM packet-length marker segments in the main header.
70    pub write_plm: bool,
71    /// Write SOP marker segments before packets.
72    pub write_sop: bool,
73    /// Write EPH markers after packet headers.
74    pub write_eph: bool,
75    /// Apply the JPEG 2000 multi-component color transform for 3+ component inputs.
76    pub use_mct: bool,
77    /// Number of cumulative quality layers to emit.
78    pub num_layers: u8,
79    /// Optional cumulative packet-body byte targets for each quality layer.
80    pub quality_layer_byte_targets: Vec<u64>,
81    /// Decode and verify HTJ2K codestreams inside the native encoder.
82    pub validate_high_throughput_codestream: bool,
83    /// Multiplier applied to irreversible 9/7 scalar quantization step sizes.
84    ///
85    /// `1.0` preserves the near-lossless default step sizes. Larger values
86    /// produce smaller codestreams by coarsening quantization.
87    pub irreversible_quantization_scale: f32,
88    /// Per-subband multipliers applied on top of
89    /// `irreversible_quantization_scale`.
90    pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
91    /// Optional per-component SIZ sampling factors (`XRsiz`, `YRsiz`).
92    ///
93    /// `None` means every component is stored at the reference-grid
94    /// resolution. This is experimental and primarily intended for precomputed
95    /// coefficient encoders that preserve JPEG-native chroma subsampling.
96    pub component_sampling: Option<Vec<(u8, u8)>>,
97    /// Optional tile width and height for multi-tile codestream output.
98    pub tile_size: Option<(u32, u32)>,
99    /// Optional precinct exponents in COD order, one per resolution level.
100    pub precinct_exponents: Vec<(u8, u8)>,
101}
102
103impl Default for EncodeOptions {
104    fn default() -> Self {
105        Self {
106            num_decomposition_levels: 5,
107            reversible: true,
108            code_block_width_exp: 4,
109            code_block_height_exp: 4,
110            guard_bits: 1,
111            use_ht_block_coding: false,
112            progression_order: EncodeProgressionOrder::Lrcp,
113            write_tlm: false,
114            write_plt: false,
115            write_plm: false,
116            write_sop: false,
117            write_eph: false,
118            use_mct: true,
119            num_layers: 1,
120            quality_layer_byte_targets: Vec::new(),
121            validate_high_throughput_codestream: true,
122            irreversible_quantization_scale: 1.0,
123            irreversible_quantization_subband_scales:
124                IrreversibleQuantizationSubbandScales::default(),
125            component_sampling: None,
126            tile_size: None,
127            precinct_exponents: Vec::new(),
128        }
129    }
130}
131
132/// JPEG 2000 packet progression orders supported by the encoder.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
134pub enum EncodeProgressionOrder {
135    /// Layer-resolution-component-position progression.
136    #[default]
137    Lrcp,
138    /// Resolution-layer-component-position progression.
139    Rlcp,
140    /// Resolution-position-component-layer progression.
141    Rpcl,
142    /// Position-component-resolution-layer progression.
143    Pcrl,
144    /// Component-position-resolution-layer progression.
145    Cprl,
146}
147
148/// Encode pixel data into a JPEG 2000 codestream.
149///
150/// # Arguments
151/// * `pixels` — Raw pixel data. For 8-bit: one byte per sample. For >8-bit: two bytes per sample (little-endian u16).
152/// * `width` — Image width in pixels.
153/// * `height` — Image height in pixels.
154/// * `num_components` — Number of components (1 for grayscale, 3 for RGB).
155/// * `bit_depth` — Bits per sample (e.g., 8, 12, 16).
156/// * `signed` — Whether samples are signed.
157/// * `options` — Encoding parameters.
158///
159/// # Returns
160/// The encoded JPEG 2000 codestream bytes (`.j2c` format).
161pub fn encode(
162    pixels: &[u8],
163    width: u32,
164    height: u32,
165    num_components: u8,
166    bit_depth: u8,
167    signed: bool,
168    options: &EncodeOptions,
169) -> Result<Vec<u8>, &'static str> {
170    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
171    encode_with_accelerator(
172        pixels,
173        width,
174        height,
175        num_components,
176        bit_depth,
177        signed,
178        options,
179        &mut accelerator,
180    )
181}
182
183/// Encode pixel data into a JPEG 2000 codestream using optional encode-stage hooks.
184///
185/// Stage hooks may accelerate forward RCT, forward 5/3 DWT, Tier-1 code-block
186/// encode, and packetization. Returning fallback from a hook preserves the CPU
187/// baseline for that stage.
188pub fn encode_with_accelerator(
189    pixels: &[u8],
190    width: u32,
191    height: u32,
192    num_components: u8,
193    bit_depth: u8,
194    signed: bool,
195    options: &EncodeOptions,
196    accelerator: &mut impl J2kEncodeStageAccelerator,
197) -> Result<Vec<u8>, &'static str> {
198    let block_coding_mode = block_coding_mode(options);
199    let codestream = encode_impl(
200        pixels,
201        width,
202        height,
203        num_components,
204        bit_depth,
205        signed,
206        options,
207        block_coding_mode,
208        accelerator,
209    )?;
210
211    if block_coding_mode == BlockCodingMode::HighThroughput
212        && options.validate_high_throughput_codestream
213    {
214        validate_htj2k_codestream(
215            &codestream,
216            pixels,
217            width,
218            height,
219            num_components,
220            bit_depth,
221            signed,
222            options.reversible,
223        )?;
224    }
225
226    Ok(codestream)
227}
228
229/// Encode pixel data into an HTJ2K codestream.
230///
231/// Lossless HTJ2K output is self-validated before it is returned.
232pub fn encode_htj2k(
233    pixels: &[u8],
234    width: u32,
235    height: u32,
236    num_components: u8,
237    bit_depth: u8,
238    signed: bool,
239    options: &EncodeOptions,
240) -> Result<Vec<u8>, &'static str> {
241    let mut options = options.clone();
242    options.use_ht_block_coding = true;
243    encode(
244        pixels,
245        width,
246        height,
247        num_components,
248        bit_depth,
249        signed,
250        &options,
251    )
252}
253
254/// Encode precomputed reversible 5/3 wavelet coefficients into an HTJ2K
255/// codestream.
256///
257/// This experimental entry point reuses the existing quantization, HT block
258/// coding, packetization, and codestream writer stages. It bypasses the
259/// encoder's forward DWT stage by supplying precomputed DWT output through the
260/// internal stage hook. Coefficients are expected in the same sample domain as
261/// the native encoder's FDWT input: unsigned components are already level
262/// shifted by subtracting `2^(bit_depth - 1)`.
263pub fn encode_precomputed_htj2k_53(
264    image: &PrecomputedHtj2k53Image,
265    options: &EncodeOptions,
266) -> Result<Vec<u8>, &'static str> {
267    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
268    encode_precomputed_htj2k_53_with_mct_and_accelerator(image, options, false, &mut accelerator)
269}
270
271/// Encode precomputed reversible 5/3 wavelet coefficients into an HTJ2K
272/// codestream using optional block encode and packetization hooks.
273pub fn encode_precomputed_htj2k_53_with_accelerator(
274    image: &PrecomputedHtj2k53Image,
275    options: &EncodeOptions,
276    accelerator: &mut impl J2kEncodeStageAccelerator,
277) -> Result<Vec<u8>, &'static str> {
278    encode_precomputed_htj2k_53_with_mct_and_accelerator(image, options, false, accelerator)
279}
280
281/// Encode precomputed reversible 5/3 wavelet coefficients into an HTJ2K
282/// codestream while controlling the output COD multi-component transform flag.
283///
284/// This is intended for coefficient-domain JPEG 2000 family recoding, where
285/// source codestream components may already be reversible-color-transformed.
286pub fn encode_precomputed_htj2k_53_with_mct(
287    image: &PrecomputedHtj2k53Image,
288    options: &EncodeOptions,
289    use_mct: bool,
290) -> Result<Vec<u8>, &'static str> {
291    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
292    encode_precomputed_htj2k_53_with_mct_and_accelerator(image, options, use_mct, &mut accelerator)
293}
294
295/// Encode precomputed reversible 5/3 wavelet coefficients while controlling
296/// the output COD multi-component transform flag and using optional encode
297/// stage hooks.
298pub fn encode_precomputed_htj2k_53_with_mct_and_accelerator(
299    image: &PrecomputedHtj2k53Image,
300    options: &EncodeOptions,
301    use_mct: bool,
302    accelerator: &mut impl J2kEncodeStageAccelerator,
303) -> Result<Vec<u8>, &'static str> {
304    if image.width == 0 || image.height == 0 {
305        return Err("invalid dimensions");
306    }
307    if image.components.is_empty() || image.components.len() > 4 {
308        return Err("unsupported component count");
309    }
310    if image.bit_depth == 0 || image.bit_depth > 16 {
311        return Err("unsupported bit depth");
312    }
313    if image
314        .components
315        .iter()
316        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
317    {
318        return Err("component sampling factors must be non-zero");
319    }
320    validate_precomputed_dwt_geometry(image)?;
321
322    let num_components =
323        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
324    let num_levels = precomputed_level_count(&image.components)?;
325    let mut precomputed_options = options.clone();
326    precomputed_options.num_decomposition_levels = num_levels;
327    precomputed_options.reversible = true;
328    precomputed_options.use_ht_block_coding = true;
329    precomputed_options.use_mct = use_mct;
330    precomputed_options.validate_high_throughput_codestream = false;
331    precomputed_options.component_sampling = Some(
332        image
333            .components
334            .iter()
335            .map(|component| (component.x_rsiz, component.y_rsiz))
336            .collect(),
337    );
338
339    let dummy_pixels =
340        zero_pixel_buffer(image.width, image.height, num_components, image.bit_depth)?;
341    let mut precomputed_accelerator = PrecomputedDwtAccelerator {
342        outputs: image
343            .components
344            .iter()
345            .map(|component| component.dwt.clone())
346            .collect(),
347        encode_accelerator: accelerator,
348    };
349
350    encode_with_accelerator(
351        &dummy_pixels,
352        image.width,
353        image.height,
354        num_components,
355        image.bit_depth,
356        image.signed,
357        &precomputed_options,
358        &mut precomputed_accelerator,
359    )
360}
361
362/// Encode precomputed irreversible 9/7 wavelet coefficients into an HTJ2K
363/// codestream.
364///
365/// This experimental entry point is the lossy counterpart of
366/// [`encode_precomputed_htj2k_53`]. It bypasses the encoder's forward 9/7 DWT
367/// stage by supplying precomputed floating-point DWT output through the
368/// internal stage hook. Coefficients are expected in the same sample domain as
369/// the native irreversible FDWT input: unsigned components are already level
370/// shifted by subtracting `2^(bit_depth - 1)`.
371pub fn encode_precomputed_htj2k_97(
372    image: &PrecomputedHtj2k97Image,
373    options: &EncodeOptions,
374) -> Result<Vec<u8>, &'static str> {
375    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
376    encode_precomputed_htj2k_97_with_accelerator(image, options, &mut accelerator)
377}
378
379/// Encode precomputed irreversible 9/7 wavelet coefficients into an HTJ2K
380/// codestream using optional block encode and packetization hooks.
381pub fn encode_precomputed_htj2k_97_with_accelerator(
382    image: &PrecomputedHtj2k97Image,
383    options: &EncodeOptions,
384    accelerator: &mut impl J2kEncodeStageAccelerator,
385) -> Result<Vec<u8>, &'static str> {
386    if image.width == 0 || image.height == 0 {
387        return Err("invalid dimensions");
388    }
389    if image.components.is_empty() || image.components.len() > 4 {
390        return Err("unsupported component count");
391    }
392    if image.bit_depth == 0 || image.bit_depth > 16 {
393        return Err("unsupported bit depth");
394    }
395    validate_irreversible_quantization_profile(options)?;
396    if image
397        .components
398        .iter()
399        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
400    {
401        return Err("component sampling factors must be non-zero");
402    }
403    validate_precomputed_dwt97_geometry(image)?;
404
405    let num_components =
406        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
407    let num_levels = precomputed_97_level_count(&image.components)?;
408    let mut precomputed_options = options.clone();
409    precomputed_options.num_decomposition_levels = num_levels;
410    precomputed_options.reversible = false;
411    precomputed_options.use_ht_block_coding = true;
412    precomputed_options.use_mct = false;
413    precomputed_options.validate_high_throughput_codestream = false;
414    precomputed_options.component_sampling = Some(
415        image
416            .components
417            .iter()
418            .map(|component| (component.x_rsiz, component.y_rsiz))
419            .collect(),
420    );
421
422    let dummy_pixels =
423        zero_pixel_buffer(image.width, image.height, num_components, image.bit_depth)?;
424    let mut precomputed_accelerator = PrecomputedDwt97Accelerator {
425        outputs: image
426            .components
427            .iter()
428            .map(|component| component.dwt.clone())
429            .collect(),
430        encode_accelerator: accelerator,
431    };
432
433    encode_with_accelerator(
434        &dummy_pixels,
435        image.width,
436        image.height,
437        num_components,
438        image.bit_depth,
439        image.signed,
440        &precomputed_options,
441        &mut precomputed_accelerator,
442    )
443}
444
445/// Encode multiple precomputed irreversible 9/7 wavelet images while sharing
446/// one HT code-block batch across all prepared tiles.
447pub fn encode_precomputed_htj2k_97_batch_with_accelerator(
448    images: &[PrecomputedHtj2k97Image],
449    options: &EncodeOptions,
450    accelerator: &mut impl J2kEncodeStageAccelerator,
451) -> Result<Vec<Vec<u8>>, &'static str> {
452    if images.is_empty() {
453        return Ok(Vec::new());
454    }
455    if options.num_layers != 1 {
456        return Err("batch precomputed 9/7 encode currently supports one quality layer");
457    }
458
459    let mut prepared_images = prepare_precomputed_htj2k97_images_for_batch(images, options)?;
460    let mut all_packets = Vec::new();
461    for prepared in &mut prepared_images {
462        prepared.packet_count = prepared.prepared_packets.len();
463        all_packets.append(&mut prepared.prepared_packets);
464    }
465
466    let mut encoded_packets =
467        encode_prepared_resolution_packets(all_packets, accelerator)?.into_iter();
468    let mut codestreams = Vec::with_capacity(prepared_images.len());
469    for prepared in prepared_images {
470        let mut resolution_packets = Vec::with_capacity(prepared.packet_count);
471        for _ in 0..prepared.packet_count {
472            resolution_packets.push(
473                encoded_packets
474                    .next()
475                    .ok_or("encoded packet count mismatch")?,
476            );
477        }
478        let scalar_packet_descriptors = scalar_packet_descriptors(&prepared.packet_descriptors);
479        let packetized_tile =
480            packet_encode::form_tile_bitstream_with_descriptors_lengths_and_markers(
481                &mut resolution_packets,
482                &scalar_packet_descriptors,
483                packet_encode::PacketMarkerOptions {
484                    write_sop: prepared.params.write_sop,
485                    write_eph: prepared.params.write_eph,
486                },
487            )?;
488        codestreams.push(codestream_write::write_codestream_with_packet_lengths(
489            &prepared.params,
490            &packetized_tile.data,
491            &prepared.quant_params,
492            &packetized_tile.packet_lengths,
493        ));
494    }
495    if encoded_packets.next().is_some() {
496        return Err("encoded packet count mismatch");
497    }
498
499    Ok(codestreams)
500}
501
502#[cfg(feature = "parallel")]
503fn prepare_precomputed_htj2k97_images_for_batch(
504    images: &[PrecomputedHtj2k97Image],
505    options: &EncodeOptions,
506) -> Result<Vec<PreparedPrecomputedHtj2k97Image>, &'static str> {
507    images
508        .par_iter()
509        .map(|image| prepare_precomputed_htj2k97_image_for_batch(image, options))
510        .collect()
511}
512
513#[cfg(not(feature = "parallel"))]
514fn prepare_precomputed_htj2k97_images_for_batch(
515    images: &[PrecomputedHtj2k97Image],
516    options: &EncodeOptions,
517) -> Result<Vec<PreparedPrecomputedHtj2k97Image>, &'static str> {
518    images
519        .iter()
520        .map(|image| prepare_precomputed_htj2k97_image_for_batch(image, options))
521        .collect()
522}
523
524/// Encode prequantized irreversible 9/7 code-block coefficients into an HTJ2K
525/// codestream.
526pub fn encode_prequantized_htj2k_97(
527    image: &PrequantizedHtj2k97Image,
528    options: &EncodeOptions,
529) -> Result<Vec<u8>, &'static str> {
530    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
531    encode_prequantized_htj2k_97_with_accelerator(image, options, &mut accelerator)
532}
533
534/// Encode prequantized irreversible 9/7 code-block coefficients into an HTJ2K
535/// codestream using optional block encode and packetization hooks.
536pub fn encode_prequantized_htj2k_97_with_accelerator(
537    image: &PrequantizedHtj2k97Image,
538    options: &EncodeOptions,
539    accelerator: &mut impl J2kEncodeStageAccelerator,
540) -> Result<Vec<u8>, &'static str> {
541    if image.width == 0 || image.height == 0 {
542        return Err("invalid dimensions");
543    }
544    if image.components.is_empty() || image.components.len() > 4 {
545        return Err("unsupported component count");
546    }
547    if image.bit_depth == 0 || image.bit_depth > 16 {
548        return Err("unsupported bit depth");
549    }
550    validate_irreversible_quantization_profile(options)?;
551    if image
552        .components
553        .iter()
554        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
555    {
556        return Err("component sampling factors must be non-zero");
557    }
558
559    let num_components =
560        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
561    let num_levels = prequantized_97_level_count(&image.components)?;
562    let guard_bits = options.guard_bits.max(2);
563    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
564        image.bit_depth,
565        num_levels,
566        false,
567        guard_bits,
568        options.irreversible_quantization_scale,
569        options.irreversible_quantization_subband_scales,
570    );
571    validate_prequantized_htj2k97_image(image, guard_bits, &step_sizes)?;
572
573    let mut prequantized_options = options.clone();
574    prequantized_options.num_decomposition_levels = num_levels;
575    prequantized_options.reversible = false;
576    prequantized_options.use_ht_block_coding = true;
577    prequantized_options.use_mct = false;
578    prequantized_options.validate_high_throughput_codestream = false;
579    prequantized_options.component_sampling = Some(
580        image
581            .components
582            .iter()
583            .map(|component| (component.x_rsiz, component.y_rsiz))
584            .collect(),
585    );
586
587    let component_resolution_packets = image
588        .components
589        .iter()
590        .enumerate()
591        .map(|(component_idx, component)| {
592            prepared_resolution_packets_from_prequantized_component(component_idx, component)
593        })
594        .collect::<Result<Vec<_>, &'static str>>()?;
595    let prepared_resolution_packets =
596        ordered_prepared_resolution_packets(component_resolution_packets, &prequantized_options)?;
597    let packet_descriptors = packet_descriptors_for_order(
598        &prepared_resolution_packets,
599        1,
600        prequantized_options.progression_order,
601    )?;
602    let mut resolution_packets =
603        encode_prepared_resolution_packets(prepared_resolution_packets, accelerator)?;
604    let packetization_resolutions = public_packetization_resolutions(&resolution_packets);
605    let packetization_job = J2kPacketizationEncodeJob {
606        resolution_count: resolution_packets.len() as u32,
607        num_layers: 1,
608        num_components,
609        code_block_count: count_code_blocks(&resolution_packets)?,
610        progression_order: public_packetization_progression_order(
611            prequantized_options.progression_order,
612        ),
613        packet_descriptors: &packet_descriptors,
614        resolutions: &packetization_resolutions,
615    };
616    let tile_data = accelerator
617        .encode_packetization(packetization_job)?
618        .unwrap_or_else(|| {
619            packet_encode::form_tile_bitstream(&mut resolution_packets, 1, num_components)
620        });
621
622    let quant_params: Vec<(u16, u16)> = step_sizes
623        .iter()
624        .map(|s| (s.exponent, s.mantissa))
625        .collect();
626    let params = EncodeParams {
627        width: image.width,
628        height: image.height,
629        tile_width: image.width,
630        tile_height: image.height,
631        num_components,
632        bit_depth: image.bit_depth,
633        signed: image.signed,
634        num_decomposition_levels: num_levels,
635        reversible: false,
636        code_block_width_exp: prequantized_options.code_block_width_exp,
637        code_block_height_exp: prequantized_options.code_block_height_exp,
638        num_layers: 1,
639        use_mct: false,
640        guard_bits,
641        block_coding_mode: BlockCodingMode::HighThroughput,
642        progression_order: prequantized_options.progression_order,
643        write_tlm: prequantized_options.write_tlm,
644        write_plt: prequantized_options.write_plt,
645        write_plm: prequantized_options.write_plm,
646        write_sop: prequantized_options.write_sop,
647        write_eph: prequantized_options.write_eph,
648        terminate_coding_passes: false,
649        component_sampling: prequantized_options
650            .component_sampling
651            .clone()
652            .ok_or("component sampling missing")?,
653        precinct_exponents: precinct_exponents_for_options(&prequantized_options, num_levels)?,
654    };
655
656    Ok(codestream_write::write_codestream(
657        &params,
658        &tile_data,
659        &quant_params,
660    ))
661}
662
663/// Encode preencoded irreversible 9/7 HTJ2K code-block payloads into a
664/// codestream.
665pub fn encode_preencoded_htj2k_97(
666    image: &PreencodedHtj2k97Image,
667    options: &EncodeOptions,
668) -> Result<Vec<u8>, &'static str> {
669    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
670    encode_preencoded_htj2k_97_with_accelerator(image, options, &mut accelerator)
671}
672
673/// Encode preencoded irreversible 9/7 HTJ2K code-block payloads into a
674/// codestream using optional packetization hooks.
675pub fn encode_preencoded_htj2k_97_with_accelerator(
676    image: &PreencodedHtj2k97Image,
677    options: &EncodeOptions,
678    accelerator: &mut impl J2kEncodeStageAccelerator,
679) -> Result<Vec<u8>, &'static str> {
680    if image.width == 0 || image.height == 0 {
681        return Err("invalid dimensions");
682    }
683    if image.components.is_empty() || image.components.len() > 4 {
684        return Err("unsupported component count");
685    }
686    if image.bit_depth == 0 || image.bit_depth > 16 {
687        return Err("unsupported bit depth");
688    }
689    validate_irreversible_quantization_profile(options)?;
690    if image
691        .components
692        .iter()
693        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
694    {
695        return Err("component sampling factors must be non-zero");
696    }
697
698    let num_components =
699        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
700    let num_levels = preencoded_97_level_count(&image.components)?;
701    let guard_bits = options.guard_bits.max(2);
702    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
703        image.bit_depth,
704        num_levels,
705        false,
706        guard_bits,
707        options.irreversible_quantization_scale,
708        options.irreversible_quantization_subband_scales,
709    );
710    validate_preencoded_htj2k97_image(image, guard_bits, &step_sizes)?;
711
712    let mut preencoded_options = options.clone();
713    preencoded_options.num_decomposition_levels = num_levels;
714    preencoded_options.reversible = false;
715    preencoded_options.use_ht_block_coding = true;
716    preencoded_options.use_mct = false;
717    preencoded_options.validate_high_throughput_codestream = false;
718    preencoded_options.component_sampling = Some(
719        image
720            .components
721            .iter()
722            .map(|component| (component.x_rsiz, component.y_rsiz))
723            .collect(),
724    );
725
726    let component_resolution_packets = image
727        .components
728        .iter()
729        .enumerate()
730        .map(|(component_idx, component)| {
731            prepared_resolution_packets_from_preencoded_component(component_idx, component)
732        })
733        .collect::<Result<Vec<_>, &'static str>>()?;
734    let prepared_resolution_packets =
735        ordered_prepared_resolution_packets(component_resolution_packets, &preencoded_options)?;
736    let packet_descriptors = packet_descriptors_for_order(
737        &prepared_resolution_packets,
738        1,
739        preencoded_options.progression_order,
740    )?;
741    let mut resolution_packets =
742        encode_prepared_resolution_packets(prepared_resolution_packets, accelerator)?;
743    let packetization_resolutions = public_packetization_resolutions(&resolution_packets);
744    let packetization_job = J2kPacketizationEncodeJob {
745        resolution_count: resolution_packets.len() as u32,
746        num_layers: 1,
747        num_components,
748        code_block_count: count_code_blocks(&resolution_packets)?,
749        progression_order: public_packetization_progression_order(
750            preencoded_options.progression_order,
751        ),
752        packet_descriptors: &packet_descriptors,
753        resolutions: &packetization_resolutions,
754    };
755    let tile_data = accelerator
756        .encode_packetization(packetization_job)?
757        .unwrap_or_else(|| {
758            packet_encode::form_tile_bitstream(&mut resolution_packets, 1, num_components)
759        });
760
761    let quant_params: Vec<(u16, u16)> = step_sizes
762        .iter()
763        .map(|s| (s.exponent, s.mantissa))
764        .collect();
765    let params = EncodeParams {
766        width: image.width,
767        height: image.height,
768        tile_width: image.width,
769        tile_height: image.height,
770        num_components,
771        bit_depth: image.bit_depth,
772        signed: image.signed,
773        num_decomposition_levels: num_levels,
774        reversible: false,
775        code_block_width_exp: preencoded_options.code_block_width_exp,
776        code_block_height_exp: preencoded_options.code_block_height_exp,
777        num_layers: 1,
778        use_mct: false,
779        guard_bits,
780        block_coding_mode: BlockCodingMode::HighThroughput,
781        progression_order: preencoded_options.progression_order,
782        write_tlm: preencoded_options.write_tlm,
783        write_plt: preencoded_options.write_plt,
784        write_plm: preencoded_options.write_plm,
785        write_sop: preencoded_options.write_sop,
786        write_eph: preencoded_options.write_eph,
787        terminate_coding_passes: false,
788        component_sampling: preencoded_options
789            .component_sampling
790            .clone()
791            .ok_or("component sampling missing")?,
792        precinct_exponents: precinct_exponents_for_options(&preencoded_options, num_levels)?,
793    };
794
795    Ok(codestream_write::write_codestream(
796        &params,
797        &tile_data,
798        &quant_params,
799    ))
800}
801
802/// Encode preencoded irreversible 9/7 HTJ2K code-block payloads into a
803/// codestream, consuming the image so code-block payloads can move into packet
804/// preparation without cloning.
805pub fn encode_preencoded_htj2k_97_owned_with_accelerator(
806    image: PreencodedHtj2k97Image,
807    options: &EncodeOptions,
808    accelerator: &mut impl J2kEncodeStageAccelerator,
809) -> Result<Vec<u8>, &'static str> {
810    if image.width == 0 || image.height == 0 {
811        return Err("invalid dimensions");
812    }
813    if image.components.is_empty() || image.components.len() > 4 {
814        return Err("unsupported component count");
815    }
816    if image.bit_depth == 0 || image.bit_depth > 16 {
817        return Err("unsupported bit depth");
818    }
819    validate_irreversible_quantization_profile(options)?;
820    if image
821        .components
822        .iter()
823        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
824    {
825        return Err("component sampling factors must be non-zero");
826    }
827
828    let width = image.width;
829    let height = image.height;
830    let bit_depth = image.bit_depth;
831    let signed = image.signed;
832    let num_components =
833        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
834    let num_levels = preencoded_97_level_count(&image.components)?;
835    let guard_bits = options.guard_bits.max(2);
836    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
837        bit_depth,
838        num_levels,
839        false,
840        guard_bits,
841        options.irreversible_quantization_scale,
842        options.irreversible_quantization_subband_scales,
843    );
844    validate_preencoded_htj2k97_image(&image, guard_bits, &step_sizes)?;
845
846    let component_sampling = image
847        .components
848        .iter()
849        .map(|component| (component.x_rsiz, component.y_rsiz))
850        .collect::<Vec<_>>();
851    let mut preencoded_options = options.clone();
852    preencoded_options.num_decomposition_levels = num_levels;
853    preencoded_options.reversible = false;
854    preencoded_options.use_ht_block_coding = true;
855    preencoded_options.use_mct = false;
856    preencoded_options.validate_high_throughput_codestream = false;
857    preencoded_options.component_sampling = Some(component_sampling);
858
859    let component_resolution_packets = image
860        .components
861        .into_iter()
862        .enumerate()
863        .map(|(component_idx, component)| {
864            prepared_resolution_packets_from_preencoded_component_owned(component_idx, component)
865        })
866        .collect::<Result<Vec<_>, &'static str>>()?;
867    let prepared_resolution_packets =
868        ordered_prepared_resolution_packets(component_resolution_packets, &preencoded_options)?;
869    let packet_descriptors = packet_descriptors_for_order(
870        &prepared_resolution_packets,
871        1,
872        preencoded_options.progression_order,
873    )?;
874    let mut resolution_packets =
875        encode_prepared_resolution_packets(prepared_resolution_packets, accelerator)?;
876    let packetization_resolutions = public_packetization_resolutions(&resolution_packets);
877    let packetization_job = J2kPacketizationEncodeJob {
878        resolution_count: resolution_packets.len() as u32,
879        num_layers: 1,
880        num_components,
881        code_block_count: count_code_blocks(&resolution_packets)?,
882        progression_order: public_packetization_progression_order(
883            preencoded_options.progression_order,
884        ),
885        packet_descriptors: &packet_descriptors,
886        resolutions: &packetization_resolutions,
887    };
888    let tile_data = accelerator
889        .encode_packetization(packetization_job)?
890        .unwrap_or_else(|| {
891            packet_encode::form_tile_bitstream(&mut resolution_packets, 1, num_components)
892        });
893
894    let quant_params: Vec<(u16, u16)> = step_sizes
895        .iter()
896        .map(|s| (s.exponent, s.mantissa))
897        .collect();
898    let params = EncodeParams {
899        width,
900        height,
901        tile_width: width,
902        tile_height: height,
903        num_components,
904        bit_depth,
905        signed,
906        num_decomposition_levels: num_levels,
907        reversible: false,
908        code_block_width_exp: preencoded_options.code_block_width_exp,
909        code_block_height_exp: preencoded_options.code_block_height_exp,
910        num_layers: 1,
911        use_mct: false,
912        guard_bits,
913        block_coding_mode: BlockCodingMode::HighThroughput,
914        progression_order: preencoded_options.progression_order,
915        write_tlm: preencoded_options.write_tlm,
916        write_plt: preencoded_options.write_plt,
917        write_plm: preencoded_options.write_plm,
918        write_sop: preencoded_options.write_sop,
919        write_eph: preencoded_options.write_eph,
920        terminate_coding_passes: false,
921        component_sampling: preencoded_options
922            .component_sampling
923            .clone()
924            .ok_or("component sampling missing")?,
925        precinct_exponents: precinct_exponents_for_options(&preencoded_options, num_levels)?,
926    };
927
928    Ok(codestream_write::write_codestream(
929        &params,
930        &tile_data,
931        &quant_params,
932    ))
933}
934
935/// Encode compact preencoded irreversible 9/7 HTJ2K code-block payloads into a
936/// codestream, borrowing code-block ranges from one image-level payload buffer
937/// during packetization.
938pub fn encode_preencoded_htj2k_97_compact_owned_with_accelerator(
939    image: PreencodedHtj2k97CompactImage,
940    options: &EncodeOptions,
941    accelerator: &mut impl J2kEncodeStageAccelerator,
942) -> Result<Vec<u8>, &'static str> {
943    if image.width == 0 || image.height == 0 {
944        return Err("invalid dimensions");
945    }
946    if image.components.is_empty() || image.components.len() > 4 {
947        return Err("unsupported component count");
948    }
949    if image.bit_depth == 0 || image.bit_depth > 16 {
950        return Err("unsupported bit depth");
951    }
952    validate_irreversible_quantization_profile(options)?;
953    if image
954        .components
955        .iter()
956        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
957    {
958        return Err("component sampling factors must be non-zero");
959    }
960
961    let width = image.width;
962    let height = image.height;
963    let bit_depth = image.bit_depth;
964    let signed = image.signed;
965    let num_components =
966        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
967    let num_levels = preencoded_compact_97_level_count(&image.components)?;
968    let guard_bits = options.guard_bits.max(2);
969    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
970        bit_depth,
971        num_levels,
972        false,
973        guard_bits,
974        options.irreversible_quantization_scale,
975        options.irreversible_quantization_subband_scales,
976    );
977    validate_preencoded_compact_htj2k97_image(&image, guard_bits, &step_sizes)?;
978
979    let component_sampling = image
980        .components
981        .iter()
982        .map(|component| (component.x_rsiz, component.y_rsiz))
983        .collect::<Vec<_>>();
984    let mut preencoded_options = options.clone();
985    preencoded_options.num_decomposition_levels = num_levels;
986    preencoded_options.reversible = false;
987    preencoded_options.use_ht_block_coding = true;
988    preencoded_options.use_mct = false;
989    preencoded_options.validate_high_throughput_codestream = false;
990    preencoded_options.component_sampling = Some(component_sampling);
991
992    let PreencodedHtj2k97CompactImage {
993        payload,
994        components,
995        ..
996    } = image;
997    let component_resolution_packets = components
998        .iter()
999        .enumerate()
1000        .map(|(component_idx, component)| {
1001            prepared_resolution_packets_from_preencoded_compact_component(
1002                component_idx,
1003                component,
1004                &payload,
1005            )
1006        })
1007        .collect::<Result<Vec<_>, &'static str>>()?;
1008    let prepared_resolution_packets = ordered_prepared_compact_resolution_packets(
1009        component_resolution_packets,
1010        &preencoded_options,
1011    )?;
1012    let packet_descriptors = packet_descriptors_for_compact_order(
1013        &prepared_resolution_packets,
1014        1,
1015        preencoded_options.progression_order,
1016    )?;
1017    let packetization_resolutions =
1018        public_packetization_resolutions_from_compact(&prepared_resolution_packets);
1019    let packetization_job = J2kPacketizationEncodeJob {
1020        resolution_count: packetization_resolutions.len() as u32,
1021        num_layers: 1,
1022        num_components,
1023        code_block_count: count_compact_code_blocks(&prepared_resolution_packets)?,
1024        progression_order: public_packetization_progression_order(
1025            preencoded_options.progression_order,
1026        ),
1027        packet_descriptors: &packet_descriptors,
1028        resolutions: &packetization_resolutions,
1029    };
1030    let tile_data = accelerator
1031        .encode_packetization(packetization_job)?
1032        .map_or_else(
1033            || crate::encode_j2k_packetization_scalar(packetization_job),
1034            Ok,
1035        )?;
1036
1037    let quant_params: Vec<(u16, u16)> = step_sizes
1038        .iter()
1039        .map(|s| (s.exponent, s.mantissa))
1040        .collect();
1041    let params = EncodeParams {
1042        width,
1043        height,
1044        tile_width: width,
1045        tile_height: height,
1046        num_components,
1047        bit_depth,
1048        signed,
1049        num_decomposition_levels: num_levels,
1050        reversible: false,
1051        code_block_width_exp: preencoded_options.code_block_width_exp,
1052        code_block_height_exp: preencoded_options.code_block_height_exp,
1053        num_layers: 1,
1054        use_mct: false,
1055        guard_bits,
1056        block_coding_mode: BlockCodingMode::HighThroughput,
1057        progression_order: preencoded_options.progression_order,
1058        write_tlm: preencoded_options.write_tlm,
1059        write_plt: preencoded_options.write_plt,
1060        write_plm: preencoded_options.write_plm,
1061        write_sop: preencoded_options.write_sop,
1062        write_eph: preencoded_options.write_eph,
1063        terminate_coding_passes: false,
1064        component_sampling: preencoded_options
1065            .component_sampling
1066            .clone()
1067            .ok_or("component sampling missing")?,
1068        precinct_exponents: precinct_exponents_for_options(&preencoded_options, num_levels)?,
1069    };
1070
1071    Ok(codestream_write::write_codestream(
1072        &params,
1073        &tile_data,
1074        &quant_params,
1075    ))
1076}
1077
1078fn validate_precomputed_dwt_geometry(image: &PrecomputedHtj2k53Image) -> Result<(), &'static str> {
1079    for component in &image.components {
1080        let component_width = image.width.div_ceil(u32::from(component.x_rsiz));
1081        let component_height = image.height.div_ceil(u32::from(component.y_rsiz));
1082        validate_precomputed_component_dwt_geometry(
1083            &component.dwt,
1084            component_width,
1085            component_height,
1086        )?;
1087    }
1088
1089    Ok(())
1090}
1091
1092fn validate_precomputed_dwt97_geometry(
1093    image: &PrecomputedHtj2k97Image,
1094) -> Result<(), &'static str> {
1095    for component in &image.components {
1096        let component_width = image.width.div_ceil(u32::from(component.x_rsiz));
1097        let component_height = image.height.div_ceil(u32::from(component.y_rsiz));
1098        validate_precomputed_component_dwt_geometry(
1099            &component.dwt,
1100            component_width,
1101            component_height,
1102        )?;
1103    }
1104
1105    Ok(())
1106}
1107
1108fn validate_precomputed_component_dwt_geometry(
1109    dwt: &impl PrecomputedDwtGeometryView,
1110    component_width: u32,
1111    component_height: u32,
1112) -> Result<(), &'static str> {
1113    if dwt.level_count() == 0 {
1114        return Err("precomputed DWT must contain at least one decomposition level");
1115    }
1116    if let Some(highest_level) = dwt.last_level_geometry() {
1117        if highest_level.width != component_width || highest_level.height != component_height {
1118            return Err("precomputed DWT component dimensions mismatch");
1119        }
1120    }
1121
1122    let mut expected_width = component_width;
1123    let mut expected_height = component_height;
1124    for level_index in (0..dwt.level_count()).rev() {
1125        let level = dwt.level_geometry(level_index);
1126        let low_width = expected_width.div_ceil(2);
1127        let low_height = expected_height.div_ceil(2);
1128        let high_width = expected_width / 2;
1129        let high_height = expected_height / 2;
1130
1131        if level.width != expected_width
1132            || level.height != expected_height
1133            || level.low_width != low_width
1134            || level.low_height != low_height
1135            || level.high_width != high_width
1136            || level.high_height != high_height
1137        {
1138            return Err("precomputed DWT recursive geometry mismatch");
1139        }
1140        validate_band_len(level.hl_len, high_width, low_height)?;
1141        validate_band_len(level.lh_len, low_width, high_height)?;
1142        validate_band_len(level.hh_len, high_width, high_height)?;
1143
1144        expected_width = low_width;
1145        expected_height = low_height;
1146    }
1147
1148    if dwt.ll_width() != expected_width || dwt.ll_height() != expected_height {
1149        return Err("precomputed DWT component dimensions mismatch");
1150    }
1151    validate_band_len(dwt.ll_len(), expected_width, expected_height)
1152}
1153
1154#[derive(Debug, Clone, Copy)]
1155struct PrecomputedDwtLevelGeometry {
1156    width: u32,
1157    height: u32,
1158    low_width: u32,
1159    low_height: u32,
1160    high_width: u32,
1161    high_height: u32,
1162    hl_len: usize,
1163    lh_len: usize,
1164    hh_len: usize,
1165}
1166
1167trait PrecomputedDwtGeometryView {
1168    fn ll_len(&self) -> usize;
1169    fn ll_width(&self) -> u32;
1170    fn ll_height(&self) -> u32;
1171    fn level_count(&self) -> usize;
1172    fn level_geometry(&self, index: usize) -> PrecomputedDwtLevelGeometry;
1173
1174    fn last_level_geometry(&self) -> Option<PrecomputedDwtLevelGeometry> {
1175        self.level_count()
1176            .checked_sub(1)
1177            .map(|index| self.level_geometry(index))
1178    }
1179}
1180
1181impl PrecomputedDwtGeometryView for J2kForwardDwt53Output {
1182    fn ll_len(&self) -> usize {
1183        self.ll.len()
1184    }
1185
1186    fn ll_width(&self) -> u32 {
1187        self.ll_width
1188    }
1189
1190    fn ll_height(&self) -> u32 {
1191        self.ll_height
1192    }
1193
1194    fn level_count(&self) -> usize {
1195        self.levels.len()
1196    }
1197
1198    fn level_geometry(&self, index: usize) -> PrecomputedDwtLevelGeometry {
1199        let level = &self.levels[index];
1200        PrecomputedDwtLevelGeometry {
1201            width: level.width,
1202            height: level.height,
1203            low_width: level.low_width,
1204            low_height: level.low_height,
1205            high_width: level.high_width,
1206            high_height: level.high_height,
1207            hl_len: level.hl.len(),
1208            lh_len: level.lh.len(),
1209            hh_len: level.hh.len(),
1210        }
1211    }
1212}
1213
1214impl PrecomputedDwtGeometryView for J2kForwardDwt97Output {
1215    fn ll_len(&self) -> usize {
1216        self.ll.len()
1217    }
1218
1219    fn ll_width(&self) -> u32 {
1220        self.ll_width
1221    }
1222
1223    fn ll_height(&self) -> u32 {
1224        self.ll_height
1225    }
1226
1227    fn level_count(&self) -> usize {
1228        self.levels.len()
1229    }
1230
1231    fn level_geometry(&self, index: usize) -> PrecomputedDwtLevelGeometry {
1232        let level = &self.levels[index];
1233        PrecomputedDwtLevelGeometry {
1234            width: level.width,
1235            height: level.height,
1236            low_width: level.low_width,
1237            low_height: level.low_height,
1238            high_width: level.high_width,
1239            high_height: level.high_height,
1240            hl_len: level.hl.len(),
1241            lh_len: level.lh.len(),
1242            hh_len: level.hh.len(),
1243        }
1244    }
1245}
1246
1247fn precomputed_level_count(components: &[PrecomputedHtj2k53Component]) -> Result<u8, &'static str> {
1248    let first = components
1249        .first()
1250        .ok_or("unsupported component count")?
1251        .dwt
1252        .levels
1253        .len();
1254    if components
1255        .iter()
1256        .any(|component| component.dwt.levels.len() != first)
1257    {
1258        return Err("precomputed components must use the same decomposition level count");
1259    }
1260    u8::try_from(first).map_err(|_| "decomposition level count exceeds u8")
1261}
1262
1263fn precomputed_97_level_count(
1264    components: &[PrecomputedHtj2k97Component],
1265) -> Result<u8, &'static str> {
1266    let first = components
1267        .first()
1268        .ok_or("unsupported component count")?
1269        .dwt
1270        .levels
1271        .len();
1272    if components
1273        .iter()
1274        .any(|component| component.dwt.levels.len() != first)
1275    {
1276        return Err("precomputed components must use the same decomposition level count");
1277    }
1278    u8::try_from(first).map_err(|_| "decomposition level count exceeds u8")
1279}
1280
1281fn prequantized_97_level_count(
1282    components: &[PrequantizedHtj2k97Component],
1283) -> Result<u8, &'static str> {
1284    let first = components
1285        .first()
1286        .ok_or("unsupported component count")?
1287        .resolutions
1288        .len()
1289        .checked_sub(1)
1290        .ok_or("prequantized components must contain at least one decomposition level")?;
1291    if components
1292        .iter()
1293        .any(|component| component.resolutions.len() != first + 1)
1294    {
1295        return Err("prequantized components must use the same decomposition level count");
1296    }
1297    u8::try_from(first).map_err(|_| "decomposition level count exceeds u8")
1298}
1299
1300fn preencoded_97_level_count(
1301    components: &[PreencodedHtj2k97Component],
1302) -> Result<u8, &'static str> {
1303    let first = components
1304        .first()
1305        .ok_or("unsupported component count")?
1306        .resolutions
1307        .len()
1308        .checked_sub(1)
1309        .ok_or("preencoded components must contain at least one decomposition level")?;
1310    if components
1311        .iter()
1312        .any(|component| component.resolutions.len() != first + 1)
1313    {
1314        return Err("preencoded components must use the same decomposition level count");
1315    }
1316    u8::try_from(first).map_err(|_| "decomposition level count exceeds u8")
1317}
1318
1319fn preencoded_compact_97_level_count(
1320    components: &[PreencodedHtj2k97CompactComponent],
1321) -> Result<u8, &'static str> {
1322    let first = components
1323        .first()
1324        .ok_or("unsupported component count")?
1325        .resolutions
1326        .len()
1327        .checked_sub(1)
1328        .ok_or("preencoded components must contain at least one decomposition level")?;
1329    if components
1330        .iter()
1331        .any(|component| component.resolutions.len() != first + 1)
1332    {
1333        return Err("preencoded components must use the same decomposition level count");
1334    }
1335    u8::try_from(first).map_err(|_| "decomposition level count exceeds u8")
1336}
1337
1338fn validate_prequantized_htj2k97_image(
1339    image: &PrequantizedHtj2k97Image,
1340    guard_bits: u8,
1341    step_sizes: &[QuantStepSize],
1342) -> Result<(), &'static str> {
1343    for component in &image.components {
1344        if component.resolutions.is_empty() {
1345            return Err("prequantized components must contain at least one resolution");
1346        }
1347        validate_prequantized_resolution(
1348            &component.resolutions[0],
1349            &[J2kSubBandType::LowLow],
1350            guard_bits,
1351            &step_sizes[0..1],
1352        )?;
1353        for (level_index, resolution) in component.resolutions.iter().enumerate().skip(1) {
1354            let step_base = 1 + (level_index - 1) * 3;
1355            validate_prequantized_resolution(
1356                resolution,
1357                &[
1358                    J2kSubBandType::HighLow,
1359                    J2kSubBandType::LowHigh,
1360                    J2kSubBandType::HighHigh,
1361                ],
1362                guard_bits,
1363                &step_sizes[step_base..step_base + 3],
1364            )?;
1365        }
1366    }
1367
1368    Ok(())
1369}
1370
1371fn validate_preencoded_htj2k97_image(
1372    image: &PreencodedHtj2k97Image,
1373    guard_bits: u8,
1374    step_sizes: &[QuantStepSize],
1375) -> Result<(), &'static str> {
1376    for component in &image.components {
1377        if component.resolutions.is_empty() {
1378            return Err("preencoded components must contain at least one resolution");
1379        }
1380        validate_preencoded_resolution(
1381            &component.resolutions[0],
1382            &[J2kSubBandType::LowLow],
1383            guard_bits,
1384            &step_sizes[0..1],
1385        )?;
1386        for (level_index, resolution) in component.resolutions.iter().enumerate().skip(1) {
1387            let step_base = 1 + (level_index - 1) * 3;
1388            validate_preencoded_resolution(
1389                resolution,
1390                &[
1391                    J2kSubBandType::HighLow,
1392                    J2kSubBandType::LowHigh,
1393                    J2kSubBandType::HighHigh,
1394                ],
1395                guard_bits,
1396                &step_sizes[step_base..step_base + 3],
1397            )?;
1398        }
1399    }
1400
1401    Ok(())
1402}
1403
1404fn validate_preencoded_compact_htj2k97_image(
1405    image: &PreencodedHtj2k97CompactImage,
1406    guard_bits: u8,
1407    step_sizes: &[QuantStepSize],
1408) -> Result<(), &'static str> {
1409    for component in &image.components {
1410        if component.resolutions.is_empty() {
1411            return Err("preencoded components must contain at least one resolution");
1412        }
1413        validate_preencoded_compact_resolution(
1414            &component.resolutions[0],
1415            &[J2kSubBandType::LowLow],
1416            guard_bits,
1417            &step_sizes[0..1],
1418            image.payload.len(),
1419        )?;
1420        for (level_index, resolution) in component.resolutions.iter().enumerate().skip(1) {
1421            let step_base = 1 + (level_index - 1) * 3;
1422            validate_preencoded_compact_resolution(
1423                resolution,
1424                &[
1425                    J2kSubBandType::HighLow,
1426                    J2kSubBandType::LowHigh,
1427                    J2kSubBandType::HighHigh,
1428                ],
1429                guard_bits,
1430                &step_sizes[step_base..step_base + 3],
1431                image.payload.len(),
1432            )?;
1433        }
1434    }
1435
1436    Ok(())
1437}
1438
1439fn validate_prequantized_resolution(
1440    resolution: &PrequantizedHtj2k97Resolution,
1441    expected_subbands: &[J2kSubBandType],
1442    guard_bits: u8,
1443    step_sizes: &[QuantStepSize],
1444) -> Result<(), &'static str> {
1445    if resolution.subbands.len() != expected_subbands.len() {
1446        return Err("prequantized resolution subband count mismatch");
1447    }
1448    for ((subband, expected_subband), step_size) in resolution
1449        .subbands
1450        .iter()
1451        .zip(expected_subbands)
1452        .zip(step_sizes)
1453    {
1454        if subband.sub_band_type != *expected_subband {
1455            return Err("prequantized resolution subband order mismatch");
1456        }
1457        let expected_blocks = subband
1458            .num_cbs_x
1459            .checked_mul(subband.num_cbs_y)
1460            .ok_or("prequantized code-block count overflow")?;
1461        if expected_blocks == 0 {
1462            if subband.total_bitplanes != 0 || !subband.code_blocks.is_empty() {
1463                return Err("empty prequantized subbands must not contain code-block data");
1464            }
1465            continue;
1466        }
1467        debug_assert!(step_size.exponent <= u16::from(u8::MAX));
1468        let expected_total_bitplanes = guard_bits
1469            .saturating_add(step_size.exponent as u8)
1470            .saturating_sub(1);
1471        if subband.total_bitplanes != expected_total_bitplanes {
1472            return Err("prequantized subband bitplane count mismatch");
1473        }
1474        if usize::try_from(expected_blocks).map_err(|_| "prequantized code-block count overflow")?
1475            != subband.code_blocks.len()
1476        {
1477            return Err("prequantized code-block count mismatch");
1478        }
1479        for block in &subband.code_blocks {
1480            if block.width == 0 || block.height == 0 {
1481                return Err("prequantized code-block dimensions must be non-zero");
1482            }
1483            validate_band_len(block.coefficients.len(), block.width, block.height)?;
1484        }
1485    }
1486
1487    Ok(())
1488}
1489
1490fn validate_preencoded_resolution(
1491    resolution: &PreencodedHtj2k97Resolution,
1492    expected_subbands: &[J2kSubBandType],
1493    guard_bits: u8,
1494    step_sizes: &[QuantStepSize],
1495) -> Result<(), &'static str> {
1496    if resolution.subbands.len() != expected_subbands.len() {
1497        return Err("preencoded resolution subband count mismatch");
1498    }
1499    for ((subband, expected_subband), step_size) in resolution
1500        .subbands
1501        .iter()
1502        .zip(expected_subbands)
1503        .zip(step_sizes)
1504    {
1505        if subband.sub_band_type != *expected_subband {
1506            return Err("preencoded resolution subband order mismatch");
1507        }
1508        let expected_blocks = subband
1509            .num_cbs_x
1510            .checked_mul(subband.num_cbs_y)
1511            .ok_or("preencoded code-block count overflow")?;
1512        if expected_blocks == 0 {
1513            if subband.total_bitplanes != 0 || !subband.code_blocks.is_empty() {
1514                return Err("empty preencoded subbands must not contain code-block data");
1515            }
1516            continue;
1517        }
1518        debug_assert!(step_size.exponent <= u16::from(u8::MAX));
1519        let expected_total_bitplanes = guard_bits
1520            .saturating_add(step_size.exponent as u8)
1521            .saturating_sub(1);
1522        if subband.total_bitplanes != expected_total_bitplanes {
1523            return Err("preencoded subband bitplane count mismatch");
1524        }
1525        if usize::try_from(expected_blocks).map_err(|_| "preencoded code-block count overflow")?
1526            != subband.code_blocks.len()
1527        {
1528            return Err("preencoded code-block count mismatch");
1529        }
1530        for block in &subband.code_blocks {
1531            if block.width == 0 || block.height == 0 {
1532                return Err("preencoded code-block dimensions must be non-zero");
1533            }
1534            validate_preencoded_code_block_payload(&block.encoded, subband.total_bitplanes)?;
1535        }
1536    }
1537
1538    Ok(())
1539}
1540
1541fn validate_preencoded_compact_resolution(
1542    resolution: &PreencodedHtj2k97CompactResolution,
1543    expected_subbands: &[J2kSubBandType],
1544    guard_bits: u8,
1545    step_sizes: &[QuantStepSize],
1546    payload_len: usize,
1547) -> Result<(), &'static str> {
1548    if resolution.subbands.len() != expected_subbands.len() {
1549        return Err("preencoded resolution subband count mismatch");
1550    }
1551    for ((subband, expected_subband), step_size) in resolution
1552        .subbands
1553        .iter()
1554        .zip(expected_subbands)
1555        .zip(step_sizes)
1556    {
1557        if subband.sub_band_type != *expected_subband {
1558            return Err("preencoded resolution subband order mismatch");
1559        }
1560        let expected_blocks = subband
1561            .num_cbs_x
1562            .checked_mul(subband.num_cbs_y)
1563            .ok_or("preencoded code-block count overflow")?;
1564        if expected_blocks == 0 {
1565            if subband.total_bitplanes != 0 || !subband.code_blocks.is_empty() {
1566                return Err("empty preencoded subbands must not contain code-block data");
1567            }
1568            continue;
1569        }
1570        debug_assert!(step_size.exponent <= u16::from(u8::MAX));
1571        let expected_total_bitplanes = guard_bits
1572            .saturating_add(step_size.exponent as u8)
1573            .saturating_sub(1);
1574        if subband.total_bitplanes != expected_total_bitplanes {
1575            return Err("preencoded subband bitplane count mismatch");
1576        }
1577        if usize::try_from(expected_blocks).map_err(|_| "preencoded code-block count overflow")?
1578            != subband.code_blocks.len()
1579        {
1580            return Err("preencoded code-block count mismatch");
1581        }
1582        for block in &subband.code_blocks {
1583            if block.width == 0 || block.height == 0 {
1584                return Err("preencoded code-block dimensions must be non-zero");
1585            }
1586            validate_preencoded_compact_code_block_payload(
1587                block,
1588                payload_len,
1589                subband.total_bitplanes,
1590            )?;
1591        }
1592    }
1593
1594    Ok(())
1595}
1596
1597fn validate_preencoded_code_block_payload(
1598    block: &EncodedHtJ2kCodeBlock,
1599    total_bitplanes: u8,
1600) -> Result<(), &'static str> {
1601    let data_len = u32::try_from(block.data.len()).map_err(|_| "HTJ2K payload too large")?;
1602    if block.num_coding_passes == 0 {
1603        if data_len != 0 || block.cleanup_length != 0 || block.refinement_length != 0 {
1604            return Err("empty HTJ2K code-block payload metadata mismatch");
1605        }
1606        if block.num_zero_bitplanes != total_bitplanes {
1607            return Err("empty HTJ2K code-block zero-bitplane count mismatch");
1608        }
1609        return Ok(());
1610    }
1611    if block.num_coding_passes > 164 {
1612        return Err("HTJ2K code-block coding pass count out of range");
1613    }
1614    if block.num_zero_bitplanes >= total_bitplanes {
1615        return Err("HTJ2K code-block zero-bitplane count out of range");
1616    }
1617    let segment_len = block
1618        .cleanup_length
1619        .checked_add(block.refinement_length)
1620        .ok_or("HTJ2K payload segment length overflow")?;
1621    if segment_len != data_len {
1622        return Err("HTJ2K payload segment length mismatch");
1623    }
1624    Ok(())
1625}
1626
1627fn validate_preencoded_compact_code_block_payload(
1628    block: &PreencodedHtj2k97CompactCodeBlock,
1629    payload_len: usize,
1630    total_bitplanes: u8,
1631) -> Result<(), &'static str> {
1632    if block.payload_range.start > block.payload_range.end || block.payload_range.end > payload_len
1633    {
1634        return Err("HTJ2K payload range out of bounds");
1635    }
1636    let data_len = u32::try_from(block.payload_range.end - block.payload_range.start)
1637        .map_err(|_| "HTJ2K payload too large")?;
1638    if block.num_coding_passes == 0 {
1639        if data_len != 0 || block.cleanup_length != 0 || block.refinement_length != 0 {
1640            return Err("empty HTJ2K code-block payload metadata mismatch");
1641        }
1642        if block.num_zero_bitplanes != total_bitplanes {
1643            return Err("empty HTJ2K code-block zero-bitplane count mismatch");
1644        }
1645        return Ok(());
1646    }
1647    if block.num_coding_passes > 164 {
1648        return Err("HTJ2K code-block coding pass count out of range");
1649    }
1650    if block.num_zero_bitplanes >= total_bitplanes {
1651        return Err("HTJ2K code-block zero-bitplane count out of range");
1652    }
1653    let segment_len = block
1654        .cleanup_length
1655        .checked_add(block.refinement_length)
1656        .ok_or("HTJ2K payload segment length overflow")?;
1657    if segment_len != data_len {
1658        return Err("HTJ2K payload segment length mismatch");
1659    }
1660    Ok(())
1661}
1662
1663fn prepared_resolution_packets_from_prequantized_component(
1664    component_idx: usize,
1665    component: &PrequantizedHtj2k97Component,
1666) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
1667    let component_idx = u8::try_from(component_idx).map_err(|_| "component index exceeds u8")?;
1668    component
1669        .resolutions
1670        .iter()
1671        .enumerate()
1672        .map(|(resolution_idx, resolution)| {
1673            Ok(PreparedResolutionPacket {
1674                component: component_idx,
1675                resolution: u32::try_from(resolution_idx)
1676                    .map_err(|_| "resolution index exceeds u32")?,
1677                precinct: 0,
1678                subbands: resolution
1679                    .subbands
1680                    .iter()
1681                    .map(prepared_subband_from_prequantized)
1682                    .collect::<Result<Vec<_>, &'static str>>()?,
1683            })
1684        })
1685        .collect()
1686}
1687
1688fn prepared_subband_from_prequantized(
1689    subband: &PrequantizedHtj2k97Subband,
1690) -> Result<PreparedEncodeSubband, &'static str> {
1691    Ok(PreparedEncodeSubband {
1692        code_blocks: subband
1693            .code_blocks
1694            .iter()
1695            .map(|block| PreparedEncodeCodeBlock {
1696                coefficients: block.coefficients.clone(),
1697                width: block.width,
1698                height: block.height,
1699            })
1700            .collect(),
1701        preencoded_ht_code_blocks: None,
1702        num_cbs_x: subband.num_cbs_x,
1703        num_cbs_y: subband.num_cbs_y,
1704        code_block_width: subband
1705            .code_blocks
1706            .iter()
1707            .map(|block| block.width)
1708            .max()
1709            .unwrap_or(0),
1710        code_block_height: subband
1711            .code_blocks
1712            .iter()
1713            .map(|block| block.height)
1714            .max()
1715            .unwrap_or(0),
1716        width: precomputed_subband_width(
1717            subband.num_cbs_x,
1718            subband.code_blocks.iter().map(|block| block.width),
1719        ),
1720        height: precomputed_subband_height(
1721            subband.num_cbs_x,
1722            subband.num_cbs_y,
1723            subband.code_blocks.iter().map(|block| block.height),
1724        ),
1725        sub_band_type: internal_sub_band_type(subband.sub_band_type),
1726        total_bitplanes: subband.total_bitplanes,
1727        block_coding_mode: BlockCodingMode::HighThroughput,
1728    })
1729}
1730
1731fn precomputed_subband_width(width_in_blocks: u32, widths: impl Iterator<Item = u32>) -> u32 {
1732    if width_in_blocks == 0 {
1733        return 0;
1734    }
1735
1736    widths.take(width_in_blocks as usize).sum()
1737}
1738
1739fn precomputed_subband_height(
1740    width_in_blocks: u32,
1741    height_in_blocks: u32,
1742    heights: impl Iterator<Item = u32>,
1743) -> u32 {
1744    if width_in_blocks == 0 || height_in_blocks == 0 {
1745        return 0;
1746    }
1747
1748    heights
1749        .step_by(width_in_blocks as usize)
1750        .take(height_in_blocks as usize)
1751        .sum()
1752}
1753
1754fn prepared_resolution_packets_from_preencoded_component(
1755    component_idx: usize,
1756    component: &PreencodedHtj2k97Component,
1757) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
1758    let component_idx = u8::try_from(component_idx).map_err(|_| "component index exceeds u8")?;
1759    component
1760        .resolutions
1761        .iter()
1762        .enumerate()
1763        .map(|(resolution_idx, resolution)| {
1764            Ok(PreparedResolutionPacket {
1765                component: component_idx,
1766                resolution: u32::try_from(resolution_idx)
1767                    .map_err(|_| "resolution index exceeds u32")?,
1768                precinct: 0,
1769                subbands: resolution
1770                    .subbands
1771                    .iter()
1772                    .map(prepared_subband_from_preencoded)
1773                    .collect::<Result<Vec<_>, &'static str>>()?,
1774            })
1775        })
1776        .collect()
1777}
1778
1779fn prepared_resolution_packets_from_preencoded_component_owned(
1780    component_idx: usize,
1781    component: PreencodedHtj2k97Component,
1782) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
1783    let component_idx = u8::try_from(component_idx).map_err(|_| "component index exceeds u8")?;
1784    component
1785        .resolutions
1786        .into_iter()
1787        .enumerate()
1788        .map(|(resolution_idx, resolution)| {
1789            Ok(PreparedResolutionPacket {
1790                component: component_idx,
1791                resolution: u32::try_from(resolution_idx)
1792                    .map_err(|_| "resolution index exceeds u32")?,
1793                precinct: 0,
1794                subbands: resolution
1795                    .subbands
1796                    .into_iter()
1797                    .map(prepared_subband_from_preencoded_owned)
1798                    .collect::<Result<Vec<_>, &'static str>>()?,
1799            })
1800        })
1801        .collect()
1802}
1803
1804fn prepared_resolution_packets_from_preencoded_compact_component<'a>(
1805    component_idx: usize,
1806    component: &'a PreencodedHtj2k97CompactComponent,
1807    payload: &'a [u8],
1808) -> Result<Vec<PreparedCompactResolutionPacket<'a>>, &'static str> {
1809    let component_idx = u8::try_from(component_idx).map_err(|_| "component index exceeds u8")?;
1810    component
1811        .resolutions
1812        .iter()
1813        .enumerate()
1814        .map(|(resolution_idx, resolution)| {
1815            Ok(PreparedCompactResolutionPacket {
1816                component: component_idx,
1817                resolution: u32::try_from(resolution_idx)
1818                    .map_err(|_| "resolution index exceeds u32")?,
1819                precinct: 0,
1820                subbands: resolution
1821                    .subbands
1822                    .iter()
1823                    .map(|subband| prepared_subband_from_preencoded_compact(subband, payload))
1824                    .collect::<Result<Vec<_>, &'static str>>()?,
1825            })
1826        })
1827        .collect()
1828}
1829
1830fn prepared_subband_from_preencoded(
1831    subband: &PreencodedHtj2k97Subband,
1832) -> Result<PreparedEncodeSubband, &'static str> {
1833    Ok(PreparedEncodeSubband {
1834        code_blocks: subband
1835            .code_blocks
1836            .iter()
1837            .map(|block| PreparedEncodeCodeBlock {
1838                coefficients: Vec::new(),
1839                width: block.width,
1840                height: block.height,
1841            })
1842            .collect(),
1843        preencoded_ht_code_blocks: Some(
1844            subband
1845                .code_blocks
1846                .iter()
1847                .map(|block| block.encoded.clone())
1848                .collect(),
1849        ),
1850        num_cbs_x: subband.num_cbs_x,
1851        num_cbs_y: subband.num_cbs_y,
1852        code_block_width: subband
1853            .code_blocks
1854            .iter()
1855            .map(|block| block.width)
1856            .max()
1857            .unwrap_or(0),
1858        code_block_height: subband
1859            .code_blocks
1860            .iter()
1861            .map(|block| block.height)
1862            .max()
1863            .unwrap_or(0),
1864        width: precomputed_subband_width(
1865            subband.num_cbs_x,
1866            subband.code_blocks.iter().map(|block| block.width),
1867        ),
1868        height: precomputed_subband_height(
1869            subband.num_cbs_x,
1870            subband.num_cbs_y,
1871            subband.code_blocks.iter().map(|block| block.height),
1872        ),
1873        sub_band_type: internal_sub_band_type(subband.sub_band_type),
1874        total_bitplanes: subband.total_bitplanes,
1875        block_coding_mode: BlockCodingMode::HighThroughput,
1876    })
1877}
1878
1879fn prepared_subband_from_preencoded_owned(
1880    subband: PreencodedHtj2k97Subband,
1881) -> Result<PreparedEncodeSubband, &'static str> {
1882    let code_block_width = subband
1883        .code_blocks
1884        .iter()
1885        .map(|block| block.width)
1886        .max()
1887        .unwrap_or(0);
1888    let code_block_height = subband
1889        .code_blocks
1890        .iter()
1891        .map(|block| block.height)
1892        .max()
1893        .unwrap_or(0);
1894    let width = precomputed_subband_width(
1895        subband.num_cbs_x,
1896        subband.code_blocks.iter().map(|block| block.width),
1897    );
1898    let height = precomputed_subband_height(
1899        subband.num_cbs_x,
1900        subband.num_cbs_y,
1901        subband.code_blocks.iter().map(|block| block.height),
1902    );
1903    let code_blocks = subband
1904        .code_blocks
1905        .into_iter()
1906        .map(|block| {
1907            let PreencodedHtj2k97CodeBlock {
1908                width,
1909                height,
1910                encoded,
1911            } = block;
1912            (
1913                PreparedEncodeCodeBlock {
1914                    coefficients: Vec::new(),
1915                    width,
1916                    height,
1917                },
1918                encoded,
1919            )
1920        })
1921        .collect::<Vec<_>>();
1922    let (code_blocks, preencoded_ht_code_blocks): (Vec<_>, Vec<_>) =
1923        code_blocks.into_iter().unzip();
1924
1925    Ok(PreparedEncodeSubband {
1926        code_blocks,
1927        preencoded_ht_code_blocks: Some(preencoded_ht_code_blocks),
1928        num_cbs_x: subband.num_cbs_x,
1929        num_cbs_y: subband.num_cbs_y,
1930        code_block_width,
1931        code_block_height,
1932        width,
1933        height,
1934        sub_band_type: internal_sub_band_type(subband.sub_band_type),
1935        total_bitplanes: subband.total_bitplanes,
1936        block_coding_mode: BlockCodingMode::HighThroughput,
1937    })
1938}
1939
1940fn prepared_subband_from_preencoded_compact<'a>(
1941    subband: &'a PreencodedHtj2k97CompactSubband,
1942    payload: &'a [u8],
1943) -> Result<PreparedCompactSubband<'a>, &'static str> {
1944    let code_blocks = subband
1945        .code_blocks
1946        .iter()
1947        .map(|block| {
1948            Ok(PreparedCompactCodeBlock {
1949                data: compact_payload_slice(payload, &block.payload_range)?,
1950                cleanup_length: block.cleanup_length,
1951                refinement_length: block.refinement_length,
1952                num_coding_passes: block.num_coding_passes,
1953                num_zero_bitplanes: block.num_zero_bitplanes,
1954            })
1955        })
1956        .collect::<Result<Vec<_>, &'static str>>()?;
1957
1958    Ok(PreparedCompactSubband {
1959        code_blocks,
1960        num_cbs_x: subband.num_cbs_x,
1961        num_cbs_y: subband.num_cbs_y,
1962    })
1963}
1964
1965fn compact_payload_slice<'a>(
1966    payload: &'a [u8],
1967    range: &Range<usize>,
1968) -> Result<&'a [u8], &'static str> {
1969    if range.start > range.end || range.end > payload.len() {
1970        return Err("HTJ2K payload range out of bounds");
1971    }
1972    Ok(&payload[range.clone()])
1973}
1974
1975fn zero_pixel_buffer(
1976    width: u32,
1977    height: u32,
1978    num_components: u8,
1979    bit_depth: u8,
1980) -> Result<Vec<u8>, &'static str> {
1981    let bytes_per_sample = if bit_depth <= 8 { 1usize } else { 2usize };
1982    let len = width as usize;
1983    let len = len
1984        .checked_mul(height as usize)
1985        .and_then(|value| value.checked_mul(usize::from(num_components)))
1986        .and_then(|value| value.checked_mul(bytes_per_sample))
1987        .ok_or("pixel buffer dimensions overflow")?;
1988    Ok(vec![0; len])
1989}
1990
1991struct PrecomputedDwtAccelerator<'a, A: J2kEncodeStageAccelerator> {
1992    outputs: Vec<J2kForwardDwt53Output>,
1993    encode_accelerator: &'a mut A,
1994}
1995
1996struct PrecomputedDwt97Accelerator<'a, A: J2kEncodeStageAccelerator> {
1997    outputs: Vec<J2kForwardDwt97Output>,
1998    encode_accelerator: &'a mut A,
1999}
2000
2001impl<A: J2kEncodeStageAccelerator> J2kEncodeStageAccelerator for PrecomputedDwtAccelerator<'_, A> {
2002    fn dispatch_report(&self) -> crate::J2kEncodeDispatchReport {
2003        self.encode_accelerator.dispatch_report()
2004    }
2005
2006    fn encode_forward_dwt53(
2007        &mut self,
2008        _job: J2kForwardDwt53Job<'_>,
2009    ) -> Result<Option<J2kForwardDwt53Output>, &'static str> {
2010        if self.outputs.is_empty() {
2011            return Err("precomputed DWT output exhausted");
2012        }
2013
2014        Ok(Some(self.outputs.remove(0)))
2015    }
2016
2017    fn encode_quantize_subband(
2018        &mut self,
2019        job: J2kQuantizeSubbandJob<'_>,
2020    ) -> Result<Option<Vec<i32>>, &'static str> {
2021        self.encode_accelerator.encode_quantize_subband(job)
2022    }
2023
2024    fn encode_tier1_code_block(
2025        &mut self,
2026        job: J2kTier1CodeBlockEncodeJob<'_>,
2027    ) -> Result<Option<EncodedJ2kCodeBlock>, &'static str> {
2028        self.encode_accelerator.encode_tier1_code_block(job)
2029    }
2030
2031    fn encode_tier1_code_blocks(
2032        &mut self,
2033        jobs: &[J2kTier1CodeBlockEncodeJob<'_>],
2034    ) -> Result<Option<Vec<EncodedJ2kCodeBlock>>, &'static str> {
2035        self.encode_accelerator.encode_tier1_code_blocks(jobs)
2036    }
2037
2038    fn encode_ht_code_block(
2039        &mut self,
2040        job: crate::J2kHtCodeBlockEncodeJob<'_>,
2041    ) -> Result<Option<EncodedHtJ2kCodeBlock>, &'static str> {
2042        self.encode_accelerator.encode_ht_code_block(job)
2043    }
2044
2045    fn encode_ht_code_blocks(
2046        &mut self,
2047        jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
2048    ) -> Result<Option<Vec<EncodedHtJ2kCodeBlock>>, &'static str> {
2049        self.encode_accelerator.encode_ht_code_blocks(jobs)
2050    }
2051
2052    fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
2053        self.encode_accelerator
2054            .prefer_parallel_cpu_code_block_fallback()
2055    }
2056
2057    fn prefer_parallel_cpu_tile_encode(&self) -> bool {
2058        self.encode_accelerator.prefer_parallel_cpu_tile_encode()
2059    }
2060
2061    fn encode_packetization(
2062        &mut self,
2063        job: J2kPacketizationEncodeJob<'_>,
2064    ) -> Result<Option<Vec<u8>>, &'static str> {
2065        self.encode_accelerator.encode_packetization(job)
2066    }
2067}
2068
2069impl<A: J2kEncodeStageAccelerator> J2kEncodeStageAccelerator
2070    for PrecomputedDwt97Accelerator<'_, A>
2071{
2072    fn dispatch_report(&self) -> crate::J2kEncodeDispatchReport {
2073        self.encode_accelerator.dispatch_report()
2074    }
2075
2076    fn encode_forward_dwt97(
2077        &mut self,
2078        _job: J2kForwardDwt97Job<'_>,
2079    ) -> Result<Option<J2kForwardDwt97Output>, &'static str> {
2080        if self.outputs.is_empty() {
2081            return Err("precomputed DWT output exhausted");
2082        }
2083
2084        Ok(Some(self.outputs.remove(0)))
2085    }
2086
2087    fn encode_quantize_subband(
2088        &mut self,
2089        job: J2kQuantizeSubbandJob<'_>,
2090    ) -> Result<Option<Vec<i32>>, &'static str> {
2091        self.encode_accelerator.encode_quantize_subband(job)
2092    }
2093
2094    fn encode_tier1_code_block(
2095        &mut self,
2096        job: J2kTier1CodeBlockEncodeJob<'_>,
2097    ) -> Result<Option<EncodedJ2kCodeBlock>, &'static str> {
2098        self.encode_accelerator.encode_tier1_code_block(job)
2099    }
2100
2101    fn encode_tier1_code_blocks(
2102        &mut self,
2103        jobs: &[J2kTier1CodeBlockEncodeJob<'_>],
2104    ) -> Result<Option<Vec<EncodedJ2kCodeBlock>>, &'static str> {
2105        self.encode_accelerator.encode_tier1_code_blocks(jobs)
2106    }
2107
2108    fn encode_ht_code_block(
2109        &mut self,
2110        job: crate::J2kHtCodeBlockEncodeJob<'_>,
2111    ) -> Result<Option<EncodedHtJ2kCodeBlock>, &'static str> {
2112        self.encode_accelerator.encode_ht_code_block(job)
2113    }
2114
2115    fn encode_ht_code_blocks(
2116        &mut self,
2117        jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
2118    ) -> Result<Option<Vec<EncodedHtJ2kCodeBlock>>, &'static str> {
2119        self.encode_accelerator.encode_ht_code_blocks(jobs)
2120    }
2121
2122    fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
2123        self.encode_accelerator
2124            .prefer_parallel_cpu_code_block_fallback()
2125    }
2126
2127    fn prefer_parallel_cpu_tile_encode(&self) -> bool {
2128        self.encode_accelerator.prefer_parallel_cpu_tile_encode()
2129    }
2130
2131    fn encode_packetization(
2132        &mut self,
2133        job: J2kPacketizationEncodeJob<'_>,
2134    ) -> Result<Option<Vec<u8>>, &'static str> {
2135        self.encode_accelerator.encode_packetization(job)
2136    }
2137}
2138
2139fn block_coding_mode(options: &EncodeOptions) -> BlockCodingMode {
2140    if options.use_ht_block_coding {
2141        BlockCodingMode::HighThroughput
2142    } else {
2143        BlockCodingMode::Classic
2144    }
2145}
2146
2147fn validate_irreversible_quantization_scale(scale: f32) -> Result<(), &'static str> {
2148    if scale.is_finite() && scale > 0.0 {
2149        Ok(())
2150    } else {
2151        Err("irreversible quantization scale must be finite and greater than zero")
2152    }
2153}
2154
2155fn validate_irreversible_quantization_profile(options: &EncodeOptions) -> Result<(), &'static str> {
2156    validate_irreversible_quantization_scale(options.irreversible_quantization_scale)?;
2157    if quantize::subband_scales_all_valid(options.irreversible_quantization_subband_scales) {
2158        Ok(())
2159    } else {
2160        Err("irreversible quantization subband scales must be finite and greater than zero")
2161    }
2162}
2163
2164fn validate_htj2k_codestream(
2165    codestream: &[u8],
2166    pixels: &[u8],
2167    width: u32,
2168    height: u32,
2169    num_components: u8,
2170    bit_depth: u8,
2171    signed: bool,
2172    reversible: bool,
2173) -> Result<(), &'static str> {
2174    let image = Image::new(codestream, &DecodeSettings::default())
2175        .map_err(|_| "generated HTJ2K codestream failed self-validation")?;
2176    let decoded = image
2177        .decode_native()
2178        .map_err(|_| "generated HTJ2K codestream failed self-validation")?;
2179
2180    if decoded.width != width
2181        || decoded.height != height
2182        || decoded.bit_depth != bit_depth
2183        || decoded.num_components != num_components
2184    {
2185        return Err("generated HTJ2K codestream failed self-validation");
2186    }
2187
2188    if reversible && !native_samples_equal(pixels, &decoded.data, bit_depth, signed) {
2189        return Err("generated HTJ2K codestream did not roundtrip");
2190    }
2191
2192    Ok(())
2193}
2194
2195fn native_samples_equal(expected: &[u8], actual: &[u8], bit_depth: u8, signed: bool) -> bool {
2196    if expected.len() != actual.len() {
2197        return false;
2198    }
2199
2200    let bytes_per_sample = if bit_depth <= 8 { 1 } else { 2 };
2201    let sample_count = expected.len() / bytes_per_sample;
2202    (0..sample_count).all(|sample_index| {
2203        decode_native_sample(expected, sample_index, bit_depth, signed)
2204            == decode_native_sample(actual, sample_index, bit_depth, signed)
2205    })
2206}
2207
2208fn decode_native_sample(bytes: &[u8], sample_index: usize, bit_depth: u8, signed: bool) -> i32 {
2209    let byte_offset = sample_index * if bit_depth <= 8 { 1 } else { 2 };
2210    let mask = (1u32 << u32::from(bit_depth)) - 1;
2211    let raw = if bit_depth <= 8 {
2212        u32::from(bytes[byte_offset])
2213    } else {
2214        u32::from(u16::from_le_bytes([
2215            bytes[byte_offset],
2216            bytes[byte_offset + 1],
2217        ]))
2218    } & mask;
2219
2220    if signed {
2221        let shift = 32 - u32::from(bit_depth);
2222        ((raw << shift) as i32) >> shift
2223    } else {
2224        raw as i32
2225    }
2226}
2227
2228fn encode_impl(
2229    pixels: &[u8],
2230    width: u32,
2231    height: u32,
2232    num_components: u8,
2233    bit_depth: u8,
2234    signed: bool,
2235    options: &EncodeOptions,
2236    block_coding_mode: BlockCodingMode,
2237    accelerator: &mut impl J2kEncodeStageAccelerator,
2238) -> Result<Vec<u8>, &'static str> {
2239    if width == 0 || height == 0 {
2240        return Err("invalid dimensions");
2241    }
2242    if num_components == 0 || num_components > 4 {
2243        return Err("unsupported component count");
2244    }
2245    if bit_depth == 0 || bit_depth > 16 {
2246        return Err("unsupported bit depth");
2247    }
2248    if options.num_layers == 0 || options.num_layers > 32 {
2249        return Err("unsupported quality layer count");
2250    }
2251    if !options.quality_layer_byte_targets.is_empty()
2252        && options.quality_layer_byte_targets.len() != usize::from(options.num_layers)
2253    {
2254        return Err("quality layer byte target count must match quality layer count");
2255    }
2256    if !options.reversible {
2257        validate_irreversible_quantization_profile(options)?;
2258    }
2259
2260    let num_pixels = (width as usize)
2261        .checked_mul(height as usize)
2262        .ok_or("image dimensions overflow")?;
2263    let bytes_per_sample = if bit_depth <= 8 { 1 } else { 2 };
2264    let expected_len = num_pixels
2265        .checked_mul(num_components as usize)
2266        .and_then(|len| len.checked_mul(bytes_per_sample))
2267        .ok_or("image dimensions overflow")?;
2268    if pixels.len() < expected_len {
2269        return Err("pixel data too short");
2270    }
2271    let component_sampling = component_sampling_for_options(options, num_components)?;
2272    if let Some((tile_width, tile_height)) = options.tile_size {
2273        if tile_width == 0 || tile_height == 0 {
2274            return Err("invalid tile dimensions");
2275        }
2276        if component_sampling
2277            .iter()
2278            .any(|sampling| *sampling != (1, 1))
2279        {
2280            return Err("multi-tile encode with component sampling is not implemented");
2281        }
2282        if tile_width < width || tile_height < height {
2283            return encode_multitile_impl(
2284                pixels,
2285                width,
2286                height,
2287                num_components,
2288                bit_depth,
2289                signed,
2290                options,
2291                block_coding_mode,
2292                accelerator,
2293                tile_width,
2294                tile_height,
2295            );
2296        }
2297    }
2298
2299    let profile_enabled = profile::profile_stages_enabled();
2300    let total_start = profile::profile_now(profile_enabled);
2301
2302    let use_mct = options.use_mct && num_components >= 3;
2303    let num_levels = options.num_decomposition_levels.min(
2304        // Don't decompose more than the image supports
2305        max_decomposition_levels(width, height),
2306    );
2307    let guard_bits = if options.reversible {
2308        if use_mct {
2309            options.guard_bits.max(2)
2310        } else {
2311            options.guard_bits
2312        }
2313    } else {
2314        options.guard_bits.max(2)
2315    };
2316    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
2317        bit_depth,
2318        num_levels,
2319        options.reversible,
2320        guard_bits,
2321        options.irreversible_quantization_scale,
2322        options.irreversible_quantization_subband_scales,
2323    );
2324    let quant_params: Vec<(u16, u16)> = step_sizes
2325        .iter()
2326        .map(|s| (s.exponent, s.mantissa))
2327        .collect();
2328    let cb_width = 1u32 << (options.code_block_width_exp + 2);
2329    let cb_height = 1u32 << (options.code_block_height_exp + 2);
2330    let precinct_exponents = precinct_exponents_for_options(options, num_levels)?;
2331    let params = EncodeParams {
2332        width,
2333        height,
2334        tile_width: options
2335            .tile_size
2336            .map_or(width, |(tile_width, _)| tile_width),
2337        tile_height: options
2338            .tile_size
2339            .map_or(height, |(_, tile_height)| tile_height),
2340        num_components,
2341        bit_depth,
2342        signed,
2343        num_decomposition_levels: num_levels,
2344        reversible: options.reversible,
2345        code_block_width_exp: options.code_block_width_exp,
2346        code_block_height_exp: options.code_block_height_exp,
2347        num_layers: options.num_layers,
2348        use_mct,
2349        guard_bits,
2350        block_coding_mode,
2351        progression_order: options.progression_order,
2352        write_tlm: options.write_tlm,
2353        write_plt: options.write_plt,
2354        write_plm: options.write_plm,
2355        write_sop: options.write_sop,
2356        write_eph: options.write_eph,
2357        terminate_coding_passes: block_coding_mode == BlockCodingMode::Classic
2358            && options.num_layers > 1,
2359        component_sampling,
2360        precinct_exponents,
2361    };
2362
2363    let stage_start = profile::profile_now(profile_enabled);
2364    if block_coding_mode == BlockCodingMode::HighThroughput
2365        && !(params.write_plt || params.write_plm || params.write_sop || params.write_eph)
2366    {
2367        if let Some(tile_data) = accelerator.encode_htj2k_tile(J2kHtj2kTileEncodeJob {
2368            pixels,
2369            width,
2370            height,
2371            num_components,
2372            bit_depth,
2373            signed,
2374            num_decomposition_levels: num_levels,
2375            reversible: options.reversible,
2376            use_mct,
2377            guard_bits,
2378            code_block_width: cb_width,
2379            code_block_height: cb_height,
2380            progression_order: public_packetization_progression_order(options.progression_order),
2381            component_sampling: &params.component_sampling,
2382            quantization_steps: &quant_params,
2383        })? {
2384            let tile_body_us = profile::elapsed_us(stage_start);
2385            let stage_start = profile::profile_now(profile_enabled);
2386            let codestream = codestream_write::write_codestream(&params, &tile_data, &quant_params);
2387            let codestream_us = profile::elapsed_us(stage_start);
2388            if profile_enabled {
2389                profile::emit_profile_row(
2390                    "encode",
2391                    "accelerated",
2392                    &[
2393                        ("tile_body_us", tile_body_us),
2394                        ("codestream_us", codestream_us),
2395                        ("total_us", profile::elapsed_us(total_start)),
2396                    ],
2397                );
2398            }
2399            return Ok(codestream);
2400        }
2401    }
2402
2403    // Step 1: Convert pixel bytes to f32 component arrays
2404    let stage_start = profile::profile_now(profile_enabled);
2405    let mut components = match accelerator.encode_deinterleave(J2kDeinterleaveToF32Job {
2406        pixels,
2407        num_pixels,
2408        num_components,
2409        bit_depth,
2410        signed,
2411    })? {
2412        Some(components) => {
2413            validate_deinterleaved_components(components, num_components, num_pixels)?
2414        }
2415        None => deinterleave_to_f32(pixels, num_pixels, num_components, bit_depth, signed),
2416    };
2417    let deinterleave_us = profile::elapsed_us(stage_start);
2418
2419    // Step 2: Apply forward MCT if RGB with 3+ components
2420    let stage_start = profile::profile_now(profile_enabled);
2421    if use_mct {
2422        if options.reversible {
2423            if !try_encode_forward_rct(&mut components, accelerator)? {
2424                forward_mct::forward_rct(&mut components);
2425            }
2426        } else if !try_encode_forward_ict(&mut components, accelerator)? {
2427            forward_mct::forward_ict(&mut components);
2428        }
2429    }
2430    let mct_us = profile::elapsed_us(stage_start);
2431
2432    // Step 3: Apply forward DWT to each component
2433    let stage_start = profile::profile_now(profile_enabled);
2434    let decompositions: Vec<DwtDecomposition> = components
2435        .iter()
2436        .map(|comp| {
2437            encode_forward_dwt(
2438                comp,
2439                width,
2440                height,
2441                num_levels,
2442                options.reversible,
2443                accelerator,
2444            )
2445        })
2446        .collect::<Result<Vec<_>, &'static str>>()?;
2447    let dwt_us = profile::elapsed_us(stage_start);
2448
2449    // Step 5: Quantize and encode code-blocks for each component
2450    let mut component_resolution_packets: Vec<Vec<PreparedResolutionPacket>> =
2451        Vec::with_capacity(num_components as usize);
2452
2453    let stage_start = profile::profile_now(profile_enabled);
2454    for (component_idx, decomp) in decompositions
2455        .iter()
2456        .take(num_components as usize)
2457        .enumerate()
2458    {
2459        let component = u8::try_from(component_idx).map_err(|_| "component index exceeds u8")?;
2460        let mut packets = Vec::with_capacity(num_levels as usize + 1);
2461
2462        // LL subband (resolution 0)
2463        let ll_subband = prepare_subband(
2464            &decomp.ll,
2465            decomp.ll_width,
2466            decomp.ll_height,
2467            &step_sizes[0],
2468            bit_depth,
2469            guard_bits,
2470            options.reversible,
2471            block_coding_mode,
2472            cb_width,
2473            cb_height,
2474            SubBandType::LowLow,
2475            accelerator,
2476        )?;
2477        packets.push(PreparedResolutionPacket {
2478            component,
2479            resolution: 0,
2480            precinct: 0,
2481            subbands: vec![ll_subband],
2482        });
2483
2484        // Higher resolution levels
2485        for (level_idx, level) in decomp.levels.iter().enumerate() {
2486            let step_base = 1 + level_idx * 3;
2487
2488            // HL subband
2489            let hl_subband = prepare_subband(
2490                &level.hl,
2491                level.high_width,
2492                level.low_height,
2493                &step_sizes[step_base],
2494                bit_depth,
2495                guard_bits,
2496                options.reversible,
2497                block_coding_mode,
2498                cb_width,
2499                cb_height,
2500                SubBandType::HighLow,
2501                accelerator,
2502            )?;
2503
2504            // LH subband
2505            let lh_subband = prepare_subband(
2506                &level.lh,
2507                level.low_width,
2508                level.high_height,
2509                &step_sizes[step_base + 1],
2510                bit_depth,
2511                guard_bits,
2512                options.reversible,
2513                block_coding_mode,
2514                cb_width,
2515                cb_height,
2516                SubBandType::LowHigh,
2517                accelerator,
2518            )?;
2519
2520            // HH subband
2521            let hh_subband = prepare_subband(
2522                &level.hh,
2523                level.high_width,
2524                level.high_height,
2525                &step_sizes[step_base + 2],
2526                bit_depth,
2527                guard_bits,
2528                options.reversible,
2529                block_coding_mode,
2530                cb_width,
2531                cb_height,
2532                SubBandType::HighHigh,
2533                accelerator,
2534            )?;
2535
2536            packets.push(PreparedResolutionPacket {
2537                component,
2538                resolution: u32::try_from(level_idx + 1)
2539                    .map_err(|_| "resolution index exceeds u32")?,
2540                precinct: 0,
2541                subbands: vec![hl_subband, lh_subband, hh_subband],
2542            });
2543        }
2544
2545        component_resolution_packets.push(packets);
2546    }
2547    let subband_prepare_us = profile::elapsed_us(stage_start);
2548
2549    let component_resolution_packets = split_component_resolution_packets_by_precinct(
2550        component_resolution_packets,
2551        width,
2552        height,
2553        num_levels,
2554        &params.precinct_exponents,
2555    )?;
2556    let prepared_resolution_packets =
2557        ordered_prepared_resolution_packets(component_resolution_packets, options)?;
2558    let stage_start = profile::profile_now(profile_enabled);
2559    let (resolution_packets, packet_descriptors, allow_packetization_accelerator) =
2560        if options.num_layers > 1 {
2561            let (resolution_packets, packet_descriptors) =
2562                encode_prepared_resolution_packets_layered(
2563                    prepared_resolution_packets,
2564                    options.num_layers,
2565                    options.progression_order,
2566                    &options.quality_layer_byte_targets,
2567                    accelerator,
2568                )?;
2569            (resolution_packets, packet_descriptors, false)
2570        } else {
2571            let packet_descriptors = packet_descriptors_for_order(
2572                &prepared_resolution_packets,
2573                1,
2574                options.progression_order,
2575            )?;
2576            let resolution_packets =
2577                encode_prepared_resolution_packets(prepared_resolution_packets, accelerator)?;
2578            (resolution_packets, packet_descriptors, true)
2579        };
2580    let block_encode_us = profile::elapsed_us(stage_start);
2581
2582    // Step 6: Form tile bitstream (T2)
2583    let stage_start = profile::profile_now(profile_enabled);
2584    let mut resolution_packets = resolution_packets;
2585    let packetization_resolutions = public_packetization_resolutions(&resolution_packets);
2586    let scalar_packet_descriptors = scalar_packet_descriptors(&packet_descriptors);
2587    let packetization_job = J2kPacketizationEncodeJob {
2588        resolution_count: resolution_packets.len() as u32,
2589        num_layers: options.num_layers,
2590        num_components,
2591        code_block_count: count_code_blocks(&resolution_packets)?,
2592        progression_order: public_packetization_progression_order(options.progression_order),
2593        packet_descriptors: &packet_descriptors,
2594        resolutions: &packetization_resolutions,
2595    };
2596    let needs_scalar_packetization =
2597        params.write_plt || params.write_plm || params.write_sop || params.write_eph;
2598    let accelerated_tile_data = if allow_packetization_accelerator && !needs_scalar_packetization {
2599        accelerator.encode_packetization(packetization_job)?
2600    } else {
2601        None
2602    };
2603    let packetized_tile = if let Some(data) = accelerated_tile_data {
2604        packet_encode::PacketizedTileData {
2605            data,
2606            packet_lengths: Vec::new(),
2607        }
2608    } else {
2609        packet_encode::form_tile_bitstream_with_descriptors_lengths_and_markers(
2610            &mut resolution_packets,
2611            &scalar_packet_descriptors,
2612            packet_encode::PacketMarkerOptions {
2613                write_sop: params.write_sop,
2614                write_eph: params.write_eph,
2615            },
2616        )?
2617    };
2618    let packetize_us = profile::elapsed_us(stage_start);
2619
2620    // Step 7: Write codestream
2621    let stage_start = profile::profile_now(profile_enabled);
2622    let codestream = codestream_write::write_codestream_with_packet_lengths(
2623        &params,
2624        &packetized_tile.data,
2625        &quant_params,
2626        &packetized_tile.packet_lengths,
2627    );
2628    let codestream_us = profile::elapsed_us(stage_start);
2629
2630    if profile_enabled {
2631        profile::emit_profile_row(
2632            "encode",
2633            "cpu",
2634            &[
2635                ("deinterleave_us", deinterleave_us),
2636                ("mct_us", mct_us),
2637                ("dwt_us", dwt_us),
2638                ("subband_prepare_us", subband_prepare_us),
2639                ("block_encode_us", block_encode_us),
2640                ("packetize_us", packetize_us),
2641                ("codestream_us", codestream_us),
2642                ("total_us", profile::elapsed_us(total_start)),
2643            ],
2644        );
2645    }
2646
2647    Ok(codestream)
2648}
2649
2650fn encode_multitile_impl(
2651    pixels: &[u8],
2652    width: u32,
2653    height: u32,
2654    num_components: u8,
2655    bit_depth: u8,
2656    signed: bool,
2657    options: &EncodeOptions,
2658    block_coding_mode: BlockCodingMode,
2659    accelerator: &mut impl J2kEncodeStageAccelerator,
2660    tile_width: u32,
2661    tile_height: u32,
2662) -> Result<Vec<u8>, &'static str> {
2663    let num_x_tiles = width.div_ceil(tile_width);
2664    let num_y_tiles = height.div_ceil(tile_height);
2665    let num_tiles = num_x_tiles
2666        .checked_mul(num_y_tiles)
2667        .ok_or("tile count overflow")?;
2668    if num_tiles > u32::from(u16::MAX) + 1 {
2669        return Err("multi-tile encode supports at most 65536 tiles");
2670    }
2671
2672    let min_tile_width = if width.is_multiple_of(tile_width) {
2673        tile_width
2674    } else {
2675        width % tile_width
2676    };
2677    let min_tile_height = if height.is_multiple_of(tile_height) {
2678        tile_height
2679    } else {
2680        height % tile_height
2681    };
2682    let num_levels = options
2683        .num_decomposition_levels
2684        .min(max_decomposition_levels(min_tile_width, min_tile_height));
2685    let use_mct = options.use_mct && num_components >= 3;
2686    let guard_bits = if options.reversible {
2687        if use_mct {
2688            options.guard_bits.max(2)
2689        } else {
2690            options.guard_bits
2691        }
2692    } else {
2693        options.guard_bits.max(2)
2694    };
2695    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
2696        bit_depth,
2697        num_levels,
2698        options.reversible,
2699        guard_bits,
2700        options.irreversible_quantization_scale,
2701        options.irreversible_quantization_subband_scales,
2702    );
2703    let quant_params: Vec<(u16, u16)> = step_sizes
2704        .iter()
2705        .map(|s| (s.exponent, s.mantissa))
2706        .collect();
2707
2708    let mut child_options = options.clone();
2709    child_options.num_decomposition_levels = num_levels;
2710    child_options.tile_size = None;
2711    child_options.write_tlm = false;
2712    child_options.write_plt = options.write_plt || options.write_plm;
2713    child_options.write_plm = false;
2714
2715    struct EncodedTilePart {
2716        data: Vec<u8>,
2717        packet_lengths: Vec<u32>,
2718    }
2719
2720    let mut tile_bodies = Vec::with_capacity(num_tiles as usize);
2721    for tile_y in 0..num_y_tiles {
2722        for tile_x in 0..num_x_tiles {
2723            let x0 = tile_x * tile_width;
2724            let y0 = tile_y * tile_height;
2725            let actual_width = (width - x0).min(tile_width);
2726            let actual_height = (height - y0).min(tile_height);
2727            let tile_pixels = extract_interleaved_tile(
2728                pixels,
2729                width,
2730                x0,
2731                y0,
2732                actual_width,
2733                actual_height,
2734                num_components,
2735                bit_depth,
2736            )?;
2737            let tile_codestream = encode_impl(
2738                &tile_pixels,
2739                actual_width,
2740                actual_height,
2741                num_components,
2742                bit_depth,
2743                signed,
2744                &child_options,
2745                block_coding_mode,
2746                accelerator,
2747            )?;
2748            let packet_lengths = if options.write_plt || options.write_plm {
2749                extract_single_tile_plt_packet_lengths(&tile_codestream)?
2750            } else {
2751                Vec::new()
2752            };
2753            tile_bodies.push(EncodedTilePart {
2754                data: extract_single_tile_body(&tile_codestream)?.to_vec(),
2755                packet_lengths,
2756            });
2757        }
2758    }
2759
2760    let component_sampling = component_sampling_for_options(options, num_components)?;
2761    let precinct_exponents = precinct_exponents_for_options(options, num_levels)?;
2762    let params = EncodeParams {
2763        width,
2764        height,
2765        tile_width,
2766        tile_height,
2767        num_components,
2768        bit_depth,
2769        signed,
2770        num_decomposition_levels: num_levels,
2771        reversible: options.reversible,
2772        code_block_width_exp: options.code_block_width_exp,
2773        code_block_height_exp: options.code_block_height_exp,
2774        num_layers: options.num_layers,
2775        use_mct,
2776        guard_bits,
2777        block_coding_mode,
2778        progression_order: options.progression_order,
2779        write_tlm: options.write_tlm,
2780        write_plt: options.write_plt,
2781        write_plm: options.write_plm,
2782        write_sop: options.write_sop,
2783        write_eph: options.write_eph,
2784        terminate_coding_passes: block_coding_mode == BlockCodingMode::Classic
2785            && options.num_layers > 1,
2786        component_sampling,
2787        precinct_exponents,
2788    };
2789    let tile_parts = tile_bodies
2790        .iter()
2791        .enumerate()
2792        .map(|(tile_index, tile)| {
2793            Ok(codestream_write::TilePartData {
2794                tile_index: u16::try_from(tile_index).map_err(|_| "tile index exceeds u16")?,
2795                data: &tile.data,
2796                packet_lengths: &tile.packet_lengths,
2797            })
2798        })
2799        .collect::<Result<Vec<_>, &'static str>>()?;
2800
2801    Ok(codestream_write::write_codestream_tiles(
2802        &params,
2803        &tile_parts,
2804        &quant_params,
2805    ))
2806}
2807
2808fn extract_interleaved_tile(
2809    pixels: &[u8],
2810    image_width: u32,
2811    x0: u32,
2812    y0: u32,
2813    tile_width: u32,
2814    tile_height: u32,
2815    num_components: u8,
2816    bit_depth: u8,
2817) -> Result<Vec<u8>, &'static str> {
2818    let bytes_per_sample = if bit_depth <= 8 { 1usize } else { 2usize };
2819    let bytes_per_pixel = usize::from(num_components)
2820        .checked_mul(bytes_per_sample)
2821        .ok_or("pixel stride overflow")?;
2822    let row_bytes = usize::try_from(tile_width)
2823        .map_err(|_| "tile width exceeds usize")?
2824        .checked_mul(bytes_per_pixel)
2825        .ok_or("tile row byte count overflow")?;
2826    let out_len = row_bytes
2827        .checked_mul(usize::try_from(tile_height).map_err(|_| "tile height exceeds usize")?)
2828        .ok_or("tile byte count overflow")?;
2829    let mut tile = Vec::with_capacity(out_len);
2830    let image_row_bytes = usize::try_from(image_width)
2831        .map_err(|_| "image width exceeds usize")?
2832        .checked_mul(bytes_per_pixel)
2833        .ok_or("image row byte count overflow")?;
2834    let x_byte_offset = usize::try_from(x0)
2835        .map_err(|_| "tile x offset exceeds usize")?
2836        .checked_mul(bytes_per_pixel)
2837        .ok_or("tile x byte offset overflow")?;
2838
2839    for y in y0..y0 + tile_height {
2840        let row_start = usize::try_from(y)
2841            .map_err(|_| "tile y offset exceeds usize")?
2842            .checked_mul(image_row_bytes)
2843            .and_then(|offset| offset.checked_add(x_byte_offset))
2844            .ok_or("tile row offset overflow")?;
2845        let row_end = row_start
2846            .checked_add(row_bytes)
2847            .ok_or("tile row range overflow")?;
2848        tile.extend_from_slice(
2849            pixels
2850                .get(row_start..row_end)
2851                .ok_or("tile row range outside source pixels")?,
2852        );
2853    }
2854
2855    Ok(tile)
2856}
2857
2858fn extract_single_tile_body(codestream: &[u8]) -> Result<&[u8], &'static str> {
2859    let sod = codestream
2860        .windows(2)
2861        .position(|marker| marker == [0xFF, super::codestream::markers::SOD])
2862        .ok_or("encoded tile codestream missing SOD")?;
2863    let eoc = codestream
2864        .windows(2)
2865        .rposition(|marker| marker == [0xFF, super::codestream::markers::EOC])
2866        .ok_or("encoded tile codestream missing EOC")?;
2867    if eoc < sod + 2 {
2868        return Err("encoded tile codestream marker order invalid");
2869    }
2870    Ok(&codestream[sod + 2..eoc])
2871}
2872
2873fn extract_single_tile_plt_packet_lengths(codestream: &[u8]) -> Result<Vec<u32>, &'static str> {
2874    let sod = codestream
2875        .windows(2)
2876        .position(|marker| marker == [0xFF, super::codestream::markers::SOD])
2877        .ok_or("encoded tile codestream missing SOD")?;
2878    let mut packet_lengths = Vec::new();
2879    let mut offset = 0usize;
2880
2881    while offset + 4 <= sod {
2882        if codestream[offset] == 0xFF && codestream[offset + 1] == super::codestream::markers::PLT {
2883            let marker_len =
2884                u16::from_be_bytes([codestream[offset + 2], codestream[offset + 3]]) as usize;
2885            if marker_len < 3 {
2886                return Err("encoded tile codestream has invalid PLT length");
2887            }
2888            let marker_end = offset
2889                .checked_add(2)
2890                .and_then(|value| value.checked_add(marker_len))
2891                .ok_or("encoded tile codestream PLT length overflow")?;
2892            if marker_end > sod {
2893                return Err("encoded tile codestream PLT extends past SOD");
2894            }
2895            let length_bytes = codestream
2896                .get(offset + 5..marker_end)
2897                .ok_or("encoded tile codestream PLT payload out of range")?;
2898            packet_lengths.extend(
2899                super::codestream::decode_packet_lengths(length_bytes)
2900                    .ok_or("encoded tile codestream has invalid PLT packet lengths")?,
2901            );
2902            offset = marker_end;
2903        } else {
2904            offset += 1;
2905        }
2906    }
2907
2908    if packet_lengths.is_empty() {
2909        return Err("encoded tile codestream missing PLT packet lengths");
2910    }
2911
2912    Ok(packet_lengths)
2913}
2914
2915fn try_encode_forward_rct(
2916    components: &mut [Vec<f32>],
2917    accelerator: &mut impl J2kEncodeStageAccelerator,
2918) -> Result<bool, &'static str> {
2919    debug_assert!(components.len() >= 3);
2920    let (plane0, rest) = components.split_at_mut(1);
2921    let (plane1, plane2) = rest.split_at_mut(1);
2922    accelerator.encode_forward_rct(J2kForwardRctJob {
2923        plane0: &mut plane0[0],
2924        plane1: &mut plane1[0],
2925        plane2: &mut plane2[0],
2926    })
2927}
2928
2929fn try_encode_forward_ict(
2930    components: &mut [Vec<f32>],
2931    accelerator: &mut impl J2kEncodeStageAccelerator,
2932) -> Result<bool, &'static str> {
2933    debug_assert!(components.len() >= 3);
2934    let (plane0, rest) = components.split_at_mut(1);
2935    let (plane1, plane2) = rest.split_at_mut(1);
2936    accelerator.encode_forward_ict(J2kForwardIctJob {
2937        plane0: &mut plane0[0],
2938        plane1: &mut plane1[0],
2939        plane2: &mut plane2[0],
2940    })
2941}
2942
2943fn encode_forward_dwt(
2944    component: &[f32],
2945    width: u32,
2946    height: u32,
2947    num_levels: u8,
2948    reversible: bool,
2949    accelerator: &mut impl J2kEncodeStageAccelerator,
2950) -> Result<DwtDecomposition, &'static str> {
2951    if reversible {
2952        if let Some(output) = accelerator.encode_forward_dwt53(J2kForwardDwt53Job {
2953            samples: component,
2954            width,
2955            height,
2956            num_levels,
2957        })? {
2958            return convert_forward_dwt53_output(output);
2959        }
2960    } else if let Some(output) = accelerator.encode_forward_dwt97(J2kForwardDwt97Job {
2961        samples: component,
2962        width,
2963        height,
2964        num_levels,
2965    })? {
2966        return convert_forward_dwt97_output(output);
2967    }
2968
2969    Ok(fdwt::forward_dwt(
2970        component, width, height, num_levels, reversible,
2971    ))
2972}
2973
2974fn convert_forward_dwt53_output(
2975    output: J2kForwardDwt53Output,
2976) -> Result<DwtDecomposition, &'static str> {
2977    validate_band_len(output.ll.len(), output.ll_width, output.ll_height)?;
2978    let mut levels = Vec::with_capacity(output.levels.len());
2979    for level in output.levels {
2980        validate_dwt53_level(&level)?;
2981        levels.push(fdwt::DwtLevel {
2982            hl: level.hl,
2983            lh: level.lh,
2984            hh: level.hh,
2985            low_width: level.low_width,
2986            low_height: level.low_height,
2987            high_width: level.high_width,
2988            high_height: level.high_height,
2989        });
2990    }
2991    Ok(DwtDecomposition {
2992        ll: output.ll,
2993        ll_width: output.ll_width,
2994        ll_height: output.ll_height,
2995        levels,
2996    })
2997}
2998
2999fn convert_forward_dwt97_output(
3000    output: J2kForwardDwt97Output,
3001) -> Result<DwtDecomposition, &'static str> {
3002    validate_band_len(output.ll.len(), output.ll_width, output.ll_height)?;
3003    let mut levels = Vec::with_capacity(output.levels.len());
3004    for level in output.levels {
3005        validate_dwt97_level(&level)?;
3006        levels.push(fdwt::DwtLevel {
3007            hl: level.hl,
3008            lh: level.lh,
3009            hh: level.hh,
3010            low_width: level.low_width,
3011            low_height: level.low_height,
3012            high_width: level.high_width,
3013            high_height: level.high_height,
3014        });
3015    }
3016    Ok(DwtDecomposition {
3017        ll: output.ll,
3018        ll_width: output.ll_width,
3019        ll_height: output.ll_height,
3020        levels,
3021    })
3022}
3023
3024fn validate_dwt53_level(level: &J2kForwardDwt53Level) -> Result<(), &'static str> {
3025    validate_band_len(level.hl.len(), level.high_width, level.low_height)?;
3026    validate_band_len(level.lh.len(), level.low_width, level.high_height)?;
3027    validate_band_len(level.hh.len(), level.high_width, level.high_height)?;
3028    Ok(())
3029}
3030
3031fn validate_dwt97_level(level: &J2kForwardDwt97Level) -> Result<(), &'static str> {
3032    validate_band_len(level.hl.len(), level.high_width, level.low_height)?;
3033    validate_band_len(level.lh.len(), level.low_width, level.high_height)?;
3034    validate_band_len(level.hh.len(), level.high_width, level.high_height)?;
3035    Ok(())
3036}
3037
3038fn validate_band_len(actual: usize, width: u32, height: u32) -> Result<(), &'static str> {
3039    let expected = (width as usize)
3040        .checked_mul(height as usize)
3041        .ok_or("accelerated DWT output dimensions overflow")?;
3042    if actual != expected {
3043        return Err("accelerated DWT output length mismatch");
3044    }
3045    Ok(())
3046}
3047
3048fn validate_deinterleaved_components(
3049    components: Vec<Vec<f32>>,
3050    num_components: u8,
3051    num_pixels: usize,
3052) -> Result<Vec<Vec<f32>>, &'static str> {
3053    if components.len() != usize::from(num_components) {
3054        return Err("accelerated deinterleave component count mismatch");
3055    }
3056    if components
3057        .iter()
3058        .any(|component| component.len() != num_pixels)
3059    {
3060        return Err("accelerated deinterleave component length mismatch");
3061    }
3062    Ok(components)
3063}
3064
3065fn component_sampling_for_options(
3066    options: &EncodeOptions,
3067    num_components: u8,
3068) -> Result<Vec<(u8, u8)>, &'static str> {
3069    match &options.component_sampling {
3070        Some(component_sampling) => {
3071            if component_sampling.len() != usize::from(num_components) {
3072                return Err("component sampling count does not match component count");
3073            }
3074            if component_sampling
3075                .iter()
3076                .any(|&(x_rsiz, y_rsiz)| x_rsiz == 0 || y_rsiz == 0)
3077            {
3078                return Err("component sampling factors must be non-zero");
3079            }
3080            Ok(component_sampling.clone())
3081        }
3082        None => Ok(vec![(1, 1); usize::from(num_components)]),
3083    }
3084}
3085
3086fn precinct_exponents_for_options(
3087    options: &EncodeOptions,
3088    num_decomposition_levels: u8,
3089) -> Result<Vec<(u8, u8)>, &'static str> {
3090    if options.precinct_exponents.is_empty() {
3091        return Ok(Vec::new());
3092    }
3093
3094    let expected = usize::from(num_decomposition_levels) + 1;
3095    if options.precinct_exponents.len() != expected {
3096        return Err("precinct exponent count must match resolution level count");
3097    }
3098    if options
3099        .precinct_exponents
3100        .iter()
3101        .any(|&(ppx, ppy)| ppx > 15 || ppy > 15)
3102    {
3103        return Err("precinct exponents must fit in COD marker nybbles");
3104    }
3105    let code_block_width_exp = options.code_block_width_exp + 2;
3106    let code_block_height_exp = options.code_block_height_exp + 2;
3107    for (resolution, &(ppx, ppy)) in options.precinct_exponents.iter().enumerate() {
3108        let min_ppx = if resolution == 0 {
3109            code_block_width_exp
3110        } else {
3111            code_block_width_exp + 1
3112        };
3113        let min_ppy = if resolution == 0 {
3114            code_block_height_exp
3115        } else {
3116            code_block_height_exp + 1
3117        };
3118        if ppx < min_ppx || ppy < min_ppy {
3119            return Err("precinct exponents must not reduce encoder code-block dimensions");
3120        }
3121    }
3122    Ok(options.precinct_exponents.clone())
3123}
3124
3125fn count_code_blocks(resolution_packets: &[ResolutionPacket]) -> Result<u32, &'static str> {
3126    let count = resolution_packets
3127        .iter()
3128        .flat_map(|resolution| resolution.subbands.iter())
3129        .try_fold(0usize, |acc, subband| {
3130            acc.checked_add(subband.code_blocks.len())
3131                .ok_or("packetization code-block count overflow")
3132        })?;
3133    u32::try_from(count).map_err(|_| "packetization code-block count exceeds u32")
3134}
3135
3136fn count_compact_code_blocks(
3137    resolution_packets: &[PreparedCompactResolutionPacket<'_>],
3138) -> Result<u32, &'static str> {
3139    let count = resolution_packets
3140        .iter()
3141        .flat_map(|resolution| resolution.subbands.iter())
3142        .try_fold(0usize, |acc, subband| {
3143            acc.checked_add(subband.code_blocks.len())
3144                .ok_or("packetization code-block count overflow")
3145        })?;
3146    u32::try_from(count).map_err(|_| "packetization code-block count exceeds u32")
3147}
3148
3149fn split_component_resolution_packets_by_precinct(
3150    component_resolution_packets: Vec<Vec<PreparedResolutionPacket>>,
3151    width: u32,
3152    height: u32,
3153    num_decomposition_levels: u8,
3154    precinct_exponents: &[(u8, u8)],
3155) -> Result<Vec<Vec<PreparedResolutionPacket>>, &'static str> {
3156    if precinct_exponents.is_empty() {
3157        return Ok(component_resolution_packets);
3158    }
3159
3160    component_resolution_packets
3161        .into_iter()
3162        .map(|component_packets| {
3163            let mut split_packets = Vec::new();
3164            for packet in component_packets {
3165                split_packets.extend(split_prepared_resolution_packet_by_precinct(
3166                    packet,
3167                    width,
3168                    height,
3169                    num_decomposition_levels,
3170                    precinct_exponents,
3171                )?);
3172            }
3173            Ok(split_packets)
3174        })
3175        .collect()
3176}
3177
3178fn split_prepared_resolution_packet_by_precinct(
3179    packet: PreparedResolutionPacket,
3180    width: u32,
3181    height: u32,
3182    num_decomposition_levels: u8,
3183    precinct_exponents: &[(u8, u8)],
3184) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
3185    let resolution =
3186        usize::try_from(packet.resolution).map_err(|_| "resolution index exceeds usize")?;
3187    let &(ppx, ppy) = precinct_exponents
3188        .get(resolution)
3189        .ok_or("missing precinct exponents for resolution")?;
3190    let (precincts_x, precincts_y) = resolution_precinct_grid(
3191        width,
3192        height,
3193        num_decomposition_levels,
3194        packet.resolution,
3195        ppx,
3196        ppy,
3197    )?;
3198    let packet_count = (precincts_x as usize)
3199        .checked_mul(precincts_y as usize)
3200        .ok_or("precinct packet count overflow")?;
3201    let component = packet.component;
3202    let resolution = packet.resolution;
3203    let subbands = packet.subbands;
3204    let mut packets = Vec::with_capacity(packet_count);
3205
3206    for precinct_y in 0..precincts_y {
3207        for precinct_x in 0..precincts_x {
3208            let precinct = u64::from(precinct_y)
3209                .checked_mul(u64::from(precincts_x))
3210                .and_then(|value| value.checked_add(u64::from(precinct_x)))
3211                .ok_or("precinct index overflow")?;
3212            let split_subbands = subbands
3213                .iter()
3214                .map(|subband| {
3215                    split_prepared_subband_by_precinct(
3216                        subband, resolution, ppx, ppy, precinct_x, precinct_y,
3217                    )
3218                })
3219                .collect::<Result<Vec<_>, &'static str>>()?;
3220            packets.push(PreparedResolutionPacket {
3221                component,
3222                resolution,
3223                precinct,
3224                subbands: split_subbands,
3225            });
3226        }
3227    }
3228
3229    Ok(packets)
3230}
3231
3232fn resolution_precinct_grid(
3233    width: u32,
3234    height: u32,
3235    num_decomposition_levels: u8,
3236    resolution: u32,
3237    ppx: u8,
3238    ppy: u8,
3239) -> Result<(u32, u32), &'static str> {
3240    let resolution_shift = u32::from(num_decomposition_levels)
3241        .checked_sub(resolution)
3242        .ok_or("resolution exceeds decomposition level count")?;
3243    let resolution_scale = pow2_u32(resolution_shift)?;
3244    let resolution_width = width.div_ceil(resolution_scale);
3245    let resolution_height = height.div_ceil(resolution_scale);
3246    let precinct_width = pow2_u32(u32::from(ppx))?;
3247    let precinct_height = pow2_u32(u32::from(ppy))?;
3248
3249    Ok((
3250        if resolution_width == 0 {
3251            0
3252        } else {
3253            resolution_width.div_ceil(precinct_width)
3254        },
3255        if resolution_height == 0 {
3256            0
3257        } else {
3258            resolution_height.div_ceil(precinct_height)
3259        },
3260    ))
3261}
3262
3263fn split_prepared_subband_by_precinct(
3264    subband: &PreparedEncodeSubband,
3265    resolution: u32,
3266    ppx: u8,
3267    ppy: u8,
3268    precinct_x: u32,
3269    precinct_y: u32,
3270) -> Result<PreparedEncodeSubband, &'static str> {
3271    if subband.code_blocks.is_empty() || subband.width == 0 || subband.height == 0 {
3272        return Ok(empty_prepared_subband_precinct(subband));
3273    }
3274
3275    let subband_ppx = if resolution > 0 {
3276        ppx.checked_sub(1)
3277            .ok_or("nonzero resolution precinct exponent underflow")?
3278    } else {
3279        ppx
3280    };
3281    let subband_ppy = if resolution > 0 {
3282        ppy.checked_sub(1)
3283            .ok_or("nonzero resolution precinct exponent underflow")?
3284    } else {
3285        ppy
3286    };
3287    let precinct_width = pow2_u32(u32::from(subband_ppx))?;
3288    let precinct_height = pow2_u32(u32::from(subband_ppy))?;
3289    let precinct_x0 = precinct_x
3290        .checked_mul(precinct_width)
3291        .ok_or("precinct x coordinate overflow")?;
3292    let precinct_y0 = precinct_y
3293        .checked_mul(precinct_height)
3294        .ok_or("precinct y coordinate overflow")?;
3295    let x0 = precinct_x0.min(subband.width);
3296    let y0 = precinct_y0.min(subband.height);
3297    let x1 = precinct_x0
3298        .checked_add(precinct_width)
3299        .ok_or("precinct x extent overflow")?
3300        .min(subband.width);
3301    let y1 = precinct_y0
3302        .checked_add(precinct_height)
3303        .ok_or("precinct y extent overflow")?
3304        .min(subband.height);
3305
3306    if x0 >= x1 || y0 >= y1 {
3307        return Ok(empty_prepared_subband_precinct(subband));
3308    }
3309
3310    let cb_width = subband.code_block_width;
3311    let cb_height = subband.code_block_height;
3312    if cb_width == 0 || cb_height == 0 {
3313        return Ok(empty_prepared_subband_precinct(subband));
3314    }
3315
3316    let cb_x0 = (x0 / cb_width) * cb_width;
3317    let cb_y0 = (y0 / cb_height) * cb_height;
3318    let cb_x1 = x1.div_ceil(cb_width) * cb_width;
3319    let cb_y1 = y1.div_ceil(cb_height) * cb_height;
3320    let cbx_start = cb_x0 / cb_width;
3321    let cby_start = cb_y0 / cb_height;
3322    let cbx_end = cb_x1 / cb_width;
3323    let cby_end = cb_y1 / cb_height;
3324    let num_cbs_x = cbx_end.saturating_sub(cbx_start);
3325    let num_cbs_y = cby_end.saturating_sub(cby_start);
3326    let mut indices = Vec::with_capacity((num_cbs_x as usize).saturating_mul(num_cbs_y as usize));
3327
3328    for cby in cby_start..cby_end {
3329        for cbx in cbx_start..cbx_end {
3330            let index = cby
3331                .checked_mul(subband.num_cbs_x)
3332                .and_then(|value| value.checked_add(cbx))
3333                .ok_or("precinct code-block index overflow")?;
3334            indices.push(usize::try_from(index).map_err(|_| "code-block index exceeds usize")?);
3335        }
3336    }
3337
3338    let code_blocks = indices
3339        .iter()
3340        .map(|&idx| {
3341            subband
3342                .code_blocks
3343                .get(idx)
3344                .cloned()
3345                .ok_or("precinct code-block index out of range")
3346        })
3347        .collect::<Result<Vec<_>, &'static str>>()?;
3348    let preencoded_ht_code_blocks = subband
3349        .preencoded_ht_code_blocks
3350        .as_ref()
3351        .map(|blocks| {
3352            indices
3353                .iter()
3354                .map(|&idx| {
3355                    blocks
3356                        .get(idx)
3357                        .cloned()
3358                        .ok_or("precinct preencoded code-block index out of range")
3359                })
3360                .collect::<Result<Vec<_>, &'static str>>()
3361        })
3362        .transpose()?;
3363
3364    Ok(PreparedEncodeSubband {
3365        code_blocks,
3366        preencoded_ht_code_blocks,
3367        num_cbs_x,
3368        num_cbs_y,
3369        code_block_width: cb_width,
3370        code_block_height: cb_height,
3371        width: x1 - x0,
3372        height: y1 - y0,
3373        sub_band_type: subband.sub_band_type,
3374        total_bitplanes: subband.total_bitplanes,
3375        block_coding_mode: subband.block_coding_mode,
3376    })
3377}
3378
3379fn empty_prepared_subband_precinct(subband: &PreparedEncodeSubband) -> PreparedEncodeSubband {
3380    PreparedEncodeSubband {
3381        code_blocks: Vec::new(),
3382        preencoded_ht_code_blocks: subband
3383            .preencoded_ht_code_blocks
3384            .as_ref()
3385            .map(|_| Vec::new()),
3386        num_cbs_x: 0,
3387        num_cbs_y: 0,
3388        code_block_width: subband.code_block_width,
3389        code_block_height: subband.code_block_height,
3390        width: 0,
3391        height: 0,
3392        sub_band_type: subband.sub_band_type,
3393        total_bitplanes: subband.total_bitplanes,
3394        block_coding_mode: subband.block_coding_mode,
3395    }
3396}
3397
3398fn pow2_u32(exponent: u32) -> Result<u32, &'static str> {
3399    1_u32
3400        .checked_shl(exponent)
3401        .ok_or("precinct exponent exceeds u32 shift width")
3402}
3403
3404fn packet_descriptors_for_order(
3405    packets: &[PreparedResolutionPacket],
3406    num_layers: u8,
3407    progression_order: EncodeProgressionOrder,
3408) -> Result<Vec<J2kPacketizationPacketDescriptor>, &'static str> {
3409    if num_layers != 1 {
3410        return Err("encode currently prepares one packet contribution layer");
3411    }
3412    let mut descriptors = packets
3413        .iter()
3414        .enumerate()
3415        .map(|(packet_index, packet)| {
3416            Ok(J2kPacketizationPacketDescriptor {
3417                packet_index: u32::try_from(packet_index)
3418                    .map_err(|_| "packet descriptor index exceeds u32")?,
3419                state_index: u32::try_from(packet_index)
3420                    .map_err(|_| "packet descriptor state index exceeds u32")?,
3421                layer: 0,
3422                resolution: packet.resolution,
3423                component: packet.component,
3424                precinct: packet.precinct,
3425            })
3426        })
3427        .collect::<Result<Vec<_>, &'static str>>()?;
3428    sort_packet_descriptors_for_progression(&mut descriptors, progression_order);
3429    Ok(descriptors)
3430}
3431
3432fn packet_descriptors_for_compact_order(
3433    packets: &[PreparedCompactResolutionPacket<'_>],
3434    num_layers: u8,
3435    progression_order: EncodeProgressionOrder,
3436) -> Result<Vec<J2kPacketizationPacketDescriptor>, &'static str> {
3437    if num_layers != 1 {
3438        return Err("encode currently prepares one packet contribution layer");
3439    }
3440    let mut descriptors = packets
3441        .iter()
3442        .enumerate()
3443        .map(|(packet_index, packet)| {
3444            Ok(J2kPacketizationPacketDescriptor {
3445                packet_index: u32::try_from(packet_index)
3446                    .map_err(|_| "packet descriptor index exceeds u32")?,
3447                state_index: u32::try_from(packet_index)
3448                    .map_err(|_| "packet descriptor state index exceeds u32")?,
3449                layer: 0,
3450                resolution: packet.resolution,
3451                component: packet.component,
3452                precinct: packet.precinct,
3453            })
3454        })
3455        .collect::<Result<Vec<_>, &'static str>>()?;
3456    sort_packet_descriptors_for_progression(&mut descriptors, progression_order);
3457    Ok(descriptors)
3458}
3459
3460fn ordered_prepared_resolution_packets(
3461    component_resolution_packets: Vec<Vec<PreparedResolutionPacket>>,
3462    options: &EncodeOptions,
3463) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
3464    match options.progression_order {
3465        EncodeProgressionOrder::Lrcp
3466        | EncodeProgressionOrder::Rlcp
3467        | EncodeProgressionOrder::Rpcl => {
3468            lrcp_ordered_prepared_resolution_packets(component_resolution_packets)
3469        }
3470        EncodeProgressionOrder::Pcrl | EncodeProgressionOrder::Cprl => {
3471            component_ordered_prepared_resolution_packets(component_resolution_packets)
3472        }
3473    }
3474}
3475
3476fn ordered_prepared_compact_resolution_packets<'a>(
3477    component_resolution_packets: Vec<Vec<PreparedCompactResolutionPacket<'a>>>,
3478    options: &EncodeOptions,
3479) -> Result<Vec<PreparedCompactResolutionPacket<'a>>, &'static str> {
3480    match options.progression_order {
3481        EncodeProgressionOrder::Lrcp
3482        | EncodeProgressionOrder::Rlcp
3483        | EncodeProgressionOrder::Rpcl => {
3484            lrcp_ordered_prepared_compact_resolution_packets(component_resolution_packets)
3485        }
3486        EncodeProgressionOrder::Pcrl | EncodeProgressionOrder::Cprl => {
3487            component_ordered_prepared_compact_resolution_packets(component_resolution_packets)
3488        }
3489    }
3490}
3491
3492fn lrcp_ordered_prepared_resolution_packets(
3493    component_resolution_packets: Vec<Vec<PreparedResolutionPacket>>,
3494) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
3495    let resolution_count = component_resolution_packets
3496        .first()
3497        .map_or(0usize, alloc::vec::Vec::len);
3498    let mut component_iters: Vec<_> = component_resolution_packets
3499        .into_iter()
3500        .map(alloc::vec::Vec::into_iter)
3501        .collect();
3502    let mut resolution_packets =
3503        Vec::with_capacity(resolution_count.saturating_mul(component_iters.len()));
3504
3505    for _resolution in 0..resolution_count {
3506        for component in &mut component_iters {
3507            resolution_packets.push(
3508                component
3509                    .next()
3510                    .ok_or("component packet resolution count mismatch")?,
3511            );
3512        }
3513    }
3514
3515    if component_iters
3516        .iter_mut()
3517        .any(|component| component.next().is_some())
3518    {
3519        return Err("component packet resolution count mismatch");
3520    }
3521
3522    Ok(resolution_packets)
3523}
3524
3525fn lrcp_ordered_prepared_compact_resolution_packets<'a>(
3526    component_resolution_packets: Vec<Vec<PreparedCompactResolutionPacket<'a>>>,
3527) -> Result<Vec<PreparedCompactResolutionPacket<'a>>, &'static str> {
3528    let resolution_count = component_resolution_packets
3529        .first()
3530        .map_or(0usize, alloc::vec::Vec::len);
3531    let mut component_iters: Vec<_> = component_resolution_packets
3532        .into_iter()
3533        .map(alloc::vec::Vec::into_iter)
3534        .collect();
3535    let mut resolution_packets =
3536        Vec::with_capacity(resolution_count.saturating_mul(component_iters.len()));
3537
3538    for _resolution in 0..resolution_count {
3539        for component in &mut component_iters {
3540            resolution_packets.push(
3541                component
3542                    .next()
3543                    .ok_or("component packet resolution count mismatch")?,
3544            );
3545        }
3546    }
3547
3548    if component_iters
3549        .iter_mut()
3550        .any(|component| component.next().is_some())
3551    {
3552        return Err("component packet resolution count mismatch");
3553    }
3554
3555    Ok(resolution_packets)
3556}
3557
3558fn component_ordered_prepared_resolution_packets(
3559    component_resolution_packets: Vec<Vec<PreparedResolutionPacket>>,
3560) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
3561    let resolution_count = component_resolution_packets
3562        .first()
3563        .map_or(0usize, alloc::vec::Vec::len);
3564    let mut resolution_packets =
3565        Vec::with_capacity(resolution_count.saturating_mul(component_resolution_packets.len()));
3566
3567    for component in component_resolution_packets {
3568        if component.len() != resolution_count {
3569            return Err("component packet resolution count mismatch");
3570        }
3571        resolution_packets.extend(component);
3572    }
3573
3574    Ok(resolution_packets)
3575}
3576
3577fn component_ordered_prepared_compact_resolution_packets<'a>(
3578    component_resolution_packets: Vec<Vec<PreparedCompactResolutionPacket<'a>>>,
3579) -> Result<Vec<PreparedCompactResolutionPacket<'a>>, &'static str> {
3580    let resolution_count = component_resolution_packets
3581        .first()
3582        .map_or(0usize, alloc::vec::Vec::len);
3583    let mut resolution_packets =
3584        Vec::with_capacity(resolution_count.saturating_mul(component_resolution_packets.len()));
3585
3586    for component in component_resolution_packets {
3587        if component.len() != resolution_count {
3588            return Err("component packet resolution count mismatch");
3589        }
3590        resolution_packets.extend(component);
3591    }
3592
3593    Ok(resolution_packets)
3594}
3595
3596fn public_packetization_progression_order(
3597    progression_order: EncodeProgressionOrder,
3598) -> crate::J2kPacketizationProgressionOrder {
3599    match progression_order {
3600        EncodeProgressionOrder::Lrcp => crate::J2kPacketizationProgressionOrder::Lrcp,
3601        EncodeProgressionOrder::Rlcp => crate::J2kPacketizationProgressionOrder::Rlcp,
3602        EncodeProgressionOrder::Rpcl => crate::J2kPacketizationProgressionOrder::Rpcl,
3603        EncodeProgressionOrder::Pcrl => crate::J2kPacketizationProgressionOrder::Pcrl,
3604        EncodeProgressionOrder::Cprl => crate::J2kPacketizationProgressionOrder::Cprl,
3605    }
3606}
3607
3608fn scalar_packet_descriptors(
3609    descriptors: &[J2kPacketizationPacketDescriptor],
3610) -> Vec<packet_encode::PacketDescriptor> {
3611    descriptors
3612        .iter()
3613        .map(|descriptor| packet_encode::PacketDescriptor {
3614            packet_index: descriptor.packet_index,
3615            state_index: descriptor.state_index,
3616            layer: descriptor.layer,
3617            resolution: descriptor.resolution,
3618            component: descriptor.component,
3619            precinct: descriptor.precinct,
3620        })
3621        .collect()
3622}
3623
3624fn public_packetization_resolutions(
3625    resolution_packets: &[ResolutionPacket],
3626) -> Vec<J2kPacketizationResolution<'_>> {
3627    resolution_packets
3628        .iter()
3629        .map(|resolution| J2kPacketizationResolution {
3630            subbands: resolution
3631                .subbands
3632                .iter()
3633                .map(|subband| J2kPacketizationSubband {
3634                    code_blocks: subband
3635                        .code_blocks
3636                        .iter()
3637                        .map(|code_block| J2kPacketizationCodeBlock {
3638                            data: &code_block.data,
3639                            ht_cleanup_length: code_block.ht_cleanup_length,
3640                            ht_refinement_length: code_block.ht_refinement_length,
3641                            num_coding_passes: code_block.num_coding_passes,
3642                            num_zero_bitplanes: code_block.num_zero_bitplanes,
3643                            previously_included: code_block.previously_included,
3644                            l_block: code_block.l_block,
3645                            block_coding_mode: public_packetization_block_coding_mode(
3646                                code_block.block_coding_mode,
3647                            ),
3648                        })
3649                        .collect(),
3650                    num_cbs_x: subband.num_cbs_x,
3651                    num_cbs_y: subband.num_cbs_y,
3652                })
3653                .collect(),
3654        })
3655        .collect()
3656}
3657
3658fn public_packetization_resolutions_from_compact<'a>(
3659    resolution_packets: &'a [PreparedCompactResolutionPacket<'a>],
3660) -> Vec<J2kPacketizationResolution<'a>> {
3661    resolution_packets
3662        .iter()
3663        .map(|resolution| J2kPacketizationResolution {
3664            subbands: resolution
3665                .subbands
3666                .iter()
3667                .map(|subband| J2kPacketizationSubband {
3668                    code_blocks: subband
3669                        .code_blocks
3670                        .iter()
3671                        .map(|code_block| J2kPacketizationCodeBlock {
3672                            data: code_block.data,
3673                            ht_cleanup_length: code_block.cleanup_length,
3674                            ht_refinement_length: code_block.refinement_length,
3675                            num_coding_passes: code_block.num_coding_passes,
3676                            num_zero_bitplanes: code_block.num_zero_bitplanes,
3677                            previously_included: false,
3678                            l_block: 3,
3679                            block_coding_mode: J2kPacketizationBlockCodingMode::HighThroughput,
3680                        })
3681                        .collect(),
3682                    num_cbs_x: subband.num_cbs_x,
3683                    num_cbs_y: subband.num_cbs_y,
3684                })
3685                .collect(),
3686        })
3687        .collect()
3688}
3689
3690fn public_packetization_block_coding_mode(
3691    block_coding_mode: BlockCodingMode,
3692) -> J2kPacketizationBlockCodingMode {
3693    match block_coding_mode {
3694        BlockCodingMode::Classic => J2kPacketizationBlockCodingMode::Classic,
3695        BlockCodingMode::HighThroughput => J2kPacketizationBlockCodingMode::HighThroughput,
3696    }
3697}
3698
3699#[derive(Clone)]
3700struct PreparedEncodeCodeBlock {
3701    coefficients: Vec<i32>,
3702    width: u32,
3703    height: u32,
3704}
3705
3706#[derive(Clone)]
3707struct PreparedEncodeSubband {
3708    code_blocks: Vec<PreparedEncodeCodeBlock>,
3709    preencoded_ht_code_blocks: Option<Vec<EncodedHtJ2kCodeBlock>>,
3710    num_cbs_x: u32,
3711    num_cbs_y: u32,
3712    code_block_width: u32,
3713    code_block_height: u32,
3714    width: u32,
3715    height: u32,
3716    sub_band_type: SubBandType,
3717    total_bitplanes: u8,
3718    block_coding_mode: BlockCodingMode,
3719}
3720
3721struct PreparedResolutionPacket {
3722    component: u8,
3723    resolution: u32,
3724    precinct: u64,
3725    subbands: Vec<PreparedEncodeSubband>,
3726}
3727
3728struct PreparedCompactCodeBlock<'a> {
3729    data: &'a [u8],
3730    cleanup_length: u32,
3731    refinement_length: u32,
3732    num_coding_passes: u8,
3733    num_zero_bitplanes: u8,
3734}
3735
3736struct PreparedCompactSubband<'a> {
3737    code_blocks: Vec<PreparedCompactCodeBlock<'a>>,
3738    num_cbs_x: u32,
3739    num_cbs_y: u32,
3740}
3741
3742struct PreparedCompactResolutionPacket<'a> {
3743    component: u8,
3744    resolution: u32,
3745    precinct: u64,
3746    subbands: Vec<PreparedCompactSubband<'a>>,
3747}
3748
3749struct PreparedPrecomputedHtj2k97Image {
3750    params: EncodeParams,
3751    quant_params: Vec<(u16, u16)>,
3752    packet_descriptors: Vec<J2kPacketizationPacketDescriptor>,
3753    packet_count: usize,
3754    prepared_packets: Vec<PreparedResolutionPacket>,
3755}
3756
3757fn prepare_precomputed_htj2k97_image_for_batch(
3758    image: &PrecomputedHtj2k97Image,
3759    options: &EncodeOptions,
3760) -> Result<PreparedPrecomputedHtj2k97Image, &'static str> {
3761    if image.width == 0 || image.height == 0 {
3762        return Err("invalid dimensions");
3763    }
3764    if image.components.is_empty() || image.components.len() > 4 {
3765        return Err("unsupported component count");
3766    }
3767    if image.bit_depth == 0 || image.bit_depth > 16 {
3768        return Err("unsupported bit depth");
3769    }
3770    validate_irreversible_quantization_profile(options)?;
3771    if image
3772        .components
3773        .iter()
3774        .any(|component| component.x_rsiz == 0 || component.y_rsiz == 0)
3775    {
3776        return Err("component sampling factors must be non-zero");
3777    }
3778    validate_precomputed_dwt97_geometry(image)?;
3779
3780    let num_components =
3781        u8::try_from(image.components.len()).map_err(|_| "unsupported component count")?;
3782    let num_levels = precomputed_97_level_count(&image.components)?;
3783    let guard_bits = options.guard_bits.max(2);
3784    let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
3785        image.bit_depth,
3786        num_levels,
3787        false,
3788        guard_bits,
3789        options.irreversible_quantization_scale,
3790        options.irreversible_quantization_subband_scales,
3791    );
3792    let quant_params: Vec<(u16, u16)> = step_sizes
3793        .iter()
3794        .map(|s| (s.exponent, s.mantissa))
3795        .collect();
3796    let cb_width = 1u32 << (options.code_block_width_exp + 2);
3797    let cb_height = 1u32 << (options.code_block_height_exp + 2);
3798    let component_sampling = image
3799        .components
3800        .iter()
3801        .map(|component| (component.x_rsiz, component.y_rsiz))
3802        .collect::<Vec<_>>();
3803    let mut precomputed_options = options.clone();
3804    precomputed_options.num_decomposition_levels = num_levels;
3805    precomputed_options.reversible = false;
3806    precomputed_options.use_ht_block_coding = true;
3807    precomputed_options.use_mct = false;
3808    precomputed_options.validate_high_throughput_codestream = false;
3809    precomputed_options.component_sampling = Some(component_sampling.clone());
3810    let precinct_exponents = precinct_exponents_for_options(&precomputed_options, num_levels)?;
3811    let params = EncodeParams {
3812        width: image.width,
3813        height: image.height,
3814        tile_width: image.width,
3815        tile_height: image.height,
3816        num_components,
3817        bit_depth: image.bit_depth,
3818        signed: image.signed,
3819        num_decomposition_levels: num_levels,
3820        reversible: false,
3821        code_block_width_exp: precomputed_options.code_block_width_exp,
3822        code_block_height_exp: precomputed_options.code_block_height_exp,
3823        num_layers: 1,
3824        use_mct: false,
3825        guard_bits,
3826        block_coding_mode: BlockCodingMode::HighThroughput,
3827        progression_order: precomputed_options.progression_order,
3828        write_tlm: precomputed_options.write_tlm,
3829        write_plt: precomputed_options.write_plt,
3830        write_plm: precomputed_options.write_plm,
3831        write_sop: precomputed_options.write_sop,
3832        write_eph: precomputed_options.write_eph,
3833        terminate_coding_passes: false,
3834        component_sampling,
3835        precinct_exponents,
3836    };
3837
3838    let component_resolution_packets = image
3839        .components
3840        .iter()
3841        .enumerate()
3842        .map(|(component_idx, component)| {
3843            prepared_resolution_packets_from_precomputed_97_component(
3844                component_idx,
3845                component,
3846                &step_sizes,
3847                image.bit_depth,
3848                guard_bits,
3849                cb_width,
3850                cb_height,
3851            )
3852        })
3853        .collect::<Result<Vec<_>, _>>()?;
3854    let component_resolution_packets = split_component_resolution_packets_by_precinct(
3855        component_resolution_packets,
3856        image.width,
3857        image.height,
3858        num_levels,
3859        &params.precinct_exponents,
3860    )?;
3861    let prepared_packets =
3862        ordered_prepared_resolution_packets(component_resolution_packets, &precomputed_options)?;
3863    let packet_descriptors =
3864        packet_descriptors_for_order(&prepared_packets, 1, precomputed_options.progression_order)?;
3865
3866    Ok(PreparedPrecomputedHtj2k97Image {
3867        params,
3868        quant_params,
3869        packet_descriptors,
3870        packet_count: 0,
3871        prepared_packets,
3872    })
3873}
3874
3875fn prepared_resolution_packets_from_precomputed_97_component(
3876    component_idx: usize,
3877    component: &PrecomputedHtj2k97Component,
3878    step_sizes: &[QuantStepSize],
3879    bit_depth: u8,
3880    guard_bits: u8,
3881    cb_width: u32,
3882    cb_height: u32,
3883) -> Result<Vec<PreparedResolutionPacket>, &'static str> {
3884    let component_idx = u8::try_from(component_idx).map_err(|_| "component index exceeds u8")?;
3885    let mut packets = Vec::with_capacity(component.dwt.levels.len() + 1);
3886    packets.push(PreparedResolutionPacket {
3887        component: component_idx,
3888        resolution: 0,
3889        precinct: 0,
3890        subbands: vec![prepare_subband_cpu_quantized(
3891            &component.dwt.ll,
3892            component.dwt.ll_width,
3893            component.dwt.ll_height,
3894            step_sizes
3895                .first()
3896                .ok_or("irreversible quantization step missing")?,
3897            bit_depth,
3898            guard_bits,
3899            false,
3900            BlockCodingMode::HighThroughput,
3901            cb_width,
3902            cb_height,
3903            SubBandType::LowLow,
3904        )?],
3905    });
3906
3907    for (level_idx, level) in component.dwt.levels.iter().enumerate() {
3908        let step_base = 1 + level_idx * 3;
3909        packets.push(PreparedResolutionPacket {
3910            component: component_idx,
3911            resolution: u32::try_from(level_idx + 1).map_err(|_| "resolution index exceeds u32")?,
3912            precinct: 0,
3913            subbands: vec![
3914                prepare_subband_cpu_quantized(
3915                    &level.hl,
3916                    level.high_width,
3917                    level.low_height,
3918                    step_sizes
3919                        .get(step_base)
3920                        .ok_or("irreversible quantization step missing")?,
3921                    bit_depth,
3922                    guard_bits,
3923                    false,
3924                    BlockCodingMode::HighThroughput,
3925                    cb_width,
3926                    cb_height,
3927                    SubBandType::HighLow,
3928                )?,
3929                prepare_subband_cpu_quantized(
3930                    &level.lh,
3931                    level.low_width,
3932                    level.high_height,
3933                    step_sizes
3934                        .get(step_base + 1)
3935                        .ok_or("irreversible quantization step missing")?,
3936                    bit_depth,
3937                    guard_bits,
3938                    false,
3939                    BlockCodingMode::HighThroughput,
3940                    cb_width,
3941                    cb_height,
3942                    SubBandType::LowHigh,
3943                )?,
3944                prepare_subband_cpu_quantized(
3945                    &level.hh,
3946                    level.high_width,
3947                    level.high_height,
3948                    step_sizes
3949                        .get(step_base + 2)
3950                        .ok_or("irreversible quantization step missing")?,
3951                    bit_depth,
3952                    guard_bits,
3953                    false,
3954                    BlockCodingMode::HighThroughput,
3955                    cb_width,
3956                    cb_height,
3957                    SubBandType::HighHigh,
3958                )?,
3959            ],
3960        });
3961    }
3962
3963    Ok(packets)
3964}
3965
3966fn copy_code_block_coefficients(
3967    quantized: &[i32],
3968    width: usize,
3969    x0: usize,
3970    y0: usize,
3971    cbw: usize,
3972    cbh: usize,
3973) -> Vec<i32> {
3974    let len = cbw * cbh;
3975    let start = y0 * width + x0;
3976    if cbw == width {
3977        return quantized[start..start + len].to_vec();
3978    }
3979
3980    let mut coefficients = Vec::with_capacity(len);
3981    for y in 0..cbh {
3982        let row_start = (y0 + y) * width + x0;
3983        coefficients.extend_from_slice(&quantized[row_start..row_start + cbw]);
3984    }
3985    coefficients
3986}
3987
3988fn prepare_subband(
3989    coefficients: &[f32],
3990    width: u32,
3991    height: u32,
3992    step_size: &QuantStepSize,
3993    bit_depth: u8,
3994    guard_bits: u8,
3995    reversible: bool,
3996    block_coding_mode: BlockCodingMode,
3997    cb_width: u32,
3998    cb_height: u32,
3999    sub_band_type: SubBandType,
4000    accelerator: &mut impl J2kEncodeStageAccelerator,
4001) -> Result<PreparedEncodeSubband, &'static str> {
4002    if width == 0 || height == 0 {
4003        return Ok(PreparedEncodeSubband {
4004            code_blocks: Vec::new(),
4005            preencoded_ht_code_blocks: None,
4006            num_cbs_x: 0,
4007            num_cbs_y: 0,
4008            code_block_width: cb_width,
4009            code_block_height: cb_height,
4010            width,
4011            height,
4012            sub_band_type,
4013            total_bitplanes: 0,
4014            block_coding_mode,
4015        });
4016    }
4017
4018    let range_bits = subband_range_bits(bit_depth, sub_band_type);
4019    debug_assert!(step_size.exponent <= u16::from(u8::MAX));
4020    let total_bitplanes = guard_bits
4021        .saturating_add(step_size.exponent as u8)
4022        .saturating_sub(1);
4023    let num_cbs_x = width.div_ceil(cb_width);
4024    let num_cbs_y = height.div_ceil(cb_height);
4025
4026    if block_coding_mode == BlockCodingMode::HighThroughput {
4027        if let Some(encoded) = accelerator.encode_ht_subband(J2kHtSubbandEncodeJob {
4028            coefficients,
4029            width,
4030            height,
4031            step_exponent: step_size.exponent,
4032            step_mantissa: step_size.mantissa,
4033            range_bits,
4034            reversible,
4035            code_block_width: cb_width,
4036            code_block_height: cb_height,
4037            total_bitplanes,
4038        })? {
4039            let expected_code_blocks = (num_cbs_x as usize)
4040                .checked_mul(num_cbs_y as usize)
4041                .ok_or("code-block count overflow")?;
4042            if encoded.len() != expected_code_blocks {
4043                return Err("accelerated HT subband code-block count mismatch");
4044            }
4045            return Ok(PreparedEncodeSubband {
4046                code_blocks: code_block_shapes(width, height, cb_width, cb_height)?,
4047                preencoded_ht_code_blocks: Some(encoded),
4048                num_cbs_x,
4049                num_cbs_y,
4050                code_block_width: cb_width,
4051                code_block_height: cb_height,
4052                width,
4053                height,
4054                sub_band_type,
4055                total_bitplanes,
4056                block_coding_mode,
4057            });
4058        }
4059    }
4060
4061    let quantized = match accelerator.encode_quantize_subband(J2kQuantizeSubbandJob {
4062        coefficients,
4063        step_exponent: step_size.exponent,
4064        step_mantissa: step_size.mantissa,
4065        range_bits,
4066        reversible,
4067    })? {
4068        Some(quantized) => {
4069            if quantized.len() != coefficients.len() {
4070                return Err("accelerated quantized subband length mismatch");
4071            }
4072            quantized
4073        }
4074        None => quantize::quantize_subband(coefficients, step_size, range_bits, reversible),
4075    };
4076
4077    // Split into code-blocks
4078    let mut code_blocks = Vec::with_capacity((num_cbs_x * num_cbs_y) as usize);
4079
4080    for cby in 0..num_cbs_y {
4081        for cbx in 0..num_cbs_x {
4082            let x0 = cbx * cb_width;
4083            let y0 = cby * cb_height;
4084            let x1 = (x0 + cb_width).min(width);
4085            let y1 = (y0 + cb_height).min(height);
4086            let cbw = x1 - x0;
4087            let cbh = y1 - y0;
4088
4089            let cb_coeffs = copy_code_block_coefficients(
4090                &quantized,
4091                width as usize,
4092                x0 as usize,
4093                y0 as usize,
4094                cbw as usize,
4095                cbh as usize,
4096            );
4097
4098            code_blocks.push(PreparedEncodeCodeBlock {
4099                coefficients: cb_coeffs,
4100                width: cbw,
4101                height: cbh,
4102            });
4103        }
4104    }
4105
4106    Ok(PreparedEncodeSubband {
4107        code_blocks,
4108        preencoded_ht_code_blocks: None,
4109        num_cbs_x,
4110        num_cbs_y,
4111        code_block_width: cb_width,
4112        code_block_height: cb_height,
4113        width,
4114        height,
4115        sub_band_type,
4116        total_bitplanes,
4117        block_coding_mode,
4118    })
4119}
4120
4121fn prepare_subband_cpu_quantized(
4122    coefficients: &[f32],
4123    width: u32,
4124    height: u32,
4125    step_size: &QuantStepSize,
4126    bit_depth: u8,
4127    guard_bits: u8,
4128    reversible: bool,
4129    block_coding_mode: BlockCodingMode,
4130    cb_width: u32,
4131    cb_height: u32,
4132    sub_band_type: SubBandType,
4133) -> Result<PreparedEncodeSubband, &'static str> {
4134    if width == 0 || height == 0 {
4135        return Ok(PreparedEncodeSubband {
4136            code_blocks: Vec::new(),
4137            preencoded_ht_code_blocks: None,
4138            num_cbs_x: 0,
4139            num_cbs_y: 0,
4140            code_block_width: cb_width,
4141            code_block_height: cb_height,
4142            width,
4143            height,
4144            sub_band_type,
4145            total_bitplanes: 0,
4146            block_coding_mode,
4147        });
4148    }
4149
4150    let range_bits = subband_range_bits(bit_depth, sub_band_type);
4151    debug_assert!(step_size.exponent <= u16::from(u8::MAX));
4152    let total_bitplanes = guard_bits
4153        .saturating_add(step_size.exponent as u8)
4154        .saturating_sub(1);
4155    let num_cbs_x = width.div_ceil(cb_width);
4156    let num_cbs_y = height.div_ceil(cb_height);
4157    let quantized = quantize::quantize_subband(coefficients, step_size, range_bits, reversible);
4158    let mut code_blocks = Vec::with_capacity((num_cbs_x * num_cbs_y) as usize);
4159
4160    for cby in 0..num_cbs_y {
4161        for cbx in 0..num_cbs_x {
4162            let x0 = cbx * cb_width;
4163            let y0 = cby * cb_height;
4164            let x1 = (x0 + cb_width).min(width);
4165            let y1 = (y0 + cb_height).min(height);
4166            let cbw = x1 - x0;
4167            let cbh = y1 - y0;
4168            let cb_coeffs = copy_code_block_coefficients(
4169                &quantized,
4170                width as usize,
4171                x0 as usize,
4172                y0 as usize,
4173                cbw as usize,
4174                cbh as usize,
4175            );
4176
4177            code_blocks.push(PreparedEncodeCodeBlock {
4178                coefficients: cb_coeffs,
4179                width: cbw,
4180                height: cbh,
4181            });
4182        }
4183    }
4184
4185    Ok(PreparedEncodeSubband {
4186        code_blocks,
4187        preencoded_ht_code_blocks: None,
4188        num_cbs_x,
4189        num_cbs_y,
4190        code_block_width: cb_width,
4191        code_block_height: cb_height,
4192        width,
4193        height,
4194        sub_band_type,
4195        total_bitplanes,
4196        block_coding_mode,
4197    })
4198}
4199
4200fn code_block_shapes(
4201    width: u32,
4202    height: u32,
4203    cb_width: u32,
4204    cb_height: u32,
4205) -> Result<Vec<PreparedEncodeCodeBlock>, &'static str> {
4206    let num_cbs_x = width.div_ceil(cb_width);
4207    let num_cbs_y = height.div_ceil(cb_height);
4208    let count = (num_cbs_x as usize)
4209        .checked_mul(num_cbs_y as usize)
4210        .ok_or("code-block count overflow")?;
4211    let mut code_blocks = Vec::with_capacity(count);
4212    for cby in 0..num_cbs_y {
4213        for cbx in 0..num_cbs_x {
4214            let x0 = cbx * cb_width;
4215            let y0 = cby * cb_height;
4216            let x1 = (x0 + cb_width).min(width);
4217            let y1 = (y0 + cb_height).min(height);
4218            code_blocks.push(PreparedEncodeCodeBlock {
4219                coefficients: Vec::new(),
4220                width: x1 - x0,
4221                height: y1 - y0,
4222            });
4223        }
4224    }
4225    Ok(code_blocks)
4226}
4227
4228fn subband_range_bits(bit_depth: u8, sub_band_type: SubBandType) -> u8 {
4229    let log_gain = match sub_band_type {
4230        SubBandType::LowLow => 0,
4231        SubBandType::LowHigh | SubBandType::HighLow => 1,
4232        SubBandType::HighHigh => 2,
4233    };
4234
4235    bit_depth.saturating_add(log_gain)
4236}
4237
4238fn encode_prepared_resolution_packets(
4239    prepared_packets: Vec<PreparedResolutionPacket>,
4240    accelerator: &mut impl J2kEncodeStageAccelerator,
4241) -> Result<Vec<ResolutionPacket>, &'static str> {
4242    let subband_counts: Vec<_> = prepared_packets
4243        .iter()
4244        .map(|packet| packet.subbands.len())
4245        .collect();
4246    let prepared_subbands: Vec<_> = prepared_packets
4247        .into_iter()
4248        .flat_map(|packet| packet.subbands)
4249        .collect();
4250    let mut encoded_subbands =
4251        encode_prepared_subbands(prepared_subbands, accelerator)?.into_iter();
4252
4253    subband_counts
4254        .into_iter()
4255        .map(|subband_count| {
4256            let mut subbands = Vec::with_capacity(subband_count);
4257            for _ in 0..subband_count {
4258                subbands.push(
4259                    encoded_subbands
4260                        .next()
4261                        .ok_or("encoded subband count mismatch")?,
4262                );
4263            }
4264            Ok(ResolutionPacket { subbands })
4265        })
4266        .collect()
4267}
4268
4269fn encode_prepared_resolution_packets_layered(
4270    prepared_packets: Vec<PreparedResolutionPacket>,
4271    num_layers: u8,
4272    progression_order: EncodeProgressionOrder,
4273    quality_layer_byte_targets: &[u64],
4274    accelerator: &mut impl J2kEncodeStageAccelerator,
4275) -> Result<(Vec<ResolutionPacket>, Vec<J2kPacketizationPacketDescriptor>), &'static str> {
4276    let layer_count = usize::from(num_layers);
4277    let mut layered_packets = Vec::with_capacity(prepared_packets.len());
4278    let mut classic_candidates = Vec::new();
4279    let mut classic_locations = Vec::new();
4280    let mut classic_block_index = 0usize;
4281    let mut ht_candidates = Vec::new();
4282    let mut ht_locations = Vec::new();
4283    let mut ht_block_index = 0usize;
4284
4285    for prepared_packet in prepared_packets {
4286        let packet_idx = layered_packets.len();
4287        let mut layered_packet = LayeredPreparedPacket {
4288            component: prepared_packet.component,
4289            resolution: prepared_packet.resolution,
4290            precinct: prepared_packet.precinct,
4291            subbands: Vec::with_capacity(prepared_packet.subbands.len()),
4292        };
4293
4294        for subband in prepared_packet.subbands {
4295            let subband_idx = layered_packet.subbands.len();
4296            let mut layered_subband = LayeredPreparedSubband {
4297                num_cbs_x: subband.num_cbs_x,
4298                num_cbs_y: subband.num_cbs_y,
4299                blocks: Vec::with_capacity(subband.code_blocks.len()),
4300            };
4301
4302            match subband.block_coding_mode {
4303                BlockCodingMode::Classic => {
4304                    for block in subband.code_blocks {
4305                        let block_idx = layered_subband.blocks.len();
4306                        let encoded = bitplane_encode::encode_code_block_segments_with_style(
4307                            &block.coefficients,
4308                            block.width,
4309                            block.height,
4310                            subband.sub_band_type,
4311                            subband.total_bitplanes,
4312                            &classic_multilayer_code_block_style(),
4313                        );
4314                        let segment_layers = if quality_layer_byte_targets.is_empty() {
4315                            classic_unbudgeted_segment_layers(&encoded, num_layers)?
4316                        } else {
4317                            for (segment_idx, segment) in encoded.segments.iter().enumerate() {
4318                                classic_candidates.push(ClassicSegmentAssignmentCandidate {
4319                                    block_index: classic_block_index,
4320                                    segment_index: segment_idx,
4321                                    rate: u64::from(segment.data_length),
4322                                    distortion_delta: segment.distortion_delta,
4323                                });
4324                                classic_locations.push(ClassicSegmentLocation {
4325                                    packet_idx,
4326                                    subband_idx,
4327                                    block_idx,
4328                                    segment_idx,
4329                                });
4330                            }
4331                            vec![layer_count.saturating_sub(1); encoded.segments.len()]
4332                        };
4333                        layered_subband.blocks.push(LayeredPreparedBlock::Classic {
4334                            encoded,
4335                            segment_layers,
4336                        });
4337                        classic_block_index = classic_block_index
4338                            .checked_add(1)
4339                            .ok_or("classic PCRD block index overflow")?;
4340                    }
4341                }
4342                BlockCodingMode::HighThroughput => {
4343                    let encoded_blocks =
4344                        encode_all_ht_code_blocks(core::slice::from_ref(&subband), accelerator)?;
4345                    let block_count = encoded_blocks.len();
4346                    for (block_idx, encoded) in encoded_blocks.into_iter().enumerate() {
4347                        let target_layer = if quality_layer_byte_targets.is_empty() {
4348                            ht_target_layer(block_idx, block_count, layer_count)?
4349                        } else {
4350                            ht_candidates.push(HtSegmentAssignmentCandidate {
4351                                block_index: ht_block_index,
4352                                rate: u64::try_from(encoded.data.len())
4353                                    .map_err(|_| "HTJ2K packet contribution length exceeds u64")?,
4354                            });
4355                            ht_locations.push(HtSegmentLocation {
4356                                packet_idx,
4357                                subband_idx,
4358                                block_idx: layered_subband.blocks.len(),
4359                            });
4360                            layer_count.saturating_sub(1)
4361                        };
4362                        layered_subband
4363                            .blocks
4364                            .push(LayeredPreparedBlock::HighThroughput {
4365                                encoded,
4366                                target_layer,
4367                            });
4368                        ht_block_index = ht_block_index
4369                            .checked_add(1)
4370                            .ok_or("HTJ2K segment block index overflow")?;
4371                    }
4372                }
4373            }
4374
4375            layered_packet.subbands.push(layered_subband);
4376        }
4377
4378        layered_packets.push(layered_packet);
4379    }
4380
4381    if !quality_layer_byte_targets.is_empty() {
4382        let assignments = assign_classic_segment_layers_by_slope(
4383            &classic_candidates,
4384            layer_count,
4385            quality_layer_byte_targets,
4386        )?;
4387        for (assignment_idx, layer) in assignments.into_iter().enumerate() {
4388            let location = classic_locations
4389                .get(assignment_idx)
4390                .ok_or("classic PCRD assignment location mismatch")?;
4391            let block = layered_packets
4392                .get_mut(location.packet_idx)
4393                .ok_or("classic PCRD packet index mismatch")?
4394                .subbands
4395                .get_mut(location.subband_idx)
4396                .ok_or("classic PCRD subband index mismatch")?
4397                .blocks
4398                .get_mut(location.block_idx)
4399                .ok_or("classic PCRD block index mismatch")?;
4400            let LayeredPreparedBlock::Classic { segment_layers, .. } = block else {
4401                return Err("classic PCRD assignment referenced HT block");
4402            };
4403            let segment_layer = segment_layers
4404                .get_mut(location.segment_idx)
4405                .ok_or("classic PCRD segment index mismatch")?;
4406            *segment_layer = layer;
4407        }
4408        enforce_classic_segment_layer_monotonicity(&mut layered_packets);
4409    }
4410    if !quality_layer_byte_targets.is_empty() {
4411        let assignments = assign_ht_segment_layers_by_budget(
4412            &ht_candidates,
4413            layer_count,
4414            quality_layer_byte_targets,
4415        )?;
4416        for (assignment_idx, layer) in assignments.into_iter().enumerate() {
4417            let location = ht_locations
4418                .get(assignment_idx)
4419                .ok_or("HTJ2K segment assignment location mismatch")?;
4420            let block = layered_packets
4421                .get_mut(location.packet_idx)
4422                .ok_or("HTJ2K packet index mismatch")?
4423                .subbands
4424                .get_mut(location.subband_idx)
4425                .ok_or("HTJ2K subband index mismatch")?
4426                .blocks
4427                .get_mut(location.block_idx)
4428                .ok_or("HTJ2K block index mismatch")?;
4429            let LayeredPreparedBlock::HighThroughput { target_layer, .. } = block else {
4430                return Err("HTJ2K segment assignment referenced classic block");
4431            };
4432            *target_layer = layer;
4433        }
4434    }
4435
4436    let mut resolution_packets = Vec::with_capacity(layered_packets.len() * layer_count);
4437    let mut descriptors = Vec::with_capacity(layered_packets.len() * layer_count);
4438    for (state_index, layered_packet) in layered_packets.into_iter().enumerate() {
4439        let mut layer_packets: Vec<_> = (0..layer_count)
4440            .map(|_| ResolutionPacket {
4441                subbands: Vec::with_capacity(layered_packet.subbands.len()),
4442            })
4443            .collect();
4444
4445        for subband in layered_packet.subbands {
4446            let mut layer_subbands: Vec<_> = (0..layer_count)
4447                .map(|_| SubbandPrecinct {
4448                    code_blocks: Vec::with_capacity(subband.blocks.len()),
4449                    num_cbs_x: subband.num_cbs_x,
4450                    num_cbs_y: subband.num_cbs_y,
4451                })
4452                .collect();
4453
4454            for block in subband.blocks {
4455                let contributions = match block {
4456                    LayeredPreparedBlock::Classic {
4457                        encoded,
4458                        segment_layers,
4459                    } => classic_layer_contributions(encoded, num_layers, &segment_layers)?,
4460                    LayeredPreparedBlock::HighThroughput {
4461                        encoded,
4462                        target_layer,
4463                    } => ht_layer_contributions(encoded, num_layers, target_layer)?,
4464                };
4465                for (layer_idx, contribution) in contributions.into_iter().enumerate() {
4466                    layer_subbands[layer_idx].code_blocks.push(contribution);
4467                }
4468            }
4469
4470            for (layer_packet, layer_subband) in layer_packets.iter_mut().zip(layer_subbands) {
4471                layer_packet.subbands.push(layer_subband);
4472            }
4473        }
4474
4475        let state_index =
4476            u32::try_from(state_index).map_err(|_| "packet descriptor state index exceeds u32")?;
4477        for (layer_idx, layer_packet) in layer_packets.into_iter().enumerate() {
4478            let packet_index = u32::try_from(resolution_packets.len())
4479                .map_err(|_| "packet descriptor index exceeds u32")?;
4480            resolution_packets.push(layer_packet);
4481            descriptors.push(J2kPacketizationPacketDescriptor {
4482                packet_index,
4483                state_index,
4484                layer: u8::try_from(layer_idx).map_err(|_| "quality layer index exceeds u8")?,
4485                resolution: layered_packet.resolution,
4486                component: layered_packet.component,
4487                precinct: layered_packet.precinct,
4488            });
4489        }
4490    }
4491
4492    sort_packet_descriptors_for_progression(&mut descriptors, progression_order);
4493
4494    Ok((resolution_packets, descriptors))
4495}
4496
4497fn sort_packet_descriptors_for_progression(
4498    descriptors: &mut [J2kPacketizationPacketDescriptor],
4499    progression_order: EncodeProgressionOrder,
4500) {
4501    match progression_order {
4502        EncodeProgressionOrder::Lrcp => descriptors.sort_by_key(|descriptor| {
4503            (
4504                descriptor.layer,
4505                descriptor.resolution,
4506                descriptor.component,
4507                descriptor.precinct,
4508            )
4509        }),
4510        EncodeProgressionOrder::Rlcp => descriptors.sort_by_key(|descriptor| {
4511            (
4512                descriptor.resolution,
4513                descriptor.layer,
4514                descriptor.component,
4515                descriptor.precinct,
4516            )
4517        }),
4518        EncodeProgressionOrder::Rpcl => descriptors.sort_by_key(|descriptor| {
4519            (
4520                descriptor.resolution,
4521                descriptor.precinct,
4522                descriptor.component,
4523                descriptor.layer,
4524            )
4525        }),
4526        EncodeProgressionOrder::Pcrl => descriptors.sort_by_key(|descriptor| {
4527            (
4528                descriptor.precinct,
4529                descriptor.component,
4530                descriptor.resolution,
4531                descriptor.layer,
4532            )
4533        }),
4534        EncodeProgressionOrder::Cprl => descriptors.sort_by_key(|descriptor| {
4535            (
4536                descriptor.component,
4537                descriptor.precinct,
4538                descriptor.resolution,
4539                descriptor.layer,
4540            )
4541        }),
4542    }
4543}
4544
4545fn classic_multilayer_code_block_style() -> CodeBlockStyle {
4546    CodeBlockStyle {
4547        termination_on_each_pass: true,
4548        ..CodeBlockStyle::default()
4549    }
4550}
4551
4552struct LayeredPreparedPacket {
4553    component: u8,
4554    resolution: u32,
4555    precinct: u64,
4556    subbands: Vec<LayeredPreparedSubband>,
4557}
4558
4559struct LayeredPreparedSubband {
4560    num_cbs_x: u32,
4561    num_cbs_y: u32,
4562    blocks: Vec<LayeredPreparedBlock>,
4563}
4564
4565enum LayeredPreparedBlock {
4566    Classic {
4567        encoded: bitplane_encode::EncodedCodeBlockWithSegments,
4568        segment_layers: Vec<usize>,
4569    },
4570    HighThroughput {
4571        encoded: bitplane_encode::EncodedCodeBlock,
4572        target_layer: usize,
4573    },
4574}
4575
4576#[derive(Debug, Clone, Copy, PartialEq)]
4577struct ClassicSegmentAssignmentCandidate {
4578    block_index: usize,
4579    segment_index: usize,
4580    rate: u64,
4581    distortion_delta: f64,
4582}
4583
4584#[derive(Debug, Clone, Copy)]
4585struct ClassicSegmentLocation {
4586    packet_idx: usize,
4587    subband_idx: usize,
4588    block_idx: usize,
4589    segment_idx: usize,
4590}
4591
4592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4593struct HtSegmentAssignmentCandidate {
4594    block_index: usize,
4595    rate: u64,
4596}
4597
4598#[derive(Debug, Clone, Copy)]
4599struct HtSegmentLocation {
4600    packet_idx: usize,
4601    subband_idx: usize,
4602    block_idx: usize,
4603}
4604
4605struct ClassicLayerBudgetAllocator {
4606    cumulative_targets: Vec<u64>,
4607    cumulative_used: Vec<u64>,
4608}
4609
4610impl ClassicLayerBudgetAllocator {
4611    fn new(cumulative_targets: &[u64], layer_count: usize) -> Result<Self, &'static str> {
4612        if cumulative_targets.is_empty() {
4613            return Ok(Self {
4614                cumulative_targets: Vec::new(),
4615                cumulative_used: Vec::new(),
4616            });
4617        }
4618        if cumulative_targets.len() != layer_count {
4619            return Err("quality layer byte target count must match quality layer count");
4620        }
4621        if cumulative_targets.windows(2).any(|pair| pair[0] > pair[1]) {
4622            return Err("quality layer byte targets must be cumulative and monotonic");
4623        }
4624        Ok(Self {
4625            cumulative_targets: cumulative_targets
4626                .iter()
4627                .map(|&target| target.saturating_add(classic_rate_target_tolerance(target)))
4628                .collect(),
4629            cumulative_used: vec![0; layer_count],
4630        })
4631    }
4632
4633    fn is_budgeted(&self) -> bool {
4634        !self.cumulative_targets.is_empty()
4635    }
4636
4637    fn assign_segment(
4638        &mut self,
4639        min_layer: usize,
4640        data_length: u64,
4641    ) -> Result<usize, &'static str> {
4642        if !self.is_budgeted() {
4643            return Ok(min_layer);
4644        }
4645
4646        let rate = data_length;
4647        let last_layer = self
4648            .cumulative_targets
4649            .len()
4650            .checked_sub(1)
4651            .ok_or("quality layer target count underflow")?;
4652        for layer_idx in min_layer..last_layer {
4653            if self.layer_can_accept(layer_idx, rate)? {
4654                self.record_segment(layer_idx, rate)?;
4655                return Ok(layer_idx);
4656            }
4657        }
4658        self.record_segment(last_layer, rate)?;
4659        Ok(last_layer)
4660    }
4661
4662    fn layer_can_accept(&self, layer_idx: usize, rate: u64) -> Result<bool, &'static str> {
4663        for cumulative_idx in layer_idx..self.cumulative_targets.len() {
4664            let used = self.cumulative_used[cumulative_idx]
4665                .checked_add(rate)
4666                .ok_or("quality layer byte budget overflow")?;
4667            if used > self.cumulative_targets[cumulative_idx] {
4668                return Ok(false);
4669            }
4670        }
4671        Ok(true)
4672    }
4673
4674    fn record_segment(&mut self, layer_idx: usize, rate: u64) -> Result<(), &'static str> {
4675        for used in &mut self.cumulative_used[layer_idx..] {
4676            *used = used
4677                .checked_add(rate)
4678                .ok_or("quality layer byte budget overflow")?;
4679        }
4680        Ok(())
4681    }
4682}
4683
4684fn classic_rate_target_tolerance(target: u64) -> u64 {
4685    (target / 100).max(512)
4686}
4687
4688fn assign_classic_segment_layers_by_slope(
4689    candidates: &[ClassicSegmentAssignmentCandidate],
4690    layer_count: usize,
4691    cumulative_targets: &[u64],
4692) -> Result<Vec<usize>, &'static str> {
4693    let mut allocator = ClassicLayerBudgetAllocator::new(cumulative_targets, layer_count)?;
4694    if candidates.is_empty() {
4695        return Ok(Vec::new());
4696    }
4697
4698    let block_count = candidates
4699        .iter()
4700        .map(|candidate| candidate.block_index)
4701        .max()
4702        .and_then(|max| max.checked_add(1))
4703        .ok_or("classic PCRD block count overflow")?;
4704    let mut block_candidates = vec![Vec::new(); block_count];
4705    for (candidate_idx, candidate) in candidates.iter().enumerate() {
4706        block_candidates
4707            .get_mut(candidate.block_index)
4708            .ok_or("classic PCRD block index mismatch")?
4709            .push(candidate_idx);
4710    }
4711    for block in &mut block_candidates {
4712        block.sort_by_key(|&idx| candidates[idx].segment_index);
4713    }
4714
4715    let mut block_min_layers = vec![0usize; block_count];
4716    let mut assignments = vec![layer_count.saturating_sub(1); candidates.len()];
4717    let mut next_block_segment = vec![0usize; block_count];
4718    let mut remaining = candidates.len();
4719    while remaining > 0 {
4720        let candidate_idx = block_candidates
4721            .iter()
4722            .enumerate()
4723            .filter_map(|(block_idx, block)| block.get(next_block_segment[block_idx]).copied())
4724            .min_by(|&left, &right| compare_classic_segment_candidates(candidates, left, right))
4725            .ok_or("classic PCRD candidate queue underflow")?;
4726        let candidate = candidates[candidate_idx];
4727        let min_layer = *block_min_layers
4728            .get(candidate.block_index)
4729            .ok_or("classic PCRD block index mismatch")?;
4730        let layer = allocator.assign_segment(min_layer, candidate.rate)?;
4731        assignments[candidate_idx] = layer;
4732        if let Some(block_layer) = block_min_layers.get_mut(candidate.block_index) {
4733            *block_layer = layer;
4734        }
4735        if let Some(next) = next_block_segment.get_mut(candidate.block_index) {
4736            *next = next
4737                .checked_add(1)
4738                .ok_or("classic PCRD segment index overflow")?;
4739        }
4740        remaining -= 1;
4741    }
4742
4743    enforce_classic_assignment_monotonicity(candidates, &mut assignments);
4744    Ok(assignments)
4745}
4746
4747fn compare_classic_segment_candidates(
4748    candidates: &[ClassicSegmentAssignmentCandidate],
4749    left: usize,
4750    right: usize,
4751) -> Ordering {
4752    let left_candidate = candidates[left];
4753    let right_candidate = candidates[right];
4754    pcrd_slope(right_candidate)
4755        .partial_cmp(&pcrd_slope(left_candidate))
4756        .unwrap_or(Ordering::Equal)
4757        .then_with(|| left_candidate.block_index.cmp(&right_candidate.block_index))
4758        .then_with(|| {
4759            left_candidate
4760                .segment_index
4761                .cmp(&right_candidate.segment_index)
4762        })
4763}
4764
4765fn pcrd_slope(candidate: ClassicSegmentAssignmentCandidate) -> f64 {
4766    if candidate.rate == 0 {
4767        return f64::INFINITY;
4768    }
4769    candidate.distortion_delta / candidate.rate as f64
4770}
4771
4772fn enforce_classic_assignment_monotonicity(
4773    candidates: &[ClassicSegmentAssignmentCandidate],
4774    assignments: &mut [usize],
4775) {
4776    let mut order: Vec<_> = (0..candidates.len()).collect();
4777    order.sort_by_key(|&idx| (candidates[idx].block_index, candidates[idx].segment_index));
4778    let mut current_block = None;
4779    let mut min_layer = 0usize;
4780    for idx in order {
4781        if current_block != Some(candidates[idx].block_index) {
4782            current_block = Some(candidates[idx].block_index);
4783            min_layer = 0;
4784        }
4785        if assignments[idx] < min_layer {
4786            assignments[idx] = min_layer;
4787        }
4788        min_layer = assignments[idx];
4789    }
4790}
4791
4792fn enforce_classic_segment_layer_monotonicity(layered_packets: &mut [LayeredPreparedPacket]) {
4793    for packet in layered_packets {
4794        for subband in &mut packet.subbands {
4795            for block in &mut subband.blocks {
4796                if let LayeredPreparedBlock::Classic { segment_layers, .. } = block {
4797                    let mut min_layer = 0usize;
4798                    for layer in segment_layers {
4799                        if *layer < min_layer {
4800                            *layer = min_layer;
4801                        }
4802                        min_layer = *layer;
4803                    }
4804                }
4805            }
4806        }
4807    }
4808}
4809
4810fn assign_ht_segment_layers_by_budget(
4811    candidates: &[HtSegmentAssignmentCandidate],
4812    layer_count: usize,
4813    cumulative_targets: &[u64],
4814) -> Result<Vec<usize>, &'static str> {
4815    let mut allocator = ClassicLayerBudgetAllocator::new(cumulative_targets, layer_count)?;
4816    let mut assignments = vec![layer_count.saturating_sub(1); candidates.len()];
4817    let mut candidate_order: Vec<_> = (0..candidates.len()).collect();
4818    candidate_order.sort_by_key(|&idx| candidates[idx].block_index);
4819
4820    for candidate_idx in candidate_order {
4821        let candidate = candidates
4822            .get(candidate_idx)
4823            .ok_or("HTJ2K segment candidate index mismatch")?;
4824        assignments[candidate_idx] = allocator.assign_segment(0, candidate.rate)?;
4825    }
4826
4827    Ok(assignments)
4828}
4829
4830fn classic_unbudgeted_segment_layers(
4831    encoded: &bitplane_encode::EncodedCodeBlockWithSegments,
4832    num_layers: u8,
4833) -> Result<Vec<usize>, &'static str> {
4834    let mut segment_layers = Vec::with_capacity(encoded.segments.len());
4835    for segment in &encoded.segments {
4836        let mut assigned = None;
4837        for layer_idx in 0..usize::from(num_layers) {
4838            let previous_pass =
4839                previous_layer_pass_count(encoded.num_coding_passes, layer_idx, num_layers)?;
4840            let cumulative_passes = if layer_idx + 1 == usize::from(num_layers) {
4841                encoded.num_coding_passes
4842            } else {
4843                layer_pass_count(encoded.num_coding_passes, layer_idx + 1, num_layers)?
4844            };
4845            if segment.start_coding_pass >= previous_pass
4846                && segment.end_coding_pass <= cumulative_passes
4847            {
4848                assigned = Some(layer_idx);
4849                break;
4850            }
4851        }
4852        segment_layers.push(
4853            assigned.ok_or("classic quality layer split must align to terminated coding passes")?,
4854        );
4855    }
4856    Ok(segment_layers)
4857}
4858
4859fn classic_layer_contributions(
4860    encoded: bitplane_encode::EncodedCodeBlockWithSegments,
4861    num_layers: u8,
4862    segment_layers: &[usize],
4863) -> Result<Vec<CodeBlockPacketData>, &'static str> {
4864    let layer_count = usize::from(num_layers);
4865    if segment_layers.len() != encoded.segments.len() {
4866        return Err("classic PCRD segment assignment count mismatch");
4867    }
4868    if segment_layers.iter().any(|&layer| layer >= layer_count) {
4869        return Err("classic PCRD segment layer exceeds layer count");
4870    }
4871    let mut contributions = Vec::with_capacity(layer_count);
4872
4873    for layer_idx in 0..layer_count {
4874        let mut data = Vec::new();
4875        let mut classic_segment_lengths = Vec::new();
4876        let mut contribution_passes = 0u8;
4877
4878        for (segment_idx, segment) in encoded.segments.iter().enumerate() {
4879            if segment_layers[segment_idx] != layer_idx {
4880                continue;
4881            }
4882            let start = usize::try_from(segment.data_offset)
4883                .map_err(|_| "classic code-block segment offset overflow")?;
4884            let len = usize::try_from(segment.data_length)
4885                .map_err(|_| "classic code-block segment length overflow")?;
4886            let end = start
4887                .checked_add(len)
4888                .ok_or("classic code-block segment range overflow")?;
4889            data.extend_from_slice(
4890                encoded
4891                    .data
4892                    .get(start..end)
4893                    .ok_or("classic code-block segment range invalid")?,
4894            );
4895            classic_segment_lengths.push(segment.data_length);
4896            contribution_passes = contribution_passes
4897                .checked_add(segment.end_coding_pass - segment.start_coding_pass)
4898                .ok_or("classic code-block contribution pass count overflow")?;
4899        }
4900
4901        contributions.push(CodeBlockPacketData {
4902            data,
4903            ht_cleanup_length: 0,
4904            ht_refinement_length: 0,
4905            num_coding_passes: contribution_passes,
4906            classic_segment_lengths,
4907            num_zero_bitplanes: encoded.num_zero_bitplanes,
4908            previously_included: false,
4909            l_block: 3,
4910            block_coding_mode: BlockCodingMode::Classic,
4911        });
4912    }
4913
4914    Ok(contributions)
4915}
4916
4917fn layer_pass_count(
4918    num_coding_passes: u8,
4919    layer_count: usize,
4920    num_layers: u8,
4921) -> Result<u8, &'static str> {
4922    let numerator = u32::from(num_coding_passes)
4923        .checked_mul(u32::try_from(layer_count).map_err(|_| "layer index overflow")?)
4924        .ok_or("quality layer pass allocation overflow")?;
4925    numerator
4926        .div_ceil(u32::from(num_layers))
4927        .try_into()
4928        .map_err(|_| "quality layer pass allocation overflow")
4929}
4930
4931fn previous_layer_pass_count(
4932    num_coding_passes: u8,
4933    layer_idx: usize,
4934    num_layers: u8,
4935) -> Result<u8, &'static str> {
4936    if layer_idx == 0 {
4937        Ok(0)
4938    } else {
4939        layer_pass_count(num_coding_passes, layer_idx, num_layers)
4940    }
4941}
4942
4943fn ht_target_layer(
4944    block_idx: usize,
4945    block_count: usize,
4946    layer_count: usize,
4947) -> Result<usize, &'static str> {
4948    if block_count == 0 || layer_count == 0 {
4949        return Err("HTJ2K layer allocation requires non-empty inputs");
4950    }
4951    Ok(block_idx
4952        .checked_mul(layer_count)
4953        .ok_or("HTJ2K layer allocation overflow")?
4954        / block_count)
4955}
4956
4957fn ht_layer_contributions(
4958    encoded: bitplane_encode::EncodedCodeBlock,
4959    num_layers: u8,
4960    target_layer: usize,
4961) -> Result<Vec<CodeBlockPacketData>, &'static str> {
4962    let layer_count = usize::from(num_layers);
4963    if target_layer >= layer_count {
4964        return Err("HTJ2K target layer out of range");
4965    }
4966
4967    let mut data = Some(encoded.data);
4968    let mut contributions = Vec::with_capacity(layer_count);
4969    for layer_idx in 0..layer_count {
4970        let include = layer_idx == target_layer && encoded.num_coding_passes > 0;
4971        let layer_data = if include {
4972            data.take()
4973                .ok_or("HTJ2K layer contribution data was already consumed")?
4974        } else {
4975            Vec::new()
4976        };
4977        contributions.push(CodeBlockPacketData {
4978            data: layer_data,
4979            ht_cleanup_length: if include {
4980                encoded.ht_cleanup_length
4981            } else {
4982                0
4983            },
4984            ht_refinement_length: if include {
4985                encoded.ht_refinement_length
4986            } else {
4987                0
4988            },
4989            num_coding_passes: if include {
4990                encoded.num_coding_passes
4991            } else {
4992                0
4993            },
4994            classic_segment_lengths: Vec::new(),
4995            num_zero_bitplanes: encoded.num_zero_bitplanes,
4996            previously_included: false,
4997            l_block: 3,
4998            block_coding_mode: BlockCodingMode::HighThroughput,
4999        });
5000    }
5001
5002    Ok(contributions)
5003}
5004
5005fn encode_prepared_subbands(
5006    prepared_subbands: Vec<PreparedEncodeSubband>,
5007    accelerator: &mut impl J2kEncodeStageAccelerator,
5008) -> Result<Vec<SubbandPrecinct>, &'static str> {
5009    let block_coding_mode = prepared_subbands
5010        .iter()
5011        .find(|subband| !subband.code_blocks.is_empty())
5012        .map(|subband| subband.block_coding_mode);
5013    let encoded_blocks = match block_coding_mode {
5014        Some(BlockCodingMode::HighThroughput) => {
5015            encode_all_ht_code_blocks(&prepared_subbands, accelerator)?
5016        }
5017        Some(BlockCodingMode::Classic) => {
5018            encode_all_tier1_code_blocks(&prepared_subbands, accelerator)?
5019        }
5020        None => Vec::new(),
5021    };
5022
5023    let mut encoded_iter = encoded_blocks.into_iter();
5024    let mut precincts = Vec::with_capacity(prepared_subbands.len());
5025    for subband in prepared_subbands {
5026        let mut code_blocks = Vec::with_capacity(subband.code_blocks.len());
5027        for _ in 0..subband.code_blocks.len() {
5028            let encoded = encoded_iter
5029                .next()
5030                .ok_or("encoded code-block count mismatch")?;
5031            code_blocks.push(CodeBlockPacketData {
5032                data: encoded.data,
5033                ht_cleanup_length: if subband.block_coding_mode == BlockCodingMode::HighThroughput {
5034                    encoded.ht_cleanup_length
5035                } else {
5036                    0
5037                },
5038                ht_refinement_length: if subband.block_coding_mode
5039                    == BlockCodingMode::HighThroughput
5040                {
5041                    encoded.ht_refinement_length
5042                } else {
5043                    0
5044                },
5045                num_coding_passes: encoded.num_coding_passes,
5046                classic_segment_lengths: Vec::new(),
5047                num_zero_bitplanes: encoded.num_zero_bitplanes,
5048                previously_included: false,
5049                l_block: 3,
5050                block_coding_mode: subband.block_coding_mode,
5051            });
5052        }
5053        precincts.push(SubbandPrecinct {
5054            code_blocks,
5055            num_cbs_x: subband.num_cbs_x,
5056            num_cbs_y: subband.num_cbs_y,
5057        });
5058    }
5059    if encoded_iter.next().is_some() {
5060        return Err("encoded code-block count mismatch");
5061    }
5062
5063    Ok(precincts)
5064}
5065
5066fn encode_all_ht_code_blocks(
5067    prepared_subbands: &[PreparedEncodeSubband],
5068    accelerator: &mut impl J2kEncodeStageAccelerator,
5069) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5070    if prepared_subbands.iter().all(|subband| {
5071        subband.code_blocks.is_empty() || subband.preencoded_ht_code_blocks.is_some()
5072    }) {
5073        let total_blocks = prepared_subbands
5074            .iter()
5075            .map(|subband| subband.code_blocks.len())
5076            .sum();
5077        let mut encoded = Vec::with_capacity(total_blocks);
5078        for subband in prepared_subbands {
5079            if let Some(blocks) = &subband.preencoded_ht_code_blocks {
5080                if blocks.len() != subband.code_blocks.len() {
5081                    return Err("preencoded HT subband code-block count mismatch");
5082                }
5083                encoded.extend(
5084                    blocks
5085                        .iter()
5086                        .cloned()
5087                        .map(ht_encoded_code_block_from_accelerator),
5088                );
5089            }
5090        }
5091        return Ok(encoded);
5092    }
5093    if prepared_subbands
5094        .iter()
5095        .any(|subband| subband.preencoded_ht_code_blocks.is_some())
5096    {
5097        return Err("mixed preencoded and quantized HT subbands are unsupported");
5098    }
5099
5100    let jobs: Vec<_> = prepared_subbands
5101        .iter()
5102        .flat_map(|subband| {
5103            subband
5104                .code_blocks
5105                .iter()
5106                .map(move |block| crate::J2kHtCodeBlockEncodeJob {
5107                    coefficients: &block.coefficients,
5108                    width: block.width,
5109                    height: block.height,
5110                    total_bitplanes: subband.total_bitplanes,
5111                    target_coding_passes: 1,
5112                })
5113        })
5114        .collect();
5115
5116    if let Some(encoded) = accelerator.encode_ht_code_blocks(&jobs)? {
5117        if encoded.len() != jobs.len() {
5118            return Err("accelerated HT code-block batch length mismatch");
5119        }
5120        return Ok(encoded
5121            .into_iter()
5122            .map(ht_encoded_code_block_from_accelerator)
5123            .collect());
5124    }
5125
5126    if accelerator.prefer_parallel_cpu_code_block_fallback() {
5127        if jobs.len() < HT_CPU_PARALLEL_FALLBACK_MIN_JOBS {
5128            return encode_all_ht_code_blocks_serial_cpu(&jobs);
5129        }
5130        return encode_all_ht_code_blocks_parallel(&jobs);
5131    }
5132
5133    jobs.iter()
5134        .map(|job| {
5135            encode_ht_code_block(
5136                job.coefficients,
5137                job.width,
5138                job.height,
5139                job.total_bitplanes,
5140                accelerator,
5141            )
5142        })
5143        .collect()
5144}
5145
5146fn encode_all_tier1_code_blocks(
5147    prepared_subbands: &[PreparedEncodeSubband],
5148    accelerator: &mut impl J2kEncodeStageAccelerator,
5149) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5150    let style = default_public_code_block_style();
5151    let jobs: Vec<_> = prepared_subbands
5152        .iter()
5153        .flat_map(|subband| {
5154            let public_sub_band_type = public_sub_band_type(subband.sub_band_type);
5155            subband
5156                .code_blocks
5157                .iter()
5158                .map(move |block| J2kTier1CodeBlockEncodeJob {
5159                    coefficients: &block.coefficients,
5160                    width: block.width,
5161                    height: block.height,
5162                    sub_band_type: public_sub_band_type,
5163                    total_bitplanes: subband.total_bitplanes,
5164                    style,
5165                })
5166        })
5167        .collect();
5168
5169    if let Some(encoded) = accelerator.encode_tier1_code_blocks(&jobs)? {
5170        if encoded.len() != jobs.len() {
5171            return Err("accelerated classic code-block batch length mismatch");
5172        }
5173        return Ok(encoded
5174            .into_iter()
5175            .map(encoded_code_block_from_accelerator)
5176            .collect());
5177    }
5178
5179    if accelerator.prefer_parallel_cpu_code_block_fallback() {
5180        return encode_all_tier1_code_blocks_parallel(&jobs);
5181    }
5182
5183    let mut encoded = Vec::with_capacity(jobs.len());
5184    for subband in prepared_subbands {
5185        for block in &subband.code_blocks {
5186            encoded.push(encode_tier1_code_block(
5187                &block.coefficients,
5188                block.width,
5189                block.height,
5190                subband.sub_band_type,
5191                subband.total_bitplanes,
5192                accelerator,
5193            )?);
5194        }
5195    }
5196    Ok(encoded)
5197}
5198
5199fn encode_all_ht_code_blocks_serial_cpu(
5200    jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
5201) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5202    if jobs.iter().any(|job| job.target_coding_passes != 1) {
5203        return Err("CPU HTJ2K code-block fallback supports cleanup-only encode");
5204    }
5205    jobs.iter()
5206        .map(|job| {
5207            ht_block_encode::encode_code_block(
5208                job.coefficients,
5209                job.width,
5210                job.height,
5211                job.total_bitplanes,
5212            )
5213        })
5214        .collect()
5215}
5216
5217#[cfg(feature = "parallel")]
5218fn encode_all_ht_code_blocks_parallel(
5219    jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
5220) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5221    if jobs.iter().any(|job| job.target_coding_passes != 1) {
5222        return Err("CPU HTJ2K code-block fallback supports cleanup-only encode");
5223    }
5224    jobs.par_iter()
5225        .map(|job| {
5226            ht_block_encode::encode_code_block(
5227                job.coefficients,
5228                job.width,
5229                job.height,
5230                job.total_bitplanes,
5231            )
5232        })
5233        .collect()
5234}
5235
5236#[cfg(not(feature = "parallel"))]
5237fn encode_all_ht_code_blocks_parallel(
5238    jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
5239) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5240    if jobs.iter().any(|job| job.target_coding_passes != 1) {
5241        return Err("CPU HTJ2K code-block fallback supports cleanup-only encode");
5242    }
5243    jobs.iter()
5244        .map(|job| {
5245            ht_block_encode::encode_code_block(
5246                job.coefficients,
5247                job.width,
5248                job.height,
5249                job.total_bitplanes,
5250            )
5251        })
5252        .collect()
5253}
5254
5255#[cfg(feature = "parallel")]
5256fn encode_all_tier1_code_blocks_parallel(
5257    jobs: &[J2kTier1CodeBlockEncodeJob<'_>],
5258) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5259    jobs.par_iter()
5260        .map(|job| {
5261            Ok(bitplane_encode::encode_code_block(
5262                job.coefficients,
5263                job.width,
5264                job.height,
5265                internal_sub_band_type(job.sub_band_type),
5266                job.total_bitplanes,
5267            ))
5268        })
5269        .collect()
5270}
5271
5272#[cfg(not(feature = "parallel"))]
5273fn encode_all_tier1_code_blocks_parallel(
5274    jobs: &[J2kTier1CodeBlockEncodeJob<'_>],
5275) -> Result<Vec<bitplane_encode::EncodedCodeBlock>, &'static str> {
5276    jobs.iter()
5277        .map(|job| {
5278            Ok(bitplane_encode::encode_code_block(
5279                job.coefficients,
5280                job.width,
5281                job.height,
5282                internal_sub_band_type(job.sub_band_type),
5283                job.total_bitplanes,
5284            ))
5285        })
5286        .collect()
5287}
5288
5289fn encode_ht_code_block(
5290    coefficients: &[i32],
5291    width: u32,
5292    height: u32,
5293    total_bitplanes: u8,
5294    accelerator: &mut impl J2kEncodeStageAccelerator,
5295) -> Result<bitplane_encode::EncodedCodeBlock, &'static str> {
5296    if let Some(encoded) = accelerator.encode_ht_code_block(crate::J2kHtCodeBlockEncodeJob {
5297        coefficients,
5298        width,
5299        height,
5300        total_bitplanes,
5301        target_coding_passes: 1,
5302    })? {
5303        return Ok(ht_encoded_code_block_from_accelerator(encoded));
5304    }
5305
5306    ht_block_encode::encode_code_block(coefficients, width, height, total_bitplanes)
5307}
5308
5309fn ht_encoded_code_block_from_accelerator(
5310    encoded: crate::EncodedHtJ2kCodeBlock,
5311) -> bitplane_encode::EncodedCodeBlock {
5312    bitplane_encode::EncodedCodeBlock {
5313        data: encoded.data,
5314        num_coding_passes: encoded.num_coding_passes,
5315        num_zero_bitplanes: encoded.num_zero_bitplanes,
5316        ht_cleanup_length: encoded.cleanup_length,
5317        ht_refinement_length: encoded.refinement_length,
5318    }
5319}
5320
5321fn encode_tier1_code_block(
5322    coefficients: &[i32],
5323    width: u32,
5324    height: u32,
5325    sub_band_type: SubBandType,
5326    total_bitplanes: u8,
5327    accelerator: &mut impl J2kEncodeStageAccelerator,
5328) -> Result<bitplane_encode::EncodedCodeBlock, &'static str> {
5329    if let Some(encoded) = accelerator.encode_tier1_code_block(J2kTier1CodeBlockEncodeJob {
5330        coefficients,
5331        width,
5332        height,
5333        sub_band_type: public_sub_band_type(sub_band_type),
5334        total_bitplanes,
5335        style: default_public_code_block_style(),
5336    })? {
5337        return Ok(encoded_code_block_from_accelerator(encoded));
5338    }
5339
5340    Ok(bitplane_encode::encode_code_block(
5341        coefficients,
5342        width,
5343        height,
5344        sub_band_type,
5345        total_bitplanes,
5346    ))
5347}
5348
5349fn encoded_code_block_from_accelerator(
5350    encoded: EncodedJ2kCodeBlock,
5351) -> bitplane_encode::EncodedCodeBlock {
5352    bitplane_encode::EncodedCodeBlock {
5353        data: encoded.data,
5354        num_coding_passes: encoded.number_of_coding_passes,
5355        num_zero_bitplanes: encoded.missing_bit_planes,
5356        ht_cleanup_length: 0,
5357        ht_refinement_length: 0,
5358    }
5359}
5360
5361fn public_sub_band_type(sub_band_type: SubBandType) -> J2kSubBandType {
5362    match sub_band_type {
5363        SubBandType::LowLow => J2kSubBandType::LowLow,
5364        SubBandType::HighLow => J2kSubBandType::HighLow,
5365        SubBandType::LowHigh => J2kSubBandType::LowHigh,
5366        SubBandType::HighHigh => J2kSubBandType::HighHigh,
5367    }
5368}
5369
5370fn internal_sub_band_type(sub_band_type: J2kSubBandType) -> SubBandType {
5371    match sub_band_type {
5372        J2kSubBandType::LowLow => SubBandType::LowLow,
5373        J2kSubBandType::HighLow => SubBandType::HighLow,
5374        J2kSubBandType::LowHigh => SubBandType::LowHigh,
5375        J2kSubBandType::HighHigh => SubBandType::HighHigh,
5376    }
5377}
5378
5379fn default_public_code_block_style() -> crate::J2kCodeBlockStyle {
5380    crate::J2kCodeBlockStyle {
5381        selective_arithmetic_coding_bypass: false,
5382        reset_context_probabilities: false,
5383        termination_on_each_pass: false,
5384        vertically_causal_context: false,
5385        segmentation_symbols: false,
5386    }
5387}
5388
5389/// Convert interleaved pixel bytes to per-component f32 arrays.
5390pub(crate) fn deinterleave_to_f32(
5391    pixels: &[u8],
5392    num_pixels: usize,
5393    num_components: u8,
5394    bit_depth: u8,
5395    signed: bool,
5396) -> Vec<Vec<f32>> {
5397    if num_components == 3 && bit_depth == 8 && !signed {
5398        return deinterleave_rgb8_unsigned_to_f32(pixels, num_pixels);
5399    }
5400
5401    let nc = num_components as usize;
5402    let mut components = vec![vec![0.0f32; num_pixels]; nc];
5403    let unsigned_offset = if signed {
5404        0.0
5405    } else {
5406        (1u32 << (bit_depth as u32 - 1)) as f32
5407    };
5408
5409    if bit_depth <= 8 {
5410        for (i, pixel) in pixels.chunks_exact(nc).take(num_pixels).enumerate() {
5411            for (c, component) in components.iter_mut().enumerate().take(nc) {
5412                let val = pixel[c];
5413                component[i] = if signed {
5414                    (val as i8) as f32
5415                } else {
5416                    val as f32 - unsigned_offset
5417                };
5418            }
5419        }
5420    } else {
5421        // 16-bit samples (little-endian)
5422        for (i, pixel) in pixels.chunks_exact(nc * 2).take(num_pixels).enumerate() {
5423            for (c, component) in components.iter_mut().enumerate().take(nc) {
5424                let offset = c * 2;
5425                let val = u16::from_le_bytes([pixel[offset], pixel[offset + 1]]);
5426                component[i] = if signed {
5427                    (val as i16) as f32
5428                } else {
5429                    val as f32 - unsigned_offset
5430                };
5431            }
5432        }
5433    }
5434
5435    components
5436}
5437
5438fn deinterleave_rgb8_unsigned_to_f32(pixels: &[u8], num_pixels: usize) -> Vec<Vec<f32>> {
5439    let mut r = Vec::with_capacity(num_pixels);
5440    let mut g = Vec::with_capacity(num_pixels);
5441    let mut b = Vec::with_capacity(num_pixels);
5442
5443    for pixel in pixels.chunks_exact(3).take(num_pixels) {
5444        r.push(f32::from(pixel[0]) - 128.0);
5445        g.push(f32::from(pixel[1]) - 128.0);
5446        b.push(f32::from(pixel[2]) - 128.0);
5447    }
5448
5449    vec![r, g, b]
5450}
5451
5452/// Calculate the maximum number of decomposition levels for given dimensions.
5453fn max_decomposition_levels(width: u32, height: u32) -> u8 {
5454    let min_dim = width.min(height);
5455    if min_dim <= 1 {
5456        return 0;
5457    }
5458    floor_f32(log2_f32(min_dim as f32)) as u8
5459}
5460
5461#[cfg(test)]
5462mod tests {
5463    use super::*;
5464    use crate::PrequantizedHtj2k97CodeBlock;
5465
5466    fn test_preencoded_subband_payload(marker: u8) -> PreencodedHtj2k97Subband {
5467        PreencodedHtj2k97Subband {
5468            sub_band_type: J2kSubBandType::LowLow,
5469            num_cbs_x: 1,
5470            num_cbs_y: 1,
5471            total_bitplanes: 8,
5472            code_blocks: vec![PreencodedHtj2k97CodeBlock {
5473                width: 1,
5474                height: 1,
5475                encoded: crate::EncodedHtJ2kCodeBlock {
5476                    data: vec![marker; 8],
5477                    cleanup_length: 8,
5478                    refinement_length: 0,
5479                    num_coding_passes: 1,
5480                    num_zero_bitplanes: 0,
5481                },
5482            }],
5483        }
5484    }
5485
5486    #[test]
5487    fn prepared_subband_from_owned_preencoded_moves_payload_without_clone() {
5488        let subband = test_preencoded_subband_payload(7);
5489        let original_ptr = subband.code_blocks[0].encoded.data.as_ptr() as usize;
5490
5491        let prepared =
5492            prepared_subband_from_preencoded_owned(subband).expect("owned preencoded subband");
5493        let prepared_blocks = prepared
5494            .preencoded_ht_code_blocks
5495            .expect("preencoded payloads");
5496
5497        assert_eq!(prepared_blocks[0].data.as_ptr() as usize, original_ptr);
5498        assert!(prepared.code_blocks[0].coefficients.is_empty());
5499    }
5500
5501    #[test]
5502    fn compact_preencoded_packetization_borrows_payload_ranges() {
5503        #[derive(Default)]
5504        struct RecordingPacketizationAccelerator {
5505            payload_base: usize,
5506            observed_offsets: Vec<usize>,
5507            observed_lengths: Vec<usize>,
5508        }
5509
5510        impl crate::J2kEncodeStageAccelerator for RecordingPacketizationAccelerator {
5511            fn encode_packetization(
5512                &mut self,
5513                job: crate::J2kPacketizationEncodeJob<'_>,
5514            ) -> core::result::Result<Option<Vec<u8>>, &'static str> {
5515                for code_block in job
5516                    .resolutions
5517                    .iter()
5518                    .flat_map(|resolution| resolution.subbands.iter())
5519                    .flat_map(|subband| subband.code_blocks.iter())
5520                    .filter(|code_block| !code_block.data.is_empty())
5521                {
5522                    self.observed_offsets
5523                        .push((code_block.data.as_ptr() as usize) - self.payload_base);
5524                    self.observed_lengths.push(code_block.data.len());
5525                }
5526                Ok(Some(crate::encode_j2k_packetization_scalar(job)?))
5527            }
5528        }
5529
5530        let (preencoded, options) = sample_preencoded_htj2k97_for_test();
5531        let expected = encode_preencoded_htj2k_97(&preencoded, &options).expect("owned preencoded");
5532        let mut payload = Vec::new();
5533        let mut expected_offsets = Vec::new();
5534        let mut expected_lengths = Vec::new();
5535        let components = preencoded
5536            .components
5537            .iter()
5538            .map(|component| PreencodedHtj2k97CompactComponent {
5539                x_rsiz: component.x_rsiz,
5540                y_rsiz: component.y_rsiz,
5541                resolutions: component
5542                    .resolutions
5543                    .iter()
5544                    .map(|resolution| PreencodedHtj2k97CompactResolution {
5545                        subbands: resolution
5546                            .subbands
5547                            .iter()
5548                            .map(|subband| PreencodedHtj2k97CompactSubband {
5549                                sub_band_type: subband.sub_band_type,
5550                                num_cbs_x: subband.num_cbs_x,
5551                                num_cbs_y: subband.num_cbs_y,
5552                                total_bitplanes: subband.total_bitplanes,
5553                                code_blocks: subband
5554                                    .code_blocks
5555                                    .iter()
5556                                    .map(|block| {
5557                                        let start = payload.len();
5558                                        payload.extend_from_slice(&block.encoded.data);
5559                                        let end = payload.len();
5560                                        if start != end {
5561                                            expected_offsets.push(start);
5562                                            expected_lengths.push(end - start);
5563                                        }
5564                                        PreencodedHtj2k97CompactCodeBlock {
5565                                            width: block.width,
5566                                            height: block.height,
5567                                            payload_range: start..end,
5568                                            cleanup_length: block.encoded.cleanup_length,
5569                                            refinement_length: block.encoded.refinement_length,
5570                                            num_coding_passes: block.encoded.num_coding_passes,
5571                                            num_zero_bitplanes: block.encoded.num_zero_bitplanes,
5572                                        }
5573                                    })
5574                                    .collect(),
5575                            })
5576                            .collect(),
5577                    })
5578                    .collect(),
5579            })
5580            .collect();
5581        let compact = PreencodedHtj2k97CompactImage {
5582            width: preencoded.width,
5583            height: preencoded.height,
5584            bit_depth: preencoded.bit_depth,
5585            signed: preencoded.signed,
5586            payload,
5587            components,
5588        };
5589        let mut accelerator = RecordingPacketizationAccelerator {
5590            payload_base: compact.payload.as_ptr() as usize,
5591            ..Default::default()
5592        };
5593
5594        let actual = encode_preencoded_htj2k_97_compact_owned_with_accelerator(
5595            compact,
5596            &options,
5597            &mut accelerator,
5598        )
5599        .expect("compact preencoded");
5600
5601        assert_eq!(actual, expected);
5602        assert_eq!(accelerator.observed_offsets, expected_offsets);
5603        assert_eq!(accelerator.observed_lengths, expected_lengths);
5604    }
5605
5606    #[test]
5607    fn test_encode_8bit_gray() {
5608        let width = 8u32;
5609        let height = 8u32;
5610        let pixels: Vec<u8> = (0..64).collect();
5611
5612        let result = encode(
5613            &pixels,
5614            width,
5615            height,
5616            1,
5617            8,
5618            false,
5619            &EncodeOptions {
5620                num_decomposition_levels: 2,
5621                ..Default::default()
5622            },
5623        );
5624
5625        assert!(result.is_ok());
5626        let codestream = result.unwrap();
5627        // Verify SOC marker
5628        assert_eq!(codestream[0], 0xFF);
5629        assert_eq!(codestream[1], 0x4F);
5630        // Verify EOC marker
5631        let len = codestream.len();
5632        assert_eq!(codestream[len - 2], 0xFF);
5633        assert_eq!(codestream[len - 1], 0xD9);
5634    }
5635
5636    #[test]
5637    fn test_encode_16bit_gray() {
5638        let width = 8u32;
5639        let height = 8u32;
5640        let mut pixels = Vec::with_capacity(128);
5641        for i in 0..64u16 {
5642            let val = i * 100;
5643            pixels.extend_from_slice(&val.to_le_bytes());
5644        }
5645
5646        let result = encode(
5647            &pixels,
5648            width,
5649            height,
5650            1,
5651            16,
5652            false,
5653            &EncodeOptions {
5654                num_decomposition_levels: 2,
5655                ..Default::default()
5656            },
5657        );
5658
5659        assert!(result.is_ok());
5660    }
5661
5662    #[test]
5663    fn test_encode_rgb() {
5664        let width = 16u32;
5665        let height = 16u32;
5666        let pixels: Vec<u8> = (0..width * height * 3).map(|i| (i & 0xFF) as u8).collect();
5667
5668        let result = encode(
5669            &pixels,
5670            width,
5671            height,
5672            3,
5673            8,
5674            false,
5675            &EncodeOptions {
5676                num_decomposition_levels: 3,
5677                ..Default::default()
5678            },
5679        );
5680
5681        assert!(result.is_ok(), "RGB encode failed: {:?}", result.err());
5682    }
5683
5684    #[test]
5685    fn encode_with_accelerator_calls_lossless_stage_hooks() {
5686        #[derive(Default)]
5687        struct CountingAccelerator {
5688            forward_rct: usize,
5689            forward_dwt53: usize,
5690            tier1_code_blocks: usize,
5691            tier1_code_block_batches: usize,
5692            tier1_batched_jobs: usize,
5693            packetization: usize,
5694            packetization_resolution_count: u32,
5695            packetization_code_block_count: u32,
5696            packetization_saw_payload: bool,
5697        }
5698
5699        impl crate::J2kEncodeStageAccelerator for CountingAccelerator {
5700            fn encode_forward_rct(
5701                &mut self,
5702                _job: crate::J2kForwardRctJob<'_>,
5703            ) -> core::result::Result<bool, &'static str> {
5704                self.forward_rct += 1;
5705                Ok(false)
5706            }
5707
5708            fn encode_forward_dwt53(
5709                &mut self,
5710                _job: crate::J2kForwardDwt53Job<'_>,
5711            ) -> core::result::Result<Option<crate::J2kForwardDwt53Output>, &'static str>
5712            {
5713                self.forward_dwt53 += 1;
5714                Ok(None)
5715            }
5716
5717            fn encode_tier1_code_block(
5718                &mut self,
5719                _job: crate::J2kTier1CodeBlockEncodeJob<'_>,
5720            ) -> core::result::Result<Option<crate::EncodedJ2kCodeBlock>, &'static str>
5721            {
5722                self.tier1_code_blocks += 1;
5723                Ok(None)
5724            }
5725
5726            fn encode_tier1_code_blocks(
5727                &mut self,
5728                jobs: &[crate::J2kTier1CodeBlockEncodeJob<'_>],
5729            ) -> core::result::Result<Option<Vec<crate::EncodedJ2kCodeBlock>>, &'static str>
5730            {
5731                self.tier1_code_block_batches += 1;
5732                self.tier1_batched_jobs += jobs.len();
5733                Ok(None)
5734            }
5735
5736            fn encode_packetization(
5737                &mut self,
5738                job: crate::J2kPacketizationEncodeJob<'_>,
5739            ) -> core::result::Result<Option<Vec<u8>>, &'static str> {
5740                self.packetization += 1;
5741                self.packetization_resolution_count = job.resolution_count;
5742                self.packetization_code_block_count = job.code_block_count;
5743                self.packetization_saw_payload = job
5744                    .resolutions
5745                    .iter()
5746                    .flat_map(|resolution| resolution.subbands.iter())
5747                    .flat_map(|subband| subband.code_blocks.iter())
5748                    .any(|code_block| !code_block.data.is_empty());
5749                Ok(None)
5750            }
5751        }
5752
5753        let pixels: Vec<u8> = (0..8 * 8 * 3).map(|i| (i & 0xFF) as u8).collect();
5754        let options = EncodeOptions {
5755            num_decomposition_levels: 1,
5756            reversible: true,
5757            ..EncodeOptions::default()
5758        };
5759        let mut accelerator = CountingAccelerator::default();
5760
5761        let codestream =
5762            encode_with_accelerator(&pixels, 8, 8, 3, 8, false, &options, &mut accelerator)
5763                .expect("encode with accelerator hooks");
5764
5765        assert!(codestream.starts_with(&[0xFF, 0x4F]));
5766        assert_eq!(accelerator.forward_rct, 1);
5767        assert_eq!(accelerator.forward_dwt53, 3);
5768        assert!(accelerator.tier1_code_block_batches > 0);
5769        assert_eq!(
5770            accelerator.tier1_code_blocks,
5771            accelerator.tier1_batched_jobs
5772        );
5773        assert_eq!(accelerator.packetization, 1);
5774        assert_eq!(accelerator.packetization_resolution_count, 6);
5775        assert_eq!(
5776            accelerator.packetization_code_block_count,
5777            u32::try_from(accelerator.tier1_code_blocks).expect("test code-block count fits u32")
5778        );
5779        assert!(accelerator.packetization_saw_payload);
5780    }
5781
5782    #[test]
5783    fn cpu_only_accelerator_opts_into_parallel_block_fallback_only_for_native_cpu() {
5784        #[derive(Default)]
5785        struct ExternalAccelerator;
5786
5787        impl crate::J2kEncodeStageAccelerator for ExternalAccelerator {}
5788
5789        let cpu = crate::CpuOnlyJ2kEncodeStageAccelerator;
5790        let external = ExternalAccelerator;
5791
5792        assert!(cpu.prefer_parallel_cpu_code_block_fallback());
5793        assert!(!external.prefer_parallel_cpu_code_block_fallback());
5794    }
5795
5796    #[test]
5797    fn cpu_parallel_block_fallback_matches_serial_classic_and_htj2k_output() {
5798        #[derive(Default)]
5799        struct SerialCpuFallbackAccelerator;
5800
5801        impl crate::J2kEncodeStageAccelerator for SerialCpuFallbackAccelerator {}
5802
5803        let pixels = gradient_u8(96, 80);
5804        for use_ht_block_coding in [false, true] {
5805            let options = EncodeOptions {
5806                num_decomposition_levels: 1,
5807                code_block_width_exp: 2,
5808                code_block_height_exp: 2,
5809                use_ht_block_coding,
5810                ..EncodeOptions::default()
5811            };
5812            let parallel = encode(&pixels, 96, 80, 1, 8, false, &options)
5813                .expect("parallel CPU fallback encode");
5814            let mut serial_accelerator = SerialCpuFallbackAccelerator;
5815            let serial = encode_with_accelerator(
5816                &pixels,
5817                96,
5818                80,
5819                1,
5820                8,
5821                false,
5822                &options,
5823                &mut serial_accelerator,
5824            )
5825            .expect("serial CPU fallback encode");
5826
5827            assert_eq!(parallel, serial);
5828        }
5829    }
5830
5831    #[test]
5832    fn precomputed_htj2k53_offers_ht_code_blocks_to_encode_accelerator() {
5833        let image = sample_precomputed_htj2k53_image();
5834        let options = EncodeOptions {
5835            num_decomposition_levels: 1,
5836            reversible: true,
5837            guard_bits: 2,
5838            code_block_width_exp: 2,
5839            code_block_height_exp: 2,
5840            ..EncodeOptions::default()
5841        };
5842        let mut accelerator = CountingHtEncodeAccelerator::default();
5843
5844        let encoded =
5845            encode_precomputed_htj2k_53_with_accelerator(&image, &options, &mut accelerator)
5846                .expect("precomputed 5/3 encode accepts encode accelerator");
5847
5848        assert!(encoded.starts_with(&[0xff, 0x4f]));
5849        assert_eq!(accelerator.forward_dwt53, 0);
5850        assert_eq!(accelerator.forward_dwt97, 0);
5851        assert_eq!(accelerator.ht_batches, 1);
5852        assert!(accelerator.ht_jobs > 0);
5853        assert_eq!(accelerator.ht_single_blocks, accelerator.ht_jobs);
5854    }
5855
5856    #[test]
5857    fn precomputed_htj2k97_offers_ht_code_blocks_to_encode_accelerator() {
5858        let image = sample_precomputed_htj2k97_image();
5859        let options = EncodeOptions {
5860            num_decomposition_levels: 1,
5861            reversible: false,
5862            guard_bits: 2,
5863            code_block_width_exp: 2,
5864            code_block_height_exp: 2,
5865            ..EncodeOptions::default()
5866        };
5867        let mut accelerator = CountingHtEncodeAccelerator::default();
5868
5869        let encoded =
5870            encode_precomputed_htj2k_97_with_accelerator(&image, &options, &mut accelerator)
5871                .expect("precomputed 9/7 encode accepts encode accelerator");
5872
5873        assert!(encoded.starts_with(&[0xff, 0x4f]));
5874        assert_eq!(accelerator.forward_dwt53, 0);
5875        assert_eq!(accelerator.forward_dwt97, 0);
5876        assert_eq!(accelerator.ht_batches, 1);
5877        assert!(accelerator.ht_jobs > 0);
5878        assert_eq!(accelerator.ht_single_blocks, accelerator.ht_jobs);
5879    }
5880
5881    #[test]
5882    fn precomputed_dwt_geometry_validation_rejects_recursive_mismatch_for_both_filters() {
5883        let mut dwt53 = sample_precomputed_htj2k53_image();
5884        dwt53.components[0].dwt.levels[0].low_width += 1;
5885        assert_eq!(
5886            validate_precomputed_dwt_geometry(&dwt53),
5887            Err("precomputed DWT recursive geometry mismatch")
5888        );
5889
5890        let mut dwt97 = sample_precomputed_htj2k97_image();
5891        dwt97.components[0].dwt.levels[0].low_width += 1;
5892        assert_eq!(
5893            validate_precomputed_dwt97_geometry(&dwt97),
5894            Err("precomputed DWT recursive geometry mismatch")
5895        );
5896    }
5897
5898    #[test]
5899    fn prequantized_htj2k97_offers_ht_code_blocks_to_encode_accelerator() {
5900        let image = sample_precomputed_htj2k97_image();
5901        let options = EncodeOptions {
5902            num_decomposition_levels: 1,
5903            reversible: false,
5904            guard_bits: 2,
5905            code_block_width_exp: 2,
5906            code_block_height_exp: 2,
5907            ..EncodeOptions::default()
5908        };
5909        let prequantized = prequantized_htj2k97_image_from_precomputed_for_test(&image, &options)
5910            .expect("test prequantized image");
5911        let mut accelerator = CountingHtEncodeAccelerator::default();
5912
5913        let encoded = encode_prequantized_htj2k_97_with_accelerator(
5914            &prequantized,
5915            &options,
5916            &mut accelerator,
5917        )
5918        .expect("prequantized 9/7 encode accepts encode accelerator");
5919
5920        assert!(encoded.starts_with(&[0xff, 0x4f]));
5921        assert_eq!(accelerator.forward_dwt53, 0);
5922        assert_eq!(accelerator.forward_dwt97, 0);
5923        assert_eq!(accelerator.ht_batches, 1);
5924        assert!(accelerator.ht_jobs > 0);
5925        assert_eq!(accelerator.ht_single_blocks, accelerator.ht_jobs);
5926    }
5927
5928    #[test]
5929    fn precomputed_htj2k97_batch_offers_all_ht_code_blocks_in_one_accelerator_call() {
5930        let image = sample_precomputed_htj2k97_image();
5931        let options = EncodeOptions {
5932            num_decomposition_levels: 1,
5933            reversible: false,
5934            guard_bits: 2,
5935            code_block_width_exp: 2,
5936            code_block_height_exp: 2,
5937            ..EncodeOptions::default()
5938        };
5939        let mut accelerator = CountingHtEncodeAccelerator::default();
5940
5941        let encoded = encode_precomputed_htj2k_97_batch_with_accelerator(
5942            &[image.clone(), image],
5943            &options,
5944            &mut accelerator,
5945        )
5946        .expect("batch precomputed 9/7 encode accepts encode accelerator");
5947
5948        assert_eq!(encoded.len(), 2);
5949        assert!(encoded
5950            .iter()
5951            .all(|codestream| codestream.starts_with(&[0xff, 0x4f])));
5952        assert_eq!(accelerator.forward_dwt53, 0);
5953        assert_eq!(accelerator.forward_dwt97, 0);
5954        assert_eq!(accelerator.ht_batches, 1);
5955        assert!(accelerator.ht_jobs > 0);
5956        assert_eq!(accelerator.ht_single_blocks, accelerator.ht_jobs);
5957    }
5958
5959    #[derive(Default)]
5960    struct CountingHtEncodeAccelerator {
5961        forward_dwt53: usize,
5962        forward_dwt97: usize,
5963        ht_batches: usize,
5964        ht_jobs: usize,
5965        ht_single_blocks: usize,
5966    }
5967
5968    impl crate::J2kEncodeStageAccelerator for CountingHtEncodeAccelerator {
5969        fn encode_forward_dwt53(
5970            &mut self,
5971            _job: crate::J2kForwardDwt53Job<'_>,
5972        ) -> core::result::Result<Option<crate::J2kForwardDwt53Output>, &'static str> {
5973            self.forward_dwt53 += 1;
5974            Ok(None)
5975        }
5976
5977        fn encode_forward_dwt97(
5978            &mut self,
5979            _job: crate::J2kForwardDwt97Job<'_>,
5980        ) -> core::result::Result<Option<crate::J2kForwardDwt97Output>, &'static str> {
5981            self.forward_dwt97 += 1;
5982            Ok(None)
5983        }
5984
5985        fn encode_ht_code_blocks(
5986            &mut self,
5987            jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
5988        ) -> core::result::Result<Option<Vec<crate::EncodedHtJ2kCodeBlock>>, &'static str> {
5989            self.ht_batches += 1;
5990            self.ht_jobs += jobs.len();
5991            Ok(None)
5992        }
5993
5994        fn encode_ht_code_block(
5995            &mut self,
5996            _job: crate::J2kHtCodeBlockEncodeJob<'_>,
5997        ) -> core::result::Result<Option<crate::EncodedHtJ2kCodeBlock>, &'static str> {
5998            self.ht_single_blocks += 1;
5999            Ok(None)
6000        }
6001    }
6002
6003    #[test]
6004    fn prepare_subband_uses_fused_ht_subband_without_host_quantized_codeblocks() {
6005        #[derive(Default)]
6006        struct FusedHtSubbandAccelerator {
6007            subband_calls: usize,
6008            quantize_calls: usize,
6009            ht_batch_calls: usize,
6010        }
6011
6012        impl crate::J2kEncodeStageAccelerator for FusedHtSubbandAccelerator {
6013            fn encode_ht_subband(
6014                &mut self,
6015                job: crate::J2kHtSubbandEncodeJob<'_>,
6016            ) -> core::result::Result<Option<Vec<crate::EncodedHtJ2kCodeBlock>>, &'static str>
6017            {
6018                self.subband_calls += 1;
6019                let count = (job.width.div_ceil(job.code_block_width) as usize)
6020                    .checked_mul(job.height.div_ceil(job.code_block_height) as usize)
6021                    .ok_or("test code-block count overflow")?;
6022                Ok(Some(
6023                    (0..count)
6024                        .map(|idx| crate::EncodedHtJ2kCodeBlock {
6025                            data: vec![u8::try_from(idx).expect("test block index fits"), 0],
6026                            cleanup_length: 2,
6027                            refinement_length: 0,
6028                            num_coding_passes: 1,
6029                            num_zero_bitplanes: 0,
6030                        })
6031                        .collect(),
6032                ))
6033            }
6034
6035            fn encode_quantize_subband(
6036                &mut self,
6037                _job: crate::J2kQuantizeSubbandJob<'_>,
6038            ) -> core::result::Result<Option<Vec<i32>>, &'static str> {
6039                self.quantize_calls += 1;
6040                Ok(None)
6041            }
6042
6043            fn encode_ht_code_blocks(
6044                &mut self,
6045                _jobs: &[crate::J2kHtCodeBlockEncodeJob<'_>],
6046            ) -> core::result::Result<Option<Vec<crate::EncodedHtJ2kCodeBlock>>, &'static str>
6047            {
6048                self.ht_batch_calls += 1;
6049                Ok(None)
6050            }
6051        }
6052
6053        let coefficients = vec![0.0; 16];
6054        let mut accelerator = FusedHtSubbandAccelerator::default();
6055        let prepared = prepare_subband(
6056            &coefficients,
6057            4,
6058            4,
6059            &QuantStepSize {
6060                exponent: 8,
6061                mantissa: 0,
6062            },
6063            8,
6064            2,
6065            true,
6066            BlockCodingMode::HighThroughput,
6067            2,
6068            2,
6069            SubBandType::LowLow,
6070            &mut accelerator,
6071        )
6072        .expect("fused HT subband prepare");
6073
6074        assert_eq!(accelerator.subband_calls, 1);
6075        assert_eq!(accelerator.quantize_calls, 0);
6076        assert!(prepared.preencoded_ht_code_blocks.is_some());
6077        assert!(prepared
6078            .code_blocks
6079            .iter()
6080            .all(|block| block.coefficients.is_empty()));
6081
6082        let precincts = encode_prepared_subbands(vec![prepared], &mut accelerator)
6083            .expect("preencoded HT subband packet data");
6084
6085        assert_eq!(accelerator.ht_batch_calls, 0);
6086        assert_eq!(precincts[0].code_blocks.len(), 4);
6087        assert_eq!(precincts[0].code_blocks[2].data, vec![2, 0]);
6088    }
6089
6090    #[test]
6091    fn ht_cpu_parallel_fallback_threshold_matches_parallel_output() {
6092        assert_eq!(HT_CPU_PARALLEL_FALLBACK_MIN_JOBS, 4);
6093
6094        let blocks: Vec<Vec<i32>> = (0..HT_CPU_PARALLEL_FALLBACK_MIN_JOBS)
6095            .map(|seed| {
6096                (0usize..64 * 64)
6097                    .map(|index| {
6098                        let value = (((index * 31) ^ (seed * 17)) & 0x01ff) as i32 - 255;
6099                        if (index + seed).is_multiple_of(11) {
6100                            0
6101                        } else {
6102                            value
6103                        }
6104                    })
6105                    .collect()
6106            })
6107            .collect();
6108        let jobs: Vec<_> = blocks
6109            .iter()
6110            .map(|coefficients| crate::J2kHtCodeBlockEncodeJob {
6111                coefficients,
6112                width: 64,
6113                height: 64,
6114                total_bitplanes: 10,
6115                target_coding_passes: 1,
6116            })
6117            .collect();
6118
6119        let serial =
6120            encode_all_ht_code_blocks_serial_cpu(&jobs[..HT_CPU_PARALLEL_FALLBACK_MIN_JOBS - 1])
6121                .expect("serial tiny HT encode");
6122        let parallel =
6123            encode_all_ht_code_blocks_parallel(&jobs[..HT_CPU_PARALLEL_FALLBACK_MIN_JOBS])
6124                .expect("parallel HT encode");
6125        let serial_threshold =
6126            encode_all_ht_code_blocks_serial_cpu(&jobs[..HT_CPU_PARALLEL_FALLBACK_MIN_JOBS])
6127                .expect("serial threshold HT encode");
6128
6129        assert_eq!(serial.len(), HT_CPU_PARALLEL_FALLBACK_MIN_JOBS - 1);
6130        assert_eq!(parallel.len(), HT_CPU_PARALLEL_FALLBACK_MIN_JOBS);
6131        assert_eq!(serial_threshold.len(), parallel.len());
6132        for (serial, parallel) in serial_threshold.iter().zip(&parallel) {
6133            assert_eq!(serial.data, parallel.data);
6134            assert_eq!(serial.num_coding_passes, parallel.num_coding_passes);
6135            assert_eq!(serial.num_zero_bitplanes, parallel.num_zero_bitplanes);
6136        }
6137    }
6138
6139    #[test]
6140    fn code_block_extraction_copies_partial_edge_blocks_rowwise() {
6141        let quantized: Vec<i32> = (0..20).collect();
6142
6143        let block = copy_code_block_coefficients(&quantized, 5, 3, 1, 2, 3);
6144
6145        assert_eq!(block, vec![8, 9, 13, 14, 18, 19]);
6146    }
6147
6148    #[test]
6149    fn test_encode_lossy() {
6150        let pixels: Vec<u8> = (0..64).collect();
6151
6152        let result = encode(
6153            &pixels,
6154            8,
6155            8,
6156            1,
6157            8,
6158            false,
6159            &EncodeOptions {
6160                num_decomposition_levels: 2,
6161                reversible: false,
6162                guard_bits: 2,
6163                ..Default::default()
6164            },
6165        );
6166
6167        assert!(result.is_ok());
6168    }
6169
6170    #[test]
6171    fn prequantized_htj2k97_matches_precomputed_dwt97_codestream() {
6172        let image = sample_precomputed_htj2k97_image();
6173        let options = EncodeOptions {
6174            num_decomposition_levels: 1,
6175            reversible: false,
6176            guard_bits: 2,
6177            code_block_width_exp: 2,
6178            code_block_height_exp: 2,
6179            ..EncodeOptions::default()
6180        };
6181
6182        let precomputed =
6183            encode_precomputed_htj2k_97(&image, &options).expect("precomputed DWT encode");
6184        let prequantized = prequantized_htj2k97_image_from_precomputed_for_test(&image, &options)
6185            .expect("test prequantized image");
6186        let direct =
6187            encode_prequantized_htj2k_97(&prequantized, &options).expect("prequantized encode");
6188
6189        assert_eq!(direct, precomputed);
6190    }
6191
6192    #[test]
6193    fn preencoded_htj2k97_matches_prequantized_codestream() {
6194        let image = sample_precomputed_htj2k97_image();
6195        let options = EncodeOptions {
6196            num_decomposition_levels: 1,
6197            reversible: false,
6198            guard_bits: 2,
6199            code_block_width_exp: 2,
6200            code_block_height_exp: 2,
6201            ..EncodeOptions::default()
6202        };
6203        let prequantized = prequantized_htj2k97_image_from_precomputed_for_test(&image, &options)
6204            .expect("test prequantized image");
6205        let expected =
6206            encode_prequantized_htj2k_97(&prequantized, &options).expect("prequantized encode");
6207        let preencoded = preencoded_htj2k97_image_from_prequantized_for_test(&prequantized)
6208            .expect("test preencoded image");
6209        let actual = encode_preencoded_htj2k_97(&preencoded, &options).expect("preencoded encode");
6210
6211        assert_eq!(actual, expected);
6212    }
6213
6214    #[test]
6215    fn preencoded_htj2k97_rejects_empty_block_with_wrong_zero_bitplanes() {
6216        let (mut image, options) = sample_preencoded_htj2k97_for_test();
6217        let block = &mut image.components[0].resolutions[0].subbands[0].code_blocks[0];
6218        block.encoded = EncodedHtJ2kCodeBlock {
6219            data: Vec::new(),
6220            cleanup_length: 0,
6221            refinement_length: 0,
6222            num_coding_passes: 0,
6223            num_zero_bitplanes: 0,
6224        };
6225
6226        let error = encode_preencoded_htj2k_97(&image, &options)
6227            .expect_err("invalid all-zero block metadata must be rejected");
6228
6229        assert_eq!(error, "empty HTJ2K code-block zero-bitplane count mismatch");
6230    }
6231
6232    #[test]
6233    fn preencoded_htj2k97_rejects_coded_block_with_too_many_zero_bitplanes() {
6234        let (mut image, options) = sample_preencoded_htj2k97_for_test();
6235        let subband = &mut image.components[0].resolutions[0].subbands[0];
6236        subband.code_blocks[0].encoded.num_zero_bitplanes = subband.total_bitplanes;
6237
6238        let error = encode_preencoded_htj2k_97(&image, &options)
6239            .expect_err("coded block with no coded bitplanes must be rejected");
6240
6241        assert_eq!(error, "HTJ2K code-block zero-bitplane count out of range");
6242    }
6243
6244    #[cfg(feature = "std")]
6245    #[test]
6246    fn preencoded_htj2k97_rejects_too_many_coding_passes_without_panic() {
6247        let (mut image, options) = sample_preencoded_htj2k97_for_test();
6248        image.components[0].resolutions[0].subbands[0].code_blocks[0]
6249            .encoded
6250            .num_coding_passes = 165;
6251
6252        let result = std::panic::catch_unwind(|| encode_preencoded_htj2k_97(&image, &options));
6253
6254        assert!(result.is_ok(), "invalid coding pass count must not panic");
6255        assert_eq!(
6256            result.expect("catch_unwind returned checked result"),
6257            Err("HTJ2K code-block coding pass count out of range")
6258        );
6259    }
6260
6261    #[test]
6262    fn prequantized_htj2k97_accepts_empty_high_subbands() {
6263        let options = EncodeOptions {
6264            num_decomposition_levels: 1,
6265            reversible: false,
6266            guard_bits: 2,
6267            code_block_width_exp: 2,
6268            code_block_height_exp: 2,
6269            ..EncodeOptions::default()
6270        };
6271        let image = PrequantizedHtj2k97Image {
6272            width: 1,
6273            height: 1,
6274            bit_depth: 8,
6275            signed: false,
6276            components: vec![PrequantizedHtj2k97Component {
6277                x_rsiz: 1,
6278                y_rsiz: 1,
6279                resolutions: vec![
6280                    PrequantizedHtj2k97Resolution {
6281                        subbands: vec![PrequantizedHtj2k97Subband {
6282                            sub_band_type: J2kSubBandType::LowLow,
6283                            num_cbs_x: 1,
6284                            num_cbs_y: 1,
6285                            total_bitplanes: 11,
6286                            code_blocks: vec![PrequantizedHtj2k97CodeBlock {
6287                                coefficients: vec![0],
6288                                width: 1,
6289                                height: 1,
6290                            }],
6291                        }],
6292                    },
6293                    PrequantizedHtj2k97Resolution {
6294                        subbands: vec![
6295                            empty_prequantized_subband(J2kSubBandType::HighLow),
6296                            empty_prequantized_subband(J2kSubBandType::LowHigh),
6297                            empty_prequantized_subband(J2kSubBandType::HighHigh),
6298                        ],
6299                    },
6300                ],
6301            }],
6302        };
6303
6304        let encoded =
6305            encode_prequantized_htj2k_97(&image, &options).expect("empty high subbands encode");
6306
6307        assert!(encoded.starts_with(&[0xff, 0x4f]));
6308    }
6309
6310    fn empty_prequantized_subband(sub_band_type: J2kSubBandType) -> PrequantizedHtj2k97Subband {
6311        PrequantizedHtj2k97Subband {
6312            sub_band_type,
6313            num_cbs_x: 0,
6314            num_cbs_y: 0,
6315            total_bitplanes: 0,
6316            code_blocks: Vec::new(),
6317        }
6318    }
6319
6320    fn sample_precomputed_htj2k97_image() -> PrecomputedHtj2k97Image {
6321        let width = 17u32;
6322        let height = 13u32;
6323        let low_width = width.div_ceil(2);
6324        let low_height = height.div_ceil(2);
6325        let high_width = width / 2;
6326        let high_height = height / 2;
6327
6328        PrecomputedHtj2k97Image {
6329            width,
6330            height,
6331            bit_depth: 8,
6332            signed: false,
6333            components: vec![PrecomputedHtj2k97Component {
6334                x_rsiz: 1,
6335                y_rsiz: 1,
6336                dwt: J2kForwardDwt97Output {
6337                    ll: sample_f32_coefficients(low_width * low_height, 0.25),
6338                    ll_width: low_width,
6339                    ll_height: low_height,
6340                    levels: vec![J2kForwardDwt97Level {
6341                        hl: sample_f32_coefficients(high_width * low_height, -0.75),
6342                        lh: sample_f32_coefficients(low_width * high_height, 1.25),
6343                        hh: sample_f32_coefficients(high_width * high_height, -1.5),
6344                        width,
6345                        height,
6346                        low_width,
6347                        low_height,
6348                        high_width,
6349                        high_height,
6350                    }],
6351                },
6352            }],
6353        }
6354    }
6355
6356    fn sample_precomputed_htj2k53_image() -> PrecomputedHtj2k53Image {
6357        let width = 17u32;
6358        let height = 13u32;
6359        let low_width = width.div_ceil(2);
6360        let low_height = height.div_ceil(2);
6361        let high_width = width / 2;
6362        let high_height = height / 2;
6363
6364        PrecomputedHtj2k53Image {
6365            width,
6366            height,
6367            bit_depth: 8,
6368            signed: false,
6369            components: vec![PrecomputedHtj2k53Component {
6370                x_rsiz: 1,
6371                y_rsiz: 1,
6372                dwt: J2kForwardDwt53Output {
6373                    ll: sample_f32_coefficients(low_width * low_height, 0.0),
6374                    ll_width: low_width,
6375                    ll_height: low_height,
6376                    levels: vec![J2kForwardDwt53Level {
6377                        hl: sample_f32_coefficients(high_width * low_height, -2.0),
6378                        lh: sample_f32_coefficients(low_width * high_height, 2.0),
6379                        hh: sample_f32_coefficients(high_width * high_height, -4.0),
6380                        width,
6381                        height,
6382                        low_width,
6383                        low_height,
6384                        high_width,
6385                        high_height,
6386                    }],
6387                },
6388            }],
6389        }
6390    }
6391
6392    fn sample_f32_coefficients(len: u32, offset: f32) -> Vec<f32> {
6393        (0..len)
6394            .map(|idx| ((idx % 17) as f32 - 8.0) * 0.5 + offset)
6395            .collect()
6396    }
6397
6398    fn prequantized_htj2k97_image_from_precomputed_for_test(
6399        image: &PrecomputedHtj2k97Image,
6400        options: &EncodeOptions,
6401    ) -> Result<PrequantizedHtj2k97Image, &'static str> {
6402        let guard_bits = options.guard_bits.max(2);
6403        let step_sizes = quantize::compute_step_sizes_with_irreversible_profile(
6404            image.bit_depth,
6405            1,
6406            false,
6407            guard_bits,
6408            options.irreversible_quantization_scale,
6409            options.irreversible_quantization_subband_scales,
6410        );
6411        let cb_width = 1u32 << (options.code_block_width_exp + 2);
6412        let cb_height = 1u32 << (options.code_block_height_exp + 2);
6413
6414        let components = image
6415            .components
6416            .iter()
6417            .map(|component| {
6418                let mut resolutions = Vec::with_capacity(component.dwt.levels.len() + 1);
6419                resolutions.push(PrequantizedHtj2k97Resolution {
6420                    subbands: vec![prequantized_subband_for_test(
6421                        &component.dwt.ll,
6422                        component.dwt.ll_width,
6423                        component.dwt.ll_height,
6424                        SubBandType::LowLow,
6425                        &step_sizes[0],
6426                        image.bit_depth,
6427                        guard_bits,
6428                        cb_width,
6429                        cb_height,
6430                    )?],
6431                });
6432
6433                for (level_index, level) in component.dwt.levels.iter().enumerate() {
6434                    let step_base = 1 + level_index * 3;
6435                    resolutions.push(PrequantizedHtj2k97Resolution {
6436                        subbands: vec![
6437                            prequantized_subband_for_test(
6438                                &level.hl,
6439                                level.high_width,
6440                                level.low_height,
6441                                SubBandType::HighLow,
6442                                &step_sizes[step_base],
6443                                image.bit_depth,
6444                                guard_bits,
6445                                cb_width,
6446                                cb_height,
6447                            )?,
6448                            prequantized_subband_for_test(
6449                                &level.lh,
6450                                level.low_width,
6451                                level.high_height,
6452                                SubBandType::LowHigh,
6453                                &step_sizes[step_base + 1],
6454                                image.bit_depth,
6455                                guard_bits,
6456                                cb_width,
6457                                cb_height,
6458                            )?,
6459                            prequantized_subband_for_test(
6460                                &level.hh,
6461                                level.high_width,
6462                                level.high_height,
6463                                SubBandType::HighHigh,
6464                                &step_sizes[step_base + 2],
6465                                image.bit_depth,
6466                                guard_bits,
6467                                cb_width,
6468                                cb_height,
6469                            )?,
6470                        ],
6471                    });
6472                }
6473
6474                Ok(PrequantizedHtj2k97Component {
6475                    x_rsiz: component.x_rsiz,
6476                    y_rsiz: component.y_rsiz,
6477                    resolutions,
6478                })
6479            })
6480            .collect::<Result<Vec<_>, &'static str>>()?;
6481
6482        Ok(PrequantizedHtj2k97Image {
6483            width: image.width,
6484            height: image.height,
6485            bit_depth: image.bit_depth,
6486            signed: image.signed,
6487            components,
6488        })
6489    }
6490
6491    #[allow(clippy::too_many_arguments)]
6492    fn prequantized_subband_for_test(
6493        coefficients: &[f32],
6494        width: u32,
6495        height: u32,
6496        sub_band_type: SubBandType,
6497        step_size: &QuantStepSize,
6498        bit_depth: u8,
6499        guard_bits: u8,
6500        cb_width: u32,
6501        cb_height: u32,
6502    ) -> Result<PrequantizedHtj2k97Subband, &'static str> {
6503        let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
6504        let prepared = prepare_subband(
6505            coefficients,
6506            width,
6507            height,
6508            step_size,
6509            bit_depth,
6510            guard_bits,
6511            false,
6512            BlockCodingMode::HighThroughput,
6513            cb_width,
6514            cb_height,
6515            sub_band_type,
6516            &mut accelerator,
6517        )?;
6518
6519        Ok(PrequantizedHtj2k97Subband {
6520            sub_band_type: public_sub_band_type(sub_band_type),
6521            num_cbs_x: prepared.num_cbs_x,
6522            num_cbs_y: prepared.num_cbs_y,
6523            total_bitplanes: prepared.total_bitplanes,
6524            code_blocks: prepared
6525                .code_blocks
6526                .into_iter()
6527                .map(|block| PrequantizedHtj2k97CodeBlock {
6528                    coefficients: block.coefficients,
6529                    width: block.width,
6530                    height: block.height,
6531                })
6532                .collect(),
6533        })
6534    }
6535
6536    fn preencoded_htj2k97_image_from_prequantized_for_test(
6537        image: &PrequantizedHtj2k97Image,
6538    ) -> Result<PreencodedHtj2k97Image, &'static str> {
6539        let components = image
6540            .components
6541            .iter()
6542            .map(|component| {
6543                Ok(PreencodedHtj2k97Component {
6544                    x_rsiz: component.x_rsiz,
6545                    y_rsiz: component.y_rsiz,
6546                    resolutions: component
6547                        .resolutions
6548                        .iter()
6549                        .map(|resolution| {
6550                            Ok(PreencodedHtj2k97Resolution {
6551                                subbands: resolution
6552                                    .subbands
6553                                    .iter()
6554                                    .map(preencoded_subband_from_prequantized_for_test)
6555                                    .collect::<Result<Vec<_>, &'static str>>()?,
6556                            })
6557                        })
6558                        .collect::<Result<Vec<_>, &'static str>>()?,
6559                })
6560            })
6561            .collect::<Result<Vec<_>, &'static str>>()?;
6562
6563        Ok(PreencodedHtj2k97Image {
6564            width: image.width,
6565            height: image.height,
6566            bit_depth: image.bit_depth,
6567            signed: image.signed,
6568            components,
6569        })
6570    }
6571
6572    fn sample_preencoded_htj2k97_for_test() -> (PreencodedHtj2k97Image, EncodeOptions) {
6573        let image = sample_precomputed_htj2k97_image();
6574        let options = EncodeOptions {
6575            num_decomposition_levels: 1,
6576            reversible: false,
6577            guard_bits: 2,
6578            code_block_width_exp: 2,
6579            code_block_height_exp: 2,
6580            ..EncodeOptions::default()
6581        };
6582        let prequantized = prequantized_htj2k97_image_from_precomputed_for_test(&image, &options)
6583            .expect("test prequantized image");
6584        let preencoded = preencoded_htj2k97_image_from_prequantized_for_test(&prequantized)
6585            .expect("test preencoded image");
6586        (preencoded, options)
6587    }
6588
6589    fn preencoded_subband_from_prequantized_for_test(
6590        subband: &PrequantizedHtj2k97Subband,
6591    ) -> Result<PreencodedHtj2k97Subband, &'static str> {
6592        let code_blocks = subband
6593            .code_blocks
6594            .iter()
6595            .map(|block| {
6596                let encoded = ht_block_encode::encode_code_block(
6597                    &block.coefficients,
6598                    block.width,
6599                    block.height,
6600                    subband.total_bitplanes,
6601                )?;
6602                Ok(PreencodedHtj2k97CodeBlock {
6603                    width: block.width,
6604                    height: block.height,
6605                    encoded: EncodedHtJ2kCodeBlock {
6606                        data: encoded.data,
6607                        cleanup_length: encoded.ht_cleanup_length,
6608                        refinement_length: encoded.ht_refinement_length,
6609                        num_coding_passes: encoded.num_coding_passes,
6610                        num_zero_bitplanes: encoded.num_zero_bitplanes,
6611                    },
6612                })
6613            })
6614            .collect::<Result<Vec<_>, &'static str>>()?;
6615
6616        Ok(PreencodedHtj2k97Subband {
6617            sub_band_type: subband.sub_band_type,
6618            num_cbs_x: subband.num_cbs_x,
6619            num_cbs_y: subband.num_cbs_y,
6620            total_bitplanes: subband.total_bitplanes,
6621            code_blocks,
6622        })
6623    }
6624
6625    fn assert_htj2k_lossless_roundtrip(
6626        pixels: &[u8],
6627        width: u32,
6628        height: u32,
6629        bit_depth: u8,
6630        num_decomposition_levels: u8,
6631    ) {
6632        let codestream = encode_htj2k(
6633            pixels,
6634            width,
6635            height,
6636            1,
6637            bit_depth,
6638            false,
6639            &EncodeOptions {
6640                num_decomposition_levels,
6641                ..Default::default()
6642            },
6643        )
6644        .expect("HTJ2K encode");
6645
6646        assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
6647        let cod_offset = codestream
6648            .windows(2)
6649            .position(|window| window == [0xFF, 0x52])
6650            .expect("COD marker");
6651        assert_eq!(codestream[cod_offset + 12], 0x40);
6652
6653        let image = Image::new(
6654            &codestream,
6655            &DecodeSettings {
6656                resolve_palette_indices: true,
6657                strict: true,
6658                target_resolution: None,
6659            },
6660        )
6661        .expect("parse HT codestream");
6662        let decoded = image.decode_native().expect("decode HT codestream");
6663
6664        assert_eq!(decoded.width, width);
6665        assert_eq!(decoded.height, height);
6666        assert_eq!(decoded.bit_depth, bit_depth);
6667        assert_eq!(decoded.data, pixels);
6668    }
6669
6670    fn gradient_u8(width: u32, height: u32) -> Vec<u8> {
6671        let mut pixels = Vec::with_capacity((width * height) as usize);
6672        for y in 0..height {
6673            for x in 0..width {
6674                pixels.push(((x * 17 + y * 31) % 256) as u8);
6675            }
6676        }
6677        pixels
6678    }
6679
6680    fn lossy_htj2k_roundtrip_u8(
6681        pixels: &[u8],
6682        width: u32,
6683        height: u32,
6684        num_decomposition_levels: u8,
6685    ) -> (Vec<u8>, usize) {
6686        let codestream = encode_htj2k(
6687            pixels,
6688            width,
6689            height,
6690            1,
6691            8,
6692            false,
6693            &EncodeOptions {
6694                num_decomposition_levels,
6695                reversible: false,
6696                guard_bits: 2,
6697                ..Default::default()
6698            },
6699        )
6700        .expect("lossy HT encode");
6701
6702        assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
6703
6704        let image = Image::new(
6705            &codestream,
6706            &DecodeSettings {
6707                resolve_palette_indices: true,
6708                strict: true,
6709                target_resolution: None,
6710            },
6711        )
6712        .expect("parse lossy HT codestream");
6713        let decoded = image.decode_native().expect("decode lossy HT codestream");
6714
6715        assert_eq!(decoded.width, width);
6716        assert_eq!(decoded.height, height);
6717        assert_eq!(decoded.bit_depth, 8);
6718
6719        (decoded.data, codestream.len())
6720    }
6721
6722    fn max_abs_error(expected: &[u8], actual: &[u8]) -> u8 {
6723        expected
6724            .iter()
6725            .zip(actual)
6726            .map(|(&expected, &actual)| expected.abs_diff(actual))
6727            .max()
6728            .unwrap_or(0)
6729    }
6730
6731    fn psnr_db(expected: &[u8], actual: &[u8]) -> f64 {
6732        let mse = expected
6733            .iter()
6734            .zip(actual)
6735            .map(|(&expected, &actual)| {
6736                let diff = f64::from(expected) - f64::from(actual);
6737                diff * diff
6738            })
6739            .sum::<f64>()
6740            / expected.len() as f64;
6741
6742        if mse == 0.0 {
6743            f64::INFINITY
6744        } else {
6745            20.0 * 255.0f64.log10() - 10.0 * mse.log10()
6746        }
6747    }
6748
6749    fn assert_not_flat_128(decoded: &[u8]) {
6750        assert!(
6751            decoded.iter().any(|&sample| sample != 128),
6752            "lossy decode collapsed to flat 128"
6753        );
6754    }
6755
6756    #[test]
6757    fn test_encode_high_throughput_zero_image_roundtrip() {
6758        let width = 4u32;
6759        let height = 4u32;
6760        let sample = 2048u16.to_le_bytes();
6761        let mut pixels = Vec::with_capacity((width * height * 2) as usize);
6762        for _ in 0..(width * height) {
6763            pixels.extend_from_slice(&sample);
6764        }
6765
6766        let codestream = encode(
6767            &pixels,
6768            width,
6769            height,
6770            1,
6771            12,
6772            false,
6773            &EncodeOptions {
6774                num_decomposition_levels: 2,
6775                use_ht_block_coding: true,
6776                ..Default::default()
6777            },
6778        )
6779        .expect("HT all-zero encode");
6780
6781        assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
6782        let cod_offset = codestream
6783            .windows(2)
6784            .position(|window| window == [0xFF, 0x52])
6785            .expect("COD marker");
6786        assert_eq!(codestream[cod_offset + 12], 0x40);
6787
6788        let image =
6789            Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
6790        let decoded = image.decode_native().expect("decode HT codestream");
6791
6792        assert_eq!(decoded.width, width);
6793        assert_eq!(decoded.height, height);
6794        assert_eq!(decoded.bit_depth, 12);
6795        assert_eq!(decoded.data, pixels);
6796    }
6797
6798    #[test]
6799    fn test_encode_high_throughput_nonzero_roundtrip() {
6800        let width = 1u32;
6801        let height = 1u32;
6802        let pixels = 2049u16.to_le_bytes().to_vec();
6803
6804        let codestream = encode_htj2k(
6805            &pixels,
6806            width,
6807            height,
6808            1,
6809            12,
6810            false,
6811            &EncodeOptions {
6812                num_decomposition_levels: 0,
6813                ..Default::default()
6814            },
6815        )
6816        .expect("HT non-zero encode");
6817
6818        assert!(codestream.windows(2).any(|window| window == [0xFF, 0x50]));
6819        let image =
6820            Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
6821        let decoded = image.decode_native().expect("decode HT codestream");
6822
6823        assert_eq!(decoded.width, width);
6824        assert_eq!(decoded.height, height);
6825        assert_eq!(decoded.bit_depth, 12);
6826        assert_eq!(decoded.data, pixels);
6827    }
6828
6829    #[test]
6830    fn test_encode_high_throughput_varied_12bit_roundtrip() {
6831        let mut pixels = Vec::with_capacity(32);
6832        for i in 0u16..16 {
6833            pixels.extend_from_slice(&((i * 257) & 0x0FFF).to_le_bytes());
6834        }
6835
6836        let codestream = encode_htj2k(
6837            &pixels,
6838            4,
6839            4,
6840            1,
6841            12,
6842            false,
6843            &EncodeOptions {
6844                num_decomposition_levels: 1,
6845                ..Default::default()
6846            },
6847        )
6848        .expect("HT varied encode");
6849
6850        let image =
6851            Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
6852        let decoded = image.decode_native().expect("decode HT codestream");
6853
6854        assert_eq!(decoded.width, 4);
6855        assert_eq!(decoded.height, 4);
6856        assert_eq!(decoded.bit_depth, 12);
6857        assert_eq!(decoded.data, pixels);
6858    }
6859
6860    #[test]
6861    fn test_encode_high_throughput_gradient_8bit_roundtrip() {
6862        let pixels: Vec<u8> = (0..64).collect();
6863
6864        let codestream = encode_htj2k(
6865            &pixels,
6866            8,
6867            8,
6868            1,
6869            8,
6870            false,
6871            &EncodeOptions {
6872                num_decomposition_levels: 3,
6873                ..Default::default()
6874            },
6875        )
6876        .expect("HT gradient encode");
6877
6878        let image =
6879            Image::new(&codestream, &DecodeSettings::default()).expect("parse HT codestream");
6880        let decoded = image.decode_native().expect("decode HT codestream");
6881
6882        assert_eq!(decoded.width, 8);
6883        assert_eq!(decoded.height, 8);
6884        assert_eq!(decoded.bit_depth, 8);
6885        assert_eq!(decoded.data, pixels);
6886    }
6887
6888    #[test]
6889    fn test_encode_high_throughput_varied_12bit_large_roundtrip() {
6890        let width = 16u32;
6891        let height = 8u32;
6892        let mut pixels = Vec::with_capacity((width * height * 2) as usize);
6893        for y in 0u16..height as u16 {
6894            for x in 0u16..width as u16 {
6895                let value = (x * 257 + y * 17) & 0x0FFF;
6896                pixels.extend_from_slice(&value.to_le_bytes());
6897            }
6898        }
6899
6900        assert_htj2k_lossless_roundtrip(&pixels, width, height, 12, 4);
6901    }
6902
6903    #[test]
6904    fn test_encode_high_throughput_ramp_16bit_roundtrip() {
6905        let width = 48u32;
6906        let height = 24u32;
6907        let mut pixels = Vec::with_capacity((width * height * 2) as usize);
6908        for y in 0u16..height as u16 {
6909            for x in 0u16..width as u16 {
6910                let value = x * 521 + y * 997;
6911                pixels.extend_from_slice(&value.to_le_bytes());
6912            }
6913        }
6914
6915        assert_htj2k_lossless_roundtrip(&pixels, width, height, 16, 4);
6916    }
6917
6918    #[test]
6919    fn test_encode_high_throughput_lossy_large_gradient_is_parseable() {
6920        let pixels = gradient_u8(128, 128);
6921
6922        let (decoded, codestream_len) = lossy_htj2k_roundtrip_u8(&pixels, 128, 128, 5);
6923
6924        assert!(codestream_len > 110);
6925        assert_not_flat_128(&decoded);
6926        assert!(
6927            psnr_db(&pixels, &decoded) >= 30.0,
6928            "psnr={} max_abs={}",
6929            psnr_db(&pixels, &decoded),
6930            max_abs_error(&pixels, &decoded)
6931        );
6932    }
6933
6934    #[test]
6935    fn test_encode_high_throughput_lossy_constant_extremes_are_not_midgray() {
6936        for sample in [0u8, 255] {
6937            let pixels = vec![sample; 64 * 64];
6938            let (decoded, codestream_len) = lossy_htj2k_roundtrip_u8(&pixels, 64, 64, 4);
6939
6940            assert!(codestream_len > 110);
6941            assert_not_flat_128(&decoded);
6942            assert!(
6943                max_abs_error(&pixels, &decoded) <= 2,
6944                "sample={sample} max_abs={} decoded_min={} decoded_max={}",
6945                max_abs_error(&pixels, &decoded),
6946                decoded.iter().min().unwrap(),
6947                decoded.iter().max().unwrap()
6948            );
6949        }
6950    }
6951
6952    #[test]
6953    fn test_encode_invalid_dimensions() {
6954        let result = encode(&[], 0, 0, 1, 8, false, &EncodeOptions::default());
6955        assert!(result.is_err());
6956    }
6957
6958    #[test]
6959    fn test_encode_too_short() {
6960        let pixels = vec![0u8; 10]; // Way too short for 8x8
6961        let result = encode(&pixels, 8, 8, 1, 8, false, &EncodeOptions::default());
6962        assert!(result.is_err());
6963    }
6964
6965    #[test]
6966    fn test_deinterleave_rgb() {
6967        let pixels = vec![
6968            10u8, 20, 30, // pixel 0: R=10, G=20, B=30
6969            40, 50, 60, // pixel 1: R=40, G=50, B=60
6970        ];
6971        let comps = deinterleave_to_f32(&pixels, 2, 3, 8, false);
6972        assert_eq!(comps[0], vec![-118.0, -88.0]); // R
6973        assert_eq!(comps[1], vec![-108.0, -78.0]); // G
6974        assert_eq!(comps[2], vec![-98.0, -68.0]); // B
6975    }
6976
6977    #[test]
6978    fn deinterleave_rgb8_unsigned_fast_path_matches_generic_output() {
6979        let pixels = (0..96)
6980            .map(|value| ((value * 19 + value / 3) & 0xff) as u8)
6981            .collect::<Vec<_>>();
6982
6983        let expected = deinterleave_to_f32(&pixels, 32, 3, 8, false);
6984        let actual = deinterleave_rgb8_unsigned_to_f32(&pixels, 32);
6985
6986        assert_eq!(actual, expected);
6987    }
6988
6989    #[test]
6990    fn test_encode_decode_roundtrip_gray_8bit() {
6991        use crate::{DecodeSettings, Image};
6992
6993        // Constant image: all pixels = 42 — simplest possible test
6994        let original: Vec<u8> = vec![42u8; 64]; // 8x8, all same value
6995        let encoded = encode(
6996            &original,
6997            8,
6998            8,
6999            1,
7000            8,
7001            false,
7002            &EncodeOptions {
7003                num_decomposition_levels: 0,
7004                reversible: true,
7005                ..Default::default()
7006            },
7007        )
7008        .expect("encode failed");
7009
7010        let settings = DecodeSettings {
7011            resolve_palette_indices: false,
7012            strict: false,
7013            target_resolution: None,
7014        };
7015        let image = Image::new(&encoded, &settings).expect("parse failed");
7016        let decoded = image.decode_native().expect("decode failed");
7017
7018        assert_eq!(decoded.width, 8);
7019        assert_eq!(decoded.height, 8);
7020        assert_eq!(decoded.data, original, "round-trip mismatch");
7021    }
7022
7023    #[test]
7024    fn test_encode_decode_roundtrip_gray_8bit_single_dwt_level() {
7025        use crate::{DecodeSettings, Image};
7026
7027        let original: Vec<u8> = (0..64 * 64)
7028            .map(|value| ((value * 37 + value / 7) & 0xFF) as u8)
7029            .collect();
7030        let encoded = encode(
7031            &original,
7032            64,
7033            64,
7034            1,
7035            8,
7036            false,
7037            &EncodeOptions {
7038                num_decomposition_levels: 1,
7039                reversible: true,
7040                ..Default::default()
7041            },
7042        )
7043        .expect("encode failed");
7044
7045        let image = Image::new(&encoded, &DecodeSettings::default()).expect("parse failed");
7046        let decoded = image.decode_native().expect("decode failed");
7047
7048        assert_eq!(decoded.width, 64);
7049        assert_eq!(decoded.height, 64);
7050        assert_eq!(decoded.data, original, "round-trip mismatch");
7051    }
7052
7053    /// Precondition gate: native encode_htj2k must produce byte-identical output
7054    /// across repeated invocations with the same input before CUDA parity can be
7055    /// asserted.  96x80 with 3 components and 5 decomposition levels exercises
7056    /// multi-codeblock subbands.
7057    #[cfg(feature = "std")]
7058    #[test]
7059    fn encode_htj2k_is_byte_deterministic() {
7060        const WIDTH: u32 = 96;
7061        const HEIGHT: u32 = 80;
7062        const NUM_COMPONENTS: u8 = 3;
7063        const BIT_DEPTH: u8 = 8;
7064        const REPETITIONS: usize = 8;
7065
7066        // Deterministic pseudo-random pixel data: simple LCG-like sequence.
7067        let pixel_count = (WIDTH * HEIGHT) as usize * usize::from(NUM_COMPONENTS);
7068        let pixels: Vec<u8> = (0..pixel_count)
7069            .map(|i| {
7070                let v = i
7071                    .wrapping_mul(6364136223846793005)
7072                    .wrapping_add(1442695040888963407);
7073                (v >> 56) as u8
7074            })
7075            .collect();
7076
7077        let options = EncodeOptions {
7078            use_ht_block_coding: true,
7079            reversible: true,
7080            num_decomposition_levels: 5,
7081            validate_high_throughput_codestream: true,
7082            ..EncodeOptions::default()
7083        };
7084
7085        let baseline = encode_htj2k(
7086            &pixels,
7087            WIDTH,
7088            HEIGHT,
7089            NUM_COMPONENTS,
7090            BIT_DEPTH,
7091            false,
7092            &options,
7093        )
7094        .expect("encode_htj2k baseline failed");
7095
7096        assert!(
7097            !baseline.is_empty(),
7098            "baseline codestream must not be empty"
7099        );
7100
7101        for i in 0..REPETITIONS {
7102            let result = encode_htj2k(
7103                &pixels,
7104                WIDTH,
7105                HEIGHT,
7106                NUM_COMPONENTS,
7107                BIT_DEPTH,
7108                false,
7109                &options,
7110            )
7111            .unwrap_or_else(|e| panic!("encode_htj2k repetition {i} failed: {e}"));
7112            assert_eq!(
7113                result,
7114                baseline,
7115                "encode_htj2k repetition {i} produced different bytes \
7116                 (len baseline={}, len result={})",
7117                baseline.len(),
7118                result.len()
7119            );
7120        }
7121
7122        println!(
7123            "encode_htj2k_is_byte_deterministic: {} bytes, {} repetitions all identical",
7124            baseline.len(),
7125            REPETITIONS
7126        );
7127    }
7128
7129    /// Precondition gate: prove native encode_htj2k round-trips 2-component
7130    /// 8-bit lossless images exactly with independent component channels.
7131    #[cfg(feature = "std")]
7132    #[test]
7133    fn native_htj2k_roundtrips_two_component_lossless() {
7134        const WIDTH: u32 = 32;
7135        const HEIGHT: u32 = 24;
7136        const NUM_COMPONENTS: u8 = 2;
7137        const BIT_DEPTH: u8 = 8;
7138
7139        // Deterministic per-pixel pattern: each sample is a function of its
7140        // flat index so the two planes carry different, non-trivial data.
7141        let pixel_count = WIDTH as usize * HEIGHT as usize * usize::from(NUM_COMPONENTS);
7142        let pixels: Vec<u8> = (0..pixel_count)
7143            .map(|i| ((i.wrapping_mul(251).wrapping_add(i / 7)) & 0xFF) as u8)
7144            .collect();
7145
7146        let codestream = encode_htj2k(
7147            &pixels,
7148            WIDTH,
7149            HEIGHT,
7150            NUM_COMPONENTS,
7151            BIT_DEPTH,
7152            false,
7153            &EncodeOptions::default(),
7154        )
7155        .expect("native 2-component HTJ2K encode failed");
7156
7157        let image = Image::new(
7158            &codestream,
7159            &DecodeSettings {
7160                resolve_palette_indices: true,
7161                strict: true,
7162                target_resolution: None,
7163            },
7164        )
7165        .expect("native 2-component HTJ2K parse failed");
7166        let decoded = image
7167            .decode_native()
7168            .expect("native 2-component HTJ2K decode failed");
7169
7170        assert_eq!(decoded.width, WIDTH, "width mismatch");
7171        assert_eq!(decoded.height, HEIGHT, "height mismatch");
7172        assert_eq!(decoded.bit_depth, BIT_DEPTH, "bit_depth mismatch");
7173        assert_eq!(
7174            decoded.num_components, NUM_COMPONENTS,
7175            "component count mismatch"
7176        );
7177        assert_eq!(
7178            decoded.data, pixels,
7179            "2-component HTJ2K lossless round-trip mismatch"
7180        );
7181
7182        println!(
7183            "native_htj2k_roundtrips_two_component_lossless: {} bytes codestream, {} pixel bytes",
7184            codestream.len(),
7185            pixels.len()
7186        );
7187    }
7188
7189    /// Precondition gate: prove native encode_htj2k round-trips 4-component
7190    /// (e.g. RGBA) 8-bit lossless images exactly.
7191    /// Required before a CUDA parity oracle can be established for this component count.
7192    #[cfg(feature = "std")]
7193    #[test]
7194    fn native_htj2k_roundtrips_four_component_lossless() {
7195        const WIDTH: u32 = 32;
7196        const HEIGHT: u32 = 24;
7197        const NUM_COMPONENTS: u8 = 4;
7198        const BIT_DEPTH: u8 = 8;
7199
7200        // Deterministic per-sample pattern across all four planes.
7201        let pixel_count = WIDTH as usize * HEIGHT as usize * usize::from(NUM_COMPONENTS);
7202        let pixels: Vec<u8> = (0..pixel_count)
7203            .map(|i| ((i.wrapping_mul(197).wrapping_add(i / 13)) & 0xFF) as u8)
7204            .collect();
7205
7206        let codestream = encode_htj2k(
7207            &pixels,
7208            WIDTH,
7209            HEIGHT,
7210            NUM_COMPONENTS,
7211            BIT_DEPTH,
7212            false,
7213            &EncodeOptions::default(),
7214        )
7215        .expect("native 4-component HTJ2K encode failed");
7216
7217        let image = Image::new(
7218            &codestream,
7219            &DecodeSettings {
7220                resolve_palette_indices: true,
7221                strict: true,
7222                target_resolution: None,
7223            },
7224        )
7225        .expect("native 4-component HTJ2K parse failed");
7226        let decoded = image
7227            .decode_native()
7228            .expect("native 4-component HTJ2K decode failed");
7229
7230        assert_eq!(decoded.width, WIDTH, "width mismatch");
7231        assert_eq!(decoded.height, HEIGHT, "height mismatch");
7232        assert_eq!(decoded.bit_depth, BIT_DEPTH, "bit_depth mismatch");
7233        assert_eq!(
7234            decoded.num_components, NUM_COMPONENTS,
7235            "component count mismatch"
7236        );
7237        assert_eq!(
7238            decoded.data, pixels,
7239            "4-component HTJ2K lossless round-trip mismatch"
7240        );
7241
7242        println!(
7243            "native_htj2k_roundtrips_four_component_lossless: {} bytes codestream, {} pixel bytes",
7244            codestream.len(),
7245            pixels.len()
7246        );
7247    }
7248
7249    #[test]
7250    fn classic_pcrd_assigns_limited_budget_by_distortion_slope() {
7251        let candidates = vec![
7252            ClassicSegmentAssignmentCandidate {
7253                block_index: 0,
7254                segment_index: 0,
7255                rate: 500,
7256                distortion_delta: 500.0,
7257            },
7258            ClassicSegmentAssignmentCandidate {
7259                block_index: 1,
7260                segment_index: 0,
7261                rate: 700,
7262                distortion_delta: 7_000.0,
7263            },
7264            ClassicSegmentAssignmentCandidate {
7265                block_index: 2,
7266                segment_index: 0,
7267                rate: 600,
7268                distortion_delta: 3_000.0,
7269            },
7270        ];
7271
7272        let assignments = assign_classic_segment_layers_by_slope(&candidates, 2, &[256, 3_000])
7273            .expect("PCRD assignment");
7274
7275        assert_eq!(
7276            assignments,
7277            vec![1, 0, 1],
7278            "the highest slope contribution should consume the constrained first-layer budget"
7279        );
7280    }
7281
7282    #[test]
7283    fn classic_pcrd_allows_byte_target_tolerance_for_first_legal_truncation() {
7284        let candidates = vec![ClassicSegmentAssignmentCandidate {
7285            block_index: 0,
7286            segment_index: 0,
7287            rate: 300,
7288            distortion_delta: 1_000.0,
7289        }];
7290
7291        let assignments = assign_classic_segment_layers_by_slope(&candidates, 2, &[256, 1_000])
7292            .expect("PCRD assignment");
7293
7294        assert_eq!(assignments, vec![0]);
7295    }
7296
7297    #[test]
7298    fn classic_pcrd_does_not_spend_budget_on_non_prefix_segments() {
7299        let candidates = vec![
7300            ClassicSegmentAssignmentCandidate {
7301                block_index: 0,
7302                segment_index: 0,
7303                rate: 1_000,
7304                distortion_delta: 1_000.0,
7305            },
7306            ClassicSegmentAssignmentCandidate {
7307                block_index: 0,
7308                segment_index: 1,
7309                rate: 500,
7310                distortion_delta: 10_000.0,
7311            },
7312            ClassicSegmentAssignmentCandidate {
7313                block_index: 1,
7314                segment_index: 0,
7315                rate: 300,
7316                distortion_delta: 600.0,
7317            },
7318        ];
7319
7320        let assignments = assign_classic_segment_layers_by_slope(&candidates, 2, &[256, 2_000])
7321            .expect("PCRD assignment");
7322
7323        assert_eq!(
7324            assignments,
7325            vec![1, 1, 0],
7326            "first-layer budget must go to the best legal prefix contribution"
7327        );
7328    }
7329
7330    #[test]
7331    fn ht_layer_assignment_uses_segment_budget_before_block_index() {
7332        let candidates = vec![
7333            HtSegmentAssignmentCandidate {
7334                block_index: 0,
7335                rate: 900,
7336            },
7337            HtSegmentAssignmentCandidate {
7338                block_index: 1,
7339                rate: 200,
7340            },
7341            HtSegmentAssignmentCandidate {
7342                block_index: 2,
7343                rate: 200,
7344            },
7345        ];
7346
7347        let assignments = assign_ht_segment_layers_by_budget(&candidates, 2, &[256, 2_000])
7348            .expect("HTJ2K segment assignment");
7349
7350        assert_eq!(
7351            assignments,
7352            vec![1, 0, 0],
7353            "HTJ2K early layers should be filled by segment byte budget, not block index"
7354        );
7355    }
7356}