Skip to main content

j2k_native/j2c/encode/
options.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! JPEG 2000 encode option and request types.
4
5use alloc::vec::Vec;
6
7use super::super::quantize;
8use crate::IrreversibleQuantizationSubbandScales;
9
10/// Encoding options for JPEG 2000.
11#[derive(Debug, Clone)]
12#[expect(
13    clippy::struct_excessive_bools,
14    reason = "the public options expose independent JPEG 2000 coding and marker switches"
15)]
16pub struct EncodeOptions {
17    /// Number of decomposition levels (default: 5).
18    pub num_decomposition_levels: u8,
19    /// Use reversible (lossless) transform (default: true).
20    pub reversible: bool,
21    /// Code-block width exponent minus 2 (default: 4, meaning 2^6=64).
22    pub code_block_width_exp: u8,
23    /// Code-block height exponent minus 2 (default: 4, meaning 2^6=64).
24    pub code_block_height_exp: u8,
25    /// Number of guard bits (default: 1 for reversible, 2 for irreversible).
26    pub guard_bits: u8,
27    /// Encode using HT block coding (HTJ2K / Part 15) instead of classic EBCOT.
28    pub use_ht_block_coding: bool,
29    /// Packet progression order to write in COD and use for packetization.
30    pub progression_order: EncodeProgressionOrder,
31    /// Write a TLM marker segment for the single tile-part.
32    pub write_tlm: bool,
33    /// Write PLT packet-length marker segments in the tile-part header.
34    pub write_plt: bool,
35    /// Write PLM packet-length marker segments in the main header.
36    pub write_plm: bool,
37    /// Write PPM packed packet-header marker segments in the main header.
38    pub write_ppm: bool,
39    /// Write PPT packed packet-header marker segments in tile-part headers.
40    pub write_ppt: bool,
41    /// Write SOP marker segments before packets.
42    pub write_sop: bool,
43    /// Write EPH markers after packet headers.
44    pub write_eph: bool,
45    /// Apply the JPEG 2000 multi-component color transform for 3+ component inputs.
46    pub use_mct: bool,
47    /// Number of cumulative quality layers to emit.
48    pub num_layers: u8,
49    /// Optional cumulative packet-body byte targets for each quality layer.
50    pub quality_layer_byte_targets: Vec<u64>,
51    /// Decode and verify HTJ2K codestreams inside the native encoder.
52    pub validate_high_throughput_codestream: bool,
53    /// Multiplier applied to irreversible 9/7 scalar quantization step sizes.
54    ///
55    /// `1.0` preserves the near-lossless default step sizes. Larger values
56    /// produce smaller codestreams by coarsening quantization.
57    pub irreversible_quantization_scale: f32,
58    /// Per-subband multipliers applied on top of
59    /// `irreversible_quantization_scale`.
60    pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
61    /// Optional per-component SIZ sampling factors (`XRsiz`, `YRsiz`).
62    ///
63    /// `None` means every component is stored at the reference-grid
64    /// resolution. This is experimental and primarily intended for precomputed
65    /// coefficient encoders that preserve JPEG-native chroma subsampling.
66    pub component_sampling: Option<Vec<(u8, u8)>>,
67    /// Optional per-component whole-component ROI maxshift values.
68    ///
69    /// Non-zero entries emit RGN markers and encode every coefficient in that
70    /// component with the requested maxshift. Rectangular ROI authoring is not
71    /// represented by this field.
72    pub roi_component_shifts: Vec<u8>,
73    /// Optional tile width and height for multi-tile codestream output.
74    pub tile_size: Option<(u32, u32)>,
75    /// Optional maximum number of complete packets to place in each tile-part.
76    pub tile_part_packet_limit: Option<u16>,
77    /// Optional precinct exponents in COD order, one per resolution level.
78    pub precinct_exponents: Vec<(u8, u8)>,
79}
80
81/// Borrowed component-plane samples for reversible 5/3 component-plane encode.
82#[derive(Debug, Clone, Copy)]
83pub struct EncodeComponentPlane<'a> {
84    /// Row-major little-endian component samples at this component's own grid.
85    pub data: &'a [u8],
86    /// Horizontal SIZ sampling factor (`XRsiz`).
87    pub x_rsiz: u8,
88    /// Vertical SIZ sampling factor (`YRsiz`).
89    pub y_rsiz: u8,
90}
91
92/// Borrowed component-plane samples with per-component precision metadata.
93#[derive(Debug, Clone, Copy)]
94pub struct EncodeTypedComponentPlane<'a> {
95    /// Row-major little-endian component samples at this component's own grid.
96    pub data: &'a [u8],
97    /// Horizontal SIZ sampling factor (`XRsiz`).
98    pub x_rsiz: u8,
99    /// Vertical SIZ sampling factor (`YRsiz`).
100    pub y_rsiz: u8,
101    /// Significant bits per sample for this component.
102    pub bit_depth: u8,
103    /// Whether samples in this component are signed.
104    pub signed: bool,
105}
106
107/// Rectangular region-of-interest request for JPEG 2000 maxshift encoding.
108///
109/// The rectangle is expressed in full-resolution reference-grid pixels. For
110/// sampled components, the encoder maps the rectangle to that component's SIZ
111/// grid before selecting wavelet coefficients. All regions for the same
112/// component must use the same non-zero `shift`, because JPEG 2000 RGN stores
113/// one maxshift value per component.
114#[derive(Debug, Clone, Copy)]
115pub struct EncodeRoiRegion {
116    /// Component index to which the ROI applies.
117    pub component: u16,
118    /// Left edge in reference-grid pixels.
119    pub x: u32,
120    /// Top edge in reference-grid pixels.
121    pub y: u32,
122    /// Width in reference-grid pixels.
123    pub width: u32,
124    /// Height in reference-grid pixels.
125    pub height: u32,
126    /// Maxshift value to write in the component's RGN marker.
127    pub shift: u8,
128}
129
130impl Default for EncodeOptions {
131    fn default() -> Self {
132        Self {
133            num_decomposition_levels: 5,
134            reversible: true,
135            code_block_width_exp: 4,
136            code_block_height_exp: 4,
137            guard_bits: 1,
138            use_ht_block_coding: false,
139            progression_order: EncodeProgressionOrder::Lrcp,
140            write_tlm: false,
141            write_plt: false,
142            write_plm: false,
143            write_ppm: false,
144            write_ppt: false,
145            write_sop: false,
146            write_eph: false,
147            use_mct: true,
148            num_layers: 1,
149            quality_layer_byte_targets: Vec::new(),
150            validate_high_throughput_codestream: true,
151            irreversible_quantization_scale: 1.0,
152            irreversible_quantization_subband_scales:
153                IrreversibleQuantizationSubbandScales::default(),
154            component_sampling: None,
155            roi_component_shifts: Vec::new(),
156            tile_size: None,
157            tile_part_packet_limit: None,
158            precinct_exponents: Vec::new(),
159        }
160    }
161}
162
163/// JPEG 2000 packet progression orders supported by the encoder.
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
165pub enum EncodeProgressionOrder {
166    /// Layer-resolution-component-position progression.
167    #[default]
168    Lrcp,
169    /// Resolution-layer-component-position progression.
170    Rlcp,
171    /// Resolution-position-component-layer progression.
172    Rpcl,
173    /// Position-component-resolution-layer progression.
174    Pcrl,
175    /// Component-position-resolution-layer progression.
176    Cprl,
177}
178
179impl EncodeProgressionOrder {
180    pub(crate) const fn packetization_order(self) -> crate::J2kPacketizationProgressionOrder {
181        match self {
182            Self::Lrcp => crate::J2kPacketizationProgressionOrder::Lrcp,
183            Self::Rlcp => crate::J2kPacketizationProgressionOrder::Rlcp,
184            Self::Rpcl => crate::J2kPacketizationProgressionOrder::Rpcl,
185            Self::Pcrl => crate::J2kPacketizationProgressionOrder::Pcrl,
186            Self::Cprl => crate::J2kPacketizationProgressionOrder::Cprl,
187        }
188    }
189}
190
191fn validate_irreversible_quantization_scale(scale: f32) -> Result<(), &'static str> {
192    if scale.is_finite() && scale > 0.0 {
193        Ok(())
194    } else {
195        Err("irreversible quantization scale must be finite and greater than zero")
196    }
197}
198
199pub(super) fn validate_irreversible_quantization_profile(
200    options: &EncodeOptions,
201) -> Result<(), &'static str> {
202    validate_irreversible_quantization_scale(options.irreversible_quantization_scale)?;
203    if quantize::subband_scales_all_valid(options.irreversible_quantization_subband_scales) {
204        Ok(())
205    } else {
206        Err("irreversible quantization subband scales must be finite and greater than zero")
207    }
208}
209
210/// Validated Part 1 code-block dimensions derived from COD's stored
211/// exponent-minus-two fields.
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub(super) struct CodeBlockGeometry {
214    pub(super) width: u32,
215    pub(super) height: u32,
216}
217
218/// Validate the JPEG 2000 Part 1 code-block exponent and area constraints
219/// without allocating or shifting by an unchecked public value.
220pub(super) fn validate_code_block_geometry(
221    options: &EncodeOptions,
222) -> Result<CodeBlockGeometry, &'static str> {
223    const MAX_STORED_EXPONENT: u8 = 8;
224    const MAX_COMBINED_ACTUAL_EXPONENT: u8 = 12;
225
226    if options.code_block_width_exp > MAX_STORED_EXPONENT {
227        return Err("code-block width exponent exceeds supported range");
228    }
229    if options.code_block_height_exp > MAX_STORED_EXPONENT {
230        return Err("code-block height exponent exceeds supported range");
231    }
232    let width_exponent = options
233        .code_block_width_exp
234        .checked_add(2)
235        .ok_or("code-block width exponent exceeds supported range")?;
236    let height_exponent = options
237        .code_block_height_exp
238        .checked_add(2)
239        .ok_or("code-block height exponent exceeds supported range")?;
240    let combined_exponent = width_exponent
241        .checked_add(height_exponent)
242        .ok_or("code-block combined exponent exceeds supported range")?;
243    if combined_exponent > MAX_COMBINED_ACTUAL_EXPONENT {
244        return Err("code-block dimensions exceed JPEG 2000 Part 1 area limit");
245    }
246    let width = 1_u32
247        .checked_shl(u32::from(width_exponent))
248        .ok_or("code-block width exponent exceeds supported range")?;
249    let height = 1_u32
250        .checked_shl(u32::from(height_exponent))
251        .ok_or("code-block height exponent exceeds supported range")?;
252    Ok(CodeBlockGeometry { width, height })
253}
254
255pub(super) fn validate_precinct_exponents_for_options(
256    options: &EncodeOptions,
257    num_decomposition_levels: u8,
258) -> Result<(), &'static str> {
259    validate_code_block_geometry(options)?;
260    if options.precinct_exponents.is_empty() {
261        return Ok(());
262    }
263
264    let expected = usize::from(num_decomposition_levels) + 1;
265    if options.precinct_exponents.len() != expected {
266        return Err("precinct exponent count must match resolution level count");
267    }
268    if options
269        .precinct_exponents
270        .iter()
271        .any(|&(horizontal_exponent, vertical_exponent)| {
272            horizontal_exponent > 15 || vertical_exponent > 15
273        })
274    {
275        return Err("precinct exponents must fit in COD marker nybbles");
276    }
277    let code_block_horizontal_exponent = options
278        .code_block_width_exp
279        .checked_add(2)
280        .ok_or("code-block width exponent exceeds supported range")?;
281    let code_block_vertical_exponent = options
282        .code_block_height_exp
283        .checked_add(2)
284        .ok_or("code-block height exponent exceeds supported range")?;
285    for (resolution, &(horizontal_exponent, vertical_exponent)) in
286        options.precinct_exponents.iter().enumerate()
287    {
288        let minimum_horizontal_exponent = if resolution == 0 {
289            code_block_horizontal_exponent
290        } else {
291            code_block_horizontal_exponent
292                .checked_add(1)
293                .ok_or("code-block width exponent exceeds supported range")?
294        };
295        let minimum_vertical_exponent = if resolution == 0 {
296            code_block_vertical_exponent
297        } else {
298            code_block_vertical_exponent
299                .checked_add(1)
300                .ok_or("code-block height exponent exceeds supported range")?
301        };
302        if horizontal_exponent < minimum_horizontal_exponent
303            || vertical_exponent < minimum_vertical_exponent
304        {
305            return Err("precinct exponents must not reduce encoder code-block dimensions");
306        }
307    }
308    Ok(())
309}