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