1use 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#[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 pub num_decomposition_levels: u8,
22 pub reversible: bool,
24 pub code_block_width_exp: u8,
26 pub code_block_height_exp: u8,
28 pub guard_bits: u8,
30 pub use_ht_block_coding: bool,
32 pub progression_order: EncodeProgressionOrder,
34 pub write_tlm: bool,
36 pub write_plt: bool,
38 pub write_plm: bool,
40 pub write_ppm: bool,
42 pub write_ppt: bool,
44 pub write_sop: bool,
46 pub write_eph: bool,
48 pub use_mct: bool,
50 pub num_layers: u8,
52 pub quality_layer_byte_targets: Vec<u64>,
54 pub validate_high_throughput_codestream: bool,
56 pub irreversible_quantization_scale: f32,
61 pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
64 pub irreversible_quantization_profile: Option<Vec<Vec<f32>>>,
81 pub component_sampling: Option<Vec<(u8, u8)>>,
87 pub roi_component_shifts: Vec<u8>,
93 pub tile_size: Option<(u32, u32)>,
99 pub tile_part_packet_limit: Option<u16>,
101 pub precinct_exponents: Vec<(u8, u8)>,
103}
104
105#[derive(Debug, Clone, Copy)]
107pub struct EncodeComponentPlane<'a> {
108 pub data: &'a [u8],
110 pub x_rsiz: u8,
112 pub y_rsiz: u8,
114}
115
116#[derive(Debug, Clone, Copy)]
118pub struct EncodeTypedComponentPlane<'a> {
119 pub data: &'a [u8],
121 pub x_rsiz: u8,
123 pub y_rsiz: u8,
125 pub bit_depth: u8,
127 pub signed: bool,
129}
130
131#[derive(Debug, Clone, Copy)]
139pub struct EncodeRoiRegion {
140 pub component: u16,
142 pub x: u32,
144 pub y: u32,
146 pub width: u32,
148 pub height: u32,
150 pub shift: u8,
152}
153
154impl Default for EncodeOptions {
155 fn default() -> Self {
156 Self {
157 num_decomposition_levels: 5,
158 reversible: true,
159 code_block_width_exp: 4,
160 code_block_height_exp: 4,
161 guard_bits: 1,
162 use_ht_block_coding: false,
163 progression_order: EncodeProgressionOrder::Lrcp,
164 write_tlm: false,
165 write_plt: false,
166 write_plm: false,
167 write_ppm: false,
168 write_ppt: false,
169 write_sop: false,
170 write_eph: false,
171 use_mct: true,
172 num_layers: 1,
173 quality_layer_byte_targets: Vec::new(),
174 validate_high_throughput_codestream: true,
175 irreversible_quantization_scale: 1.0,
176 irreversible_quantization_subband_scales:
177 IrreversibleQuantizationSubbandScales::default(),
178 irreversible_quantization_profile: None,
179 component_sampling: None,
180 roi_component_shifts: Vec::new(),
181 tile_size: None,
182 tile_part_packet_limit: None,
183 precinct_exponents: Vec::new(),
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
190pub enum EncodeProgressionOrder {
191 #[default]
193 Lrcp,
194 Rlcp,
196 Rpcl,
198 Pcrl,
200 Cprl,
202}
203
204impl EncodeProgressionOrder {
205 pub(crate) const fn packetization_order(self) -> crate::J2kPacketizationProgressionOrder {
206 match self {
207 Self::Lrcp => crate::J2kPacketizationProgressionOrder::Lrcp,
208 Self::Rlcp => crate::J2kPacketizationProgressionOrder::Rlcp,
209 Self::Rpcl => crate::J2kPacketizationProgressionOrder::Rpcl,
210 Self::Pcrl => crate::J2kPacketizationProgressionOrder::Pcrl,
211 Self::Cprl => crate::J2kPacketizationProgressionOrder::Cprl,
212 }
213 }
214}
215
216fn validate_irreversible_quantization_scale(scale: f32) -> Result<(), &'static str> {
217 if scale.is_finite() && scale > 0.0 {
218 Ok(())
219 } else {
220 Err("irreversible quantization scale must be finite and greater than zero")
221 }
222}
223
224pub(super) fn validate_irreversible_quantization_profile(
225 options: &EncodeOptions,
226) -> Result<(), &'static str> {
227 validate_irreversible_quantization_scale(options.irreversible_quantization_scale)?;
228 if quantize::subband_scales_all_valid(options.irreversible_quantization_subband_scales) {
229 Ok(())
230 } else {
231 Err("irreversible quantization subband scales must be finite and greater than zero")
232 }
233}
234
235pub(super) type CodeBlockGeometry = EncodeCodeBlockDimensions;
238
239pub(super) fn validate_code_block_geometry(
242 options: &EncodeOptions,
243) -> Result<CodeBlockGeometry, &'static str> {
244 code_block_dimension(options.code_block_width_exp)
245 .map_err(|_| "code-block width exponent exceeds supported range")?;
246 code_block_dimension(options.code_block_height_exp)
247 .map_err(|_| "code-block height exponent exceeds supported range")?;
248 code_block_dimensions(options.code_block_width_exp, options.code_block_height_exp).map_err(
249 |error| match error {
250 CodeBlockGeometryError::AreaTooLarge => {
251 "code-block dimensions exceed JPEG 2000 Part 1 area limit"
252 }
253 CodeBlockGeometryError::DimensionTooSmall
254 | CodeBlockGeometryError::DimensionNotPowerOfTwo
255 | CodeBlockGeometryError::StoredExponentTooLarge => {
256 "code-block exponent exceeds supported range"
257 }
258 },
259 )
260}
261
262pub(super) fn validate_precinct_exponents_for_options(
263 options: &EncodeOptions,
264 num_decomposition_levels: u8,
265) -> Result<(), &'static str> {
266 validate_code_block_geometry(options)?;
267 if options.precinct_exponents.is_empty() {
268 return Ok(());
269 }
270
271 let expected = usize::from(num_decomposition_levels) + 1;
272 if options.precinct_exponents.len() != expected {
273 return Err("precinct exponent count must match resolution level count");
274 }
275 if options
276 .precinct_exponents
277 .iter()
278 .any(|&(horizontal_exponent, vertical_exponent)| {
279 horizontal_exponent > 15 || vertical_exponent > 15
280 })
281 {
282 return Err("precinct exponents must fit in COD marker nybbles");
283 }
284 let code_block_horizontal_exponent = options
285 .code_block_width_exp
286 .checked_add(2)
287 .ok_or("code-block width exponent exceeds supported range")?;
288 let code_block_vertical_exponent = options
289 .code_block_height_exp
290 .checked_add(2)
291 .ok_or("code-block height exponent exceeds supported range")?;
292 for (resolution, &(horizontal_exponent, vertical_exponent)) in
293 options.precinct_exponents.iter().enumerate()
294 {
295 let minimum_horizontal_exponent = if resolution == 0 {
296 code_block_horizontal_exponent
297 } else {
298 code_block_horizontal_exponent
299 .checked_add(1)
300 .ok_or("code-block width exponent exceeds supported range")?
301 };
302 let minimum_vertical_exponent = if resolution == 0 {
303 code_block_vertical_exponent
304 } else {
305 code_block_vertical_exponent
306 .checked_add(1)
307 .ok_or("code-block height exponent exceeds supported range")?
308 };
309 if horizontal_exponent < minimum_horizontal_exponent
310 || vertical_exponent < minimum_vertical_exponent
311 {
312 return Err("precinct exponents must not reduce encoder code-block dimensions");
313 }
314 }
315 Ok(())
316}
317
318impl EncodeOptions {
319 pub(super) fn custom_component_step_count(&self) -> usize {
320 self.irreversible_quantization_profile
321 .as_ref()
322 .filter(|profile| !quantize::profile_is_unity(profile))
323 .map_or(0, Vec::len)
324 }
325
326 pub(super) fn validate_component_profile(
327 &self,
328 components: usize,
329 levels: u8,
330 ) -> Result<(), &'static str> {
331 let Some(profile) = &self.irreversible_quantization_profile else {
332 return Ok(());
333 };
334 if self.reversible {
335 return Err("component quantization profile requires irreversible encoding");
336 }
337 if profile.len() != components {
338 return Err("quantization profile component count does not match input");
339 }
340 let expected = 1 + 3 * usize::from(levels);
341 if profile.iter().any(|row| row.len() != expected) {
342 return Err("quantization profile band count must match actual decomposition levels");
343 }
344 if profile
345 .iter()
346 .flatten()
347 .any(|x| !x.is_finite() || *x <= 0.0)
348 {
349 return Err("quantization profile multipliers must be finite and positive");
350 }
351 Ok(())
352 }
353}