Skip to main content

j2k_transcode/jpeg_to_htj2k/
options.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::vec::Vec;
4
5use crate::allocation::try_vec_from_slice;
6use j2k::IrreversibleQuantizationSubbandScales;
7use j2k::J2kProgressionOrder;
8
9use super::JpegToHtj2kError;
10
11/// Default irreversible quantization multiplier for JPEG direct 9/7 HTJ2K.
12///
13/// Empirically rate-match the explicit lossy comparison profile near the
14/// external comparator output size on the bundled WSI tiles. Lower values
15/// produce larger/higher-quality codestreams; `1.0` matches the native encoder
16/// default but overshoots the external baseline size for this transcode path.
17pub const JPEG_TO_HTJ2K_LOSSY_97_QUANTIZATION_SCALE: f32 = 1.9;
18
19/// HTJ2K encode options used after JPEG coefficient-domain wavelet bands are produced.
20#[derive(Debug, Clone, PartialEq)]
21#[expect(
22    clippy::struct_excessive_bools,
23    reason = "stable public options expose independent codec switches rather than an implicit mode matrix"
24)]
25pub struct JpegToHtj2kEncodeOptions {
26    /// Number of wavelet decomposition levels.
27    pub num_decomposition_levels: u8,
28    /// Whether to emit reversible/lossless coding.
29    pub reversible: bool,
30    /// Code-block width exponent minus two.
31    pub code_block_width_exp: u8,
32    /// Code-block height exponent minus two.
33    pub code_block_height_exp: u8,
34    /// JPEG 2000 guard bits.
35    pub guard_bits: u8,
36    /// Whether to encode HTJ2K code blocks instead of classic EBCOT.
37    pub use_ht_block_coding: bool,
38    /// Packet progression order.
39    pub progression_order: J2kProgressionOrder,
40    /// Whether to write a TLM marker segment.
41    pub write_tlm: bool,
42    /// Whether to write PLT packet-length marker segments.
43    pub write_plt: bool,
44    /// Whether to write PLM packet-length marker segments.
45    pub write_plm: bool,
46    /// Whether to write PPM packed packet-header marker segments.
47    pub write_ppm: bool,
48    /// Whether to write PPT packed packet-header marker segments.
49    pub write_ppt: bool,
50    /// Whether to write SOP marker segments before packets.
51    pub write_sop: bool,
52    /// Whether to write EPH markers after packet headers.
53    pub write_eph: bool,
54    /// Whether to apply JPEG 2000 multi-component transform.
55    pub use_mct: bool,
56    /// Number of cumulative quality layers.
57    pub num_layers: u8,
58    /// Optional cumulative packet-body byte targets for each quality layer.
59    pub quality_layer_byte_targets: Vec<u64>,
60    /// Whether native HTJ2K validation is enabled after encode.
61    pub validate_high_throughput_codestream: bool,
62    /// Global irreversible 9/7 quantization scale.
63    pub irreversible_quantization_scale: f32,
64    /// Per-subband irreversible 9/7 quantization scales.
65    pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
66    /// Optional per-component SIZ sampling factors (`XRsiz`, `YRsiz`).
67    pub component_sampling: Option<Vec<(u8, u8)>>,
68    /// Optional tile size for multi-tile codestreams.
69    pub tile_size: Option<(u32, u32)>,
70    /// Optional maximum number of complete packets to place in each tile-part.
71    pub tile_part_packet_limit: Option<u16>,
72    /// Optional precinct exponents in COD order.
73    pub precinct_exponents: Vec<(u8, u8)>,
74}
75
76impl Default for JpegToHtj2kEncodeOptions {
77    fn default() -> Self {
78        Self {
79            num_decomposition_levels: 5,
80            reversible: true,
81            code_block_width_exp: 4,
82            code_block_height_exp: 4,
83            guard_bits: 1,
84            use_ht_block_coding: false,
85            progression_order: J2kProgressionOrder::Lrcp,
86            write_tlm: false,
87            write_plt: false,
88            write_plm: false,
89            write_ppm: false,
90            write_ppt: false,
91            write_sop: false,
92            write_eph: false,
93            use_mct: true,
94            num_layers: 1,
95            quality_layer_byte_targets: Vec::new(),
96            validate_high_throughput_codestream: true,
97            irreversible_quantization_scale: 1.0,
98            irreversible_quantization_subband_scales:
99                IrreversibleQuantizationSubbandScales::default(),
100            component_sampling: None,
101            tile_size: None,
102            tile_part_packet_limit: None,
103            precinct_exponents: Vec::new(),
104        }
105    }
106}
107
108impl JpegToHtj2kEncodeOptions {
109    pub(super) fn to_native(&self) -> Result<j2k_native::EncodeOptions, JpegToHtj2kError> {
110        Ok(j2k_native::EncodeOptions {
111            num_decomposition_levels: self.num_decomposition_levels,
112            reversible: self.reversible,
113            code_block_width_exp: self.code_block_width_exp,
114            code_block_height_exp: self.code_block_height_exp,
115            guard_bits: self.guard_bits,
116            use_ht_block_coding: self.use_ht_block_coding,
117            progression_order: native_progression_order(self.progression_order),
118            write_tlm: self.write_tlm,
119            write_plt: self.write_plt,
120            write_plm: self.write_plm,
121            write_ppm: self.write_ppm,
122            write_ppt: self.write_ppt,
123            write_sop: self.write_sop,
124            write_eph: self.write_eph,
125            use_mct: self.use_mct,
126            num_layers: self.num_layers,
127            quality_layer_byte_targets: try_vec_from_slice(&self.quality_layer_byte_targets)?,
128            validate_high_throughput_codestream: self.validate_high_throughput_codestream,
129            irreversible_quantization_scale: self.irreversible_quantization_scale,
130            irreversible_quantization_subband_scales: self.irreversible_quantization_subband_scales,
131            component_sampling: self
132                .component_sampling
133                .as_deref()
134                .map(try_vec_from_slice)
135                .transpose()?,
136            tile_size: self.tile_size,
137            tile_part_packet_limit: self.tile_part_packet_limit,
138            precinct_exponents: try_vec_from_slice(&self.precinct_exponents)?,
139            roi_component_shifts: Vec::new(),
140        })
141    }
142}
143
144/// Options for the experimental JPEG-to-HTJ2K path.
145#[derive(Debug, Clone)]
146pub struct JpegToHtj2kOptions {
147    /// HTJ2K encode options used after wavelet bands are produced.
148    pub encode_options: JpegToHtj2kEncodeOptions,
149    /// Coefficient production path used for HTJ2K precomputed bands.
150    pub coefficient_path: JpegToHtj2kCoefficientPath,
151    /// Materialize the float IDCT-then-DWT oracle and report rounded
152    /// coefficient differences. This is intended for validation and tests, not
153    /// the production direct path.
154    pub validate_against_float_reference: bool,
155    /// Materialize j2k-jpeg scalar ISLOW samples and report reversible
156    /// integer 5/3 coefficient differences against the rounded direct path.
157    /// This is intended for validation and tests, not the production direct
158    /// path.
159    pub validate_against_integer_reference: bool,
160}
161
162impl Default for JpegToHtj2kOptions {
163    fn default() -> Self {
164        Self::lossless_53()
165    }
166}
167
168impl JpegToHtj2kOptions {
169    /// Options for the default reversible 5/3 HTJ2K coefficient path.
170    #[must_use]
171    pub fn lossless_53() -> Self {
172        Self {
173            encode_options: transcode_encode_options(true),
174            coefficient_path: JpegToHtj2kCoefficientPath::IntegerDirect53,
175            validate_against_float_reference: false,
176            validate_against_integer_reference: false,
177        }
178    }
179
180    /// Options for the irreversible 9/7 HTJ2K float-linear coefficient path.
181    #[must_use]
182    pub fn lossy_97() -> Self {
183        let mut encode_options = transcode_encode_options(false);
184        encode_options.irreversible_quantization_scale = JPEG_TO_HTJ2K_LOSSY_97_QUANTIZATION_SCALE;
185        Self {
186            encode_options,
187            coefficient_path: JpegToHtj2kCoefficientPath::FloatDirectLinear97,
188            validate_against_float_reference: false,
189            validate_against_integer_reference: false,
190        }
191    }
192}
193
194fn transcode_encode_options(reversible: bool) -> JpegToHtj2kEncodeOptions {
195    JpegToHtj2kEncodeOptions {
196        num_decomposition_levels: 1,
197        reversible,
198        use_ht_block_coding: true,
199        use_mct: false,
200        validate_high_throughput_codestream: false,
201        ..JpegToHtj2kEncodeOptions::default()
202    }
203}
204
205/// Experimental production path used to generate HTJ2K wavelet coefficients.
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum JpegToHtj2kCoefficientPath {
208    /// Exact reversible 5/3 coefficients relative to `j2k-jpeg` scalar
209    /// ISLOW block decode semantics. The first 5/3 level is computed from DCT
210    /// blocks without materializing a full spatial image plane; later levels
211    /// recurse conventionally over the LL coefficient band.
212    IntegerDirect53,
213    /// Floating-point linear composition of IDCT and 5/3 analysis. This is the
214    /// linear math oracle path and remains useful for validating the direct
215    /// matrix composition, but it is not the integer reversible production
216    /// default.
217    FloatDirectLinear53,
218    /// Floating-point linear composition of IDCT and irreversible 9/7
219    /// analysis. This is a lossy experimental path and must be paired with an
220    /// irreversible HTJ2K encode.
221    FloatDirectLinear97,
222}
223
224fn native_progression_order(
225    progression: J2kProgressionOrder,
226) -> j2k_native::EncodeProgressionOrder {
227    match progression {
228        J2kProgressionOrder::Lrcp => j2k_native::EncodeProgressionOrder::Lrcp,
229        J2kProgressionOrder::Rlcp => j2k_native::EncodeProgressionOrder::Rlcp,
230        J2kProgressionOrder::Rpcl => j2k_native::EncodeProgressionOrder::Rpcl,
231        J2kProgressionOrder::Pcrl => j2k_native::EncodeProgressionOrder::Pcrl,
232        J2kProgressionOrder::Cprl => j2k_native::EncodeProgressionOrder::Cprl,
233    }
234}