Skip to main content

openjph_core/params/
local.rs

1//! Internal (local) parameter structures for JPEG 2000 codestream markers.
2//!
3//! Port of `ojph_params_local.h` and the implementation from `ojph_params.cpp`.
4
5use std::f64::consts::LN_2;
6
7use crate::arch::population_count;
8use crate::error::{OjphError, Result};
9use crate::file::{InfileBase, OutfileBase};
10use crate::types::*;
11
12// =========================================================================
13// JPEG 2000 Marker Codes
14// =========================================================================
15
16#[allow(dead_code)]
17pub(crate) mod markers {
18    pub const SOC: u16 = 0xFF4F;
19    pub const CAP: u16 = 0xFF50;
20    pub const SIZ: u16 = 0xFF51;
21    pub const COD: u16 = 0xFF52;
22    pub const COC: u16 = 0xFF53;
23    pub const TLM: u16 = 0xFF55;
24    pub const PRF: u16 = 0xFF56;
25    pub const PLM: u16 = 0xFF57;
26    pub const PLT: u16 = 0xFF58;
27    pub const CPF: u16 = 0xFF59;
28    pub const QCD: u16 = 0xFF5C;
29    pub const QCC: u16 = 0xFF5D;
30    pub const RGN: u16 = 0xFF5E;
31    pub const POC: u16 = 0xFF5F;
32    pub const PPM: u16 = 0xFF60;
33    pub const PPT: u16 = 0xFF61;
34    pub const CRG: u16 = 0xFF63;
35    pub const COM: u16 = 0xFF64;
36    pub const DFS: u16 = 0xFF72;
37    pub const ADS: u16 = 0xFF73;
38    pub const NLT: u16 = 0xFF76;
39    pub const ATK: u16 = 0xFF79;
40    pub const SOT: u16 = 0xFF90;
41    pub const SOP: u16 = 0xFF91;
42    pub const EPH: u16 = 0xFF92;
43    pub const SOD: u16 = 0xFF93;
44    pub const EOC: u16 = 0xFFD9;
45}
46
47// =========================================================================
48// Progression Orders
49// =========================================================================
50
51/// JPEG 2000 progression order for packet sequencing.
52///
53/// Determines the order in which packets are written into the codestream:
54/// Layer (L), Resolution (R), Component (C), and Position/Precinct (P).
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56#[repr(i32)]
57pub enum ProgressionOrder {
58    /// Layer–Resolution–Component–Position.
59    LRCP = 0,
60    /// Resolution–Layer–Component–Position.
61    RLCP = 1,
62    /// Resolution–Position–Component–Layer.
63    RPCL = 2,
64    /// Position–Component–Resolution–Layer.
65    PCRL = 3,
66    /// Component–Position–Resolution–Layer.
67    CPRL = 4,
68}
69
70impl ProgressionOrder {
71    /// Converts an integer to a progression order, if valid.
72    pub fn from_i32(v: i32) -> Option<Self> {
73        match v {
74            0 => Some(Self::LRCP),
75            1 => Some(Self::RLCP),
76            2 => Some(Self::RPCL),
77            3 => Some(Self::PCRL),
78            4 => Some(Self::CPRL),
79            _ => None,
80        }
81    }
82
83    /// Returns the four-character string representation (e.g. `"LRCP"`).
84    pub fn as_str(&self) -> &'static str {
85        match self {
86            Self::LRCP => "LRCP",
87            Self::RLCP => "RLCP",
88            Self::RPCL => "RPCL",
89            Self::PCRL => "PCRL",
90            Self::CPRL => "CPRL",
91        }
92    }
93
94    /// Parses a progression order from a case-insensitive string.
95    #[allow(clippy::should_implement_trait)]
96    pub fn from_str(s: &str) -> Option<Self> {
97        match s.to_uppercase().as_str() {
98            "LRCP" => Some(Self::LRCP),
99            "RLCP" => Some(Self::RLCP),
100            "RPCL" => Some(Self::RPCL),
101            "PCRL" => Some(Self::PCRL),
102            "CPRL" => Some(Self::CPRL),
103            _ => None,
104        }
105    }
106}
107
108// =========================================================================
109// Profile Numbers
110// =========================================================================
111
112/// JPEG 2000 codestream profile identifiers.
113///
114/// Profiles constrain certain codestream parameters to meet specific
115/// application requirements (e.g. cinema, broadcast).
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[repr(i32)]
118#[allow(dead_code)]
119pub enum ProfileNum {
120    /// No profile specified.
121    Undefined = 0,
122    /// Profile 0.
123    Profile0 = 1,
124    /// Profile 1.
125    Profile1 = 2,
126    /// Digital Cinema 2K.
127    Cinema2K = 3,
128    /// Digital Cinema 4K.
129    Cinema4K = 4,
130    /// Scalable Digital Cinema 2K.
131    CinemaS2K = 5,
132    /// Scalable Digital Cinema 4K.
133    CinemaS4K = 6,
134    /// Broadcast profile.
135    Broadcast = 7,
136    /// Interoperable Master Format (IMF).
137    Imf = 8,
138}
139
140impl ProfileNum {
141    /// Parses a profile name from a case-insensitive string.
142    #[allow(clippy::should_implement_trait)]
143    pub fn from_str(s: &str) -> Option<Self> {
144        match s.to_uppercase().as_str() {
145            "PROFILE0" => Some(Self::Profile0),
146            "PROFILE1" => Some(Self::Profile1),
147            "CINEMA2K" => Some(Self::Cinema2K),
148            "CINEMA4K" => Some(Self::Cinema4K),
149            "CINEMAS2K" => Some(Self::CinemaS2K),
150            "CINEMAS4K" => Some(Self::CinemaS4K),
151            "BROADCAST" => Some(Self::Broadcast),
152            "IMF" => Some(Self::Imf),
153            _ => None,
154        }
155    }
156}
157
158// =========================================================================
159// Tilepart Division flags
160// =========================================================================
161
162#[allow(dead_code)]
163pub(crate) const TILEPART_NO_DIVISIONS: u32 = 0x0;
164pub(crate) const TILEPART_RESOLUTIONS: u32 = 0x1;
165pub(crate) const TILEPART_COMPONENTS: u32 = 0x2;
166#[allow(dead_code)]
167pub(crate) const TILEPART_LAYERS: u32 = 0x4;
168#[allow(dead_code)]
169pub(crate) const TILEPART_MASK: u32 = 0x3;
170
171// =========================================================================
172// Byte-swap helpers (big-endian I/O)
173// =========================================================================
174
175#[inline]
176#[allow(dead_code)]
177pub(crate) fn swap_byte_u16(v: u16) -> u16 {
178    v.swap_bytes()
179}
180
181#[inline]
182#[allow(dead_code)]
183pub(crate) fn swap_byte_u32(v: u32) -> u32 {
184    v.swap_bytes()
185}
186
187#[inline]
188#[allow(dead_code)]
189pub(crate) fn swap_byte_u64(v: u64) -> u64 {
190    v.swap_bytes()
191}
192
193// Read/write big-endian helpers
194fn read_u8(file: &mut dyn InfileBase) -> Result<u8> {
195    let mut buf = [0u8; 1];
196    if file.read(&mut buf)? != 1 {
197        return Err(OjphError::Codec {
198            code: 0,
199            message: "unexpected EOF reading u8".into(),
200        });
201    }
202    Ok(buf[0])
203}
204
205fn read_u16_be(file: &mut dyn InfileBase) -> Result<u16> {
206    let mut buf = [0u8; 2];
207    if file.read(&mut buf)? != 2 {
208        return Err(OjphError::Codec {
209            code: 0,
210            message: "unexpected EOF reading u16".into(),
211        });
212    }
213    Ok(u16::from_be_bytes(buf))
214}
215
216fn read_u32_be(file: &mut dyn InfileBase) -> Result<u32> {
217    let mut buf = [0u8; 4];
218    if file.read(&mut buf)? != 4 {
219        return Err(OjphError::Codec {
220            code: 0,
221            message: "unexpected EOF reading u32".into(),
222        });
223    }
224    Ok(u32::from_be_bytes(buf))
225}
226
227fn write_u8(file: &mut dyn OutfileBase, v: u8) -> Result<bool> {
228    Ok(file.write(&[v])? == 1)
229}
230
231fn write_u16_be(file: &mut dyn OutfileBase, v: u16) -> Result<bool> {
232    Ok(file.write(&v.to_be_bytes())? == 2)
233}
234
235fn write_u32_be(file: &mut dyn OutfileBase, v: u32) -> Result<bool> {
236    Ok(file.write(&v.to_be_bytes())? == 4)
237}
238
239// =========================================================================
240// SIZ component info
241// =========================================================================
242
243#[derive(Debug, Clone, Copy, Default)]
244pub(crate) struct SizCompInfo {
245    pub ssiz: u8,
246    pub xr_siz: u8,
247    pub yr_siz: u8,
248}
249
250// =========================================================================
251// param_siz — Image and Tile Size marker segment
252// =========================================================================
253
254#[allow(dead_code)]
255pub(crate) const RSIZ_NLT_FLAG: u16 = 0x200;
256pub(crate) const RSIZ_HT_FLAG: u16 = 0x4000;
257#[allow(dead_code)]
258pub(crate) const RSIZ_EXT_FLAG: u16 = 0x8000;
259
260/// SIZ marker segment — image and tile size parameters.
261///
262/// Contains the fundamental geometry of the image: reference grid size,
263/// tile partitioning, image and tile offsets, and per-component
264/// subsampling and bit-depth information.
265///
266/// # Examples
267///
268/// ```rust
269/// use openjph_core::codestream::Codestream;
270/// use openjph_core::types::{Point, Size};
271///
272/// let mut cs = Codestream::new();
273/// let siz = cs.access_siz_mut();
274/// siz.set_image_extent(Point::new(1920, 1080));
275/// siz.set_tile_size(Size::new(1920, 1080));
276/// siz.set_num_components(3);
277/// for c in 0..3 {
278///     siz.set_comp_info(c, Point::new(1, 1), 8, false);
279/// }
280/// assert_eq!(siz.get_num_components(), 3);
281/// ```
282#[derive(Debug)]
283pub struct ParamSiz {
284    pub(crate) lsiz: u16,
285    pub(crate) rsiz: u16,
286    pub(crate) xsiz: u32,
287    pub(crate) ysiz: u32,
288    pub(crate) xo_siz: u32,
289    pub(crate) yo_siz: u32,
290    pub(crate) xt_siz: u32,
291    pub(crate) yt_siz: u32,
292    pub(crate) xto_siz: u32,
293    pub(crate) yto_siz: u32,
294    pub(crate) csiz: u16,
295    pub(crate) components: Vec<SizCompInfo>,
296    pub(crate) skipped_resolutions: u32,
297}
298
299impl Default for ParamSiz {
300    fn default() -> Self {
301        Self {
302            lsiz: 0,
303            rsiz: RSIZ_HT_FLAG,
304            xsiz: 0,
305            ysiz: 0,
306            xo_siz: 0,
307            yo_siz: 0,
308            xt_siz: 0,
309            yt_siz: 0,
310            xto_siz: 0,
311            yto_siz: 0,
312            csiz: 0,
313            components: Vec::new(),
314            skipped_resolutions: 0,
315        }
316    }
317}
318
319impl ParamSiz {
320    /// Sets the image reference grid extent (Xsiz, Ysiz).
321    pub fn set_image_extent(&mut self, extent: Point) {
322        self.xsiz = extent.x;
323        self.ysiz = extent.y;
324    }
325
326    /// Returns the image reference grid extent.
327    pub fn get_image_extent(&self) -> Point {
328        Point::new(self.xsiz, self.ysiz)
329    }
330
331    /// Sets the tile size (XTsiz, YTsiz).
332    pub fn set_tile_size(&mut self, s: Size) {
333        self.xt_siz = s.w;
334        self.yt_siz = s.h;
335    }
336
337    /// Returns the tile size.
338    pub fn get_tile_size(&self) -> Size {
339        Size::new(self.xt_siz, self.yt_siz)
340    }
341
342    /// Sets the image origin offset (XOsiz, YOsiz).
343    pub fn set_image_offset(&mut self, offset: Point) {
344        self.xo_siz = offset.x;
345        self.yo_siz = offset.y;
346    }
347
348    /// Returns the image origin offset.
349    pub fn get_image_offset(&self) -> Point {
350        Point::new(self.xo_siz, self.yo_siz)
351    }
352
353    /// Sets the tile grid origin offset (XTOsiz, YTOsiz).
354    pub fn set_tile_offset(&mut self, offset: Point) {
355        self.xto_siz = offset.x;
356        self.yto_siz = offset.y;
357    }
358
359    /// Returns the tile grid origin offset.
360    pub fn get_tile_offset(&self) -> Point {
361        Point::new(self.xto_siz, self.yto_siz)
362    }
363
364    /// Sets the number of image components (Csiz) and allocates storage.
365    pub fn set_num_components(&mut self, num_comps: u32) {
366        self.csiz = num_comps as u16;
367        self.components
368            .resize(num_comps as usize, SizCompInfo::default());
369    }
370
371    /// Returns the number of image components.
372    pub fn get_num_components(&self) -> u16 {
373        self.csiz
374    }
375
376    /// Sets per-component information: subsampling factors, bit depth, and
377    /// signedness.
378    ///
379    /// # Panics
380    ///
381    /// Debug-panics if `comp_num >= num_components` or if either
382    /// downsampling factor is zero.
383    pub fn set_comp_info(
384        &mut self,
385        comp_num: u32,
386        downsampling: Point,
387        bit_depth: u32,
388        is_signed: bool,
389    ) {
390        debug_assert!(comp_num < self.csiz as u32);
391        debug_assert!(downsampling.x != 0 && downsampling.y != 0);
392        let c = &mut self.components[comp_num as usize];
393        c.ssiz = (bit_depth - 1) as u8 + if is_signed { 0x80 } else { 0 };
394        c.xr_siz = downsampling.x as u8;
395        c.yr_siz = downsampling.y as u8;
396    }
397
398    /// Returns the bit depth (1–38) for the specified component.
399    pub fn get_bit_depth(&self, comp_num: u32) -> u32 {
400        debug_assert!(comp_num < self.csiz as u32);
401        ((self.components[comp_num as usize].ssiz & 0x7F) + 1) as u32
402    }
403
404    /// Returns `true` if the specified component uses signed samples.
405    pub fn is_signed(&self, comp_num: u32) -> bool {
406        debug_assert!(comp_num < self.csiz as u32);
407        (self.components[comp_num as usize].ssiz & 0x80) != 0
408    }
409
410    /// Returns the subsampling factors (XRsiz, YRsiz) for the specified component.
411    pub fn get_downsampling(&self, comp_num: u32) -> Point {
412        debug_assert!(comp_num < self.csiz as u32);
413        let c = &self.components[comp_num as usize];
414        Point::new(c.xr_siz as u32, c.yr_siz as u32)
415    }
416
417    /// Returns the width (in samples) of the specified component on the
418    /// reference grid.
419    pub fn get_width(&self, comp_num: u32) -> u32 {
420        let ds = self.components[comp_num as usize].xr_siz as u32;
421        div_ceil(self.xsiz, ds) - div_ceil(self.xo_siz, ds)
422    }
423
424    /// Returns the height (in samples) of the specified component on the
425    /// reference grid.
426    pub fn get_height(&self, comp_num: u32) -> u32 {
427        let ds = self.components[comp_num as usize].yr_siz as u32;
428        div_ceil(self.ysiz, ds) - div_ceil(self.yo_siz, ds)
429    }
430
431    /// Returns the reconstructed width accounting for skipped resolutions.
432    pub fn get_recon_width(&self, comp_num: u32) -> u32 {
433        let factor = self.get_recon_downsampling(comp_num);
434        div_ceil(self.xsiz, factor.x) - div_ceil(self.xo_siz, factor.x)
435    }
436
437    /// Returns the reconstructed height accounting for skipped resolutions.
438    pub fn get_recon_height(&self, comp_num: u32) -> u32 {
439        let factor = self.get_recon_downsampling(comp_num);
440        div_ceil(self.ysiz, factor.y) - div_ceil(self.yo_siz, factor.y)
441    }
442
443    /// Returns the effective downsampling factor for reconstruction,
444    /// combining component subsampling with skipped resolutions.
445    pub fn get_recon_downsampling(&self, comp_num: u32) -> Point {
446        let sr = self.skipped_resolutions;
447        let mut factor = Point::new(1u32 << sr, 1u32 << sr);
448        factor.x *= self.components[comp_num as usize].xr_siz as u32;
449        factor.y *= self.components[comp_num as usize].yr_siz as u32;
450        factor
451    }
452
453    /// Sets a flag bit in the Rsiz field.
454    pub fn set_rsiz_flag(&mut self, flag: u16) {
455        self.rsiz |= flag;
456    }
457
458    /// Clears a flag bit in the Rsiz field.
459    #[allow(dead_code)]
460    pub fn reset_rsiz_flag(&mut self, flag: u16) {
461        self.rsiz &= !flag;
462    }
463
464    /// Sets the number of resolution levels to skip during decoding.
465    pub fn set_skipped_resolutions(&mut self, sr: u32) {
466        self.skipped_resolutions = sr;
467    }
468
469    /// Validates the SIZ parameters.
470    ///
471    /// # Errors
472    ///
473    /// Returns [`OjphError::Codec`] if the image extent, tile size, or
474    /// offsets are invalid (zero extent, bad offsets, etc.).
475    pub fn check_validity(&self) -> Result<()> {
476        if self.xsiz == 0 || self.ysiz == 0 || self.xt_siz == 0 || self.yt_siz == 0 {
477            return Err(OjphError::Codec {
478                code: 0x00040001,
479                message: "Image extent and/or tile size cannot be zero".into(),
480            });
481        }
482        if self.xto_siz > self.xo_siz || self.yto_siz > self.yo_siz {
483            return Err(OjphError::Codec {
484                code: 0x00040002,
485                message: "Tile offset has to be smaller than the image offset".into(),
486            });
487        }
488        if self.xt_siz + self.xto_siz <= self.xo_siz || self.yt_siz + self.yto_siz <= self.yo_siz {
489            return Err(OjphError::Codec {
490                code: 0x00040003,
491                message: "The top left tile must intersect with the image".into(),
492            });
493        }
494        if self.xsiz <= self.xo_siz || self.ysiz <= self.yo_siz {
495            return Err(OjphError::Codec {
496                code: 0x00040004,
497                message: "Image extent must be larger than image offset".into(),
498            });
499        }
500        Ok(())
501    }
502
503    pub fn write(&mut self, file: &mut dyn OutfileBase) -> Result<bool> {
504        self.lsiz = 38 + 3 * self.csiz;
505        let mut ok = true;
506        ok &= write_u16_be(file, markers::SIZ)?;
507        ok &= write_u16_be(file, self.lsiz)?;
508        ok &= write_u16_be(file, self.rsiz)?;
509        ok &= write_u32_be(file, self.xsiz)?;
510        ok &= write_u32_be(file, self.ysiz)?;
511        ok &= write_u32_be(file, self.xo_siz)?;
512        ok &= write_u32_be(file, self.yo_siz)?;
513        ok &= write_u32_be(file, self.xt_siz)?;
514        ok &= write_u32_be(file, self.yt_siz)?;
515        ok &= write_u32_be(file, self.xto_siz)?;
516        ok &= write_u32_be(file, self.yto_siz)?;
517        ok &= write_u16_be(file, self.csiz)?;
518        for c in &self.components {
519            ok &= write_u8(file, c.ssiz)?;
520            ok &= write_u8(file, c.xr_siz)?;
521            ok &= write_u8(file, c.yr_siz)?;
522        }
523        Ok(ok)
524    }
525
526    pub fn read(&mut self, file: &mut dyn InfileBase) -> Result<()> {
527        self.lsiz = read_u16_be(file).map_err(|_| OjphError::Codec {
528            code: 0x00050041,
529            message: "error reading SIZ marker".into(),
530        })?;
531        if self.lsiz < 38 {
532            return Err(OjphError::Codec {
533                code: 0x00050042,
534                message: "error in SIZ marker length".into(),
535            });
536        }
537        let num_comps = ((self.lsiz - 38) / 3) as i32;
538        if self.lsiz != 38 + 3 * num_comps as u16 {
539            return Err(OjphError::Codec {
540                code: 0x00050042,
541                message: "error in SIZ marker length".into(),
542            });
543        }
544        self.rsiz = read_u16_be(file).map_err(|_| OjphError::Codec {
545            code: 0x00050043,
546            message: "error reading SIZ marker".into(),
547        })?;
548        if (self.rsiz & 0x4000) == 0 {
549            return Err(OjphError::Codec {
550                code: 0x00050044,
551                message: "Rsiz bit 14 is not set (this is not a JPH file)".into(),
552            });
553        }
554        self.xsiz = read_u32_be(file).map_err(|_| OjphError::Codec {
555            code: 0x00050045,
556            message: "error reading SIZ marker".into(),
557        })?;
558        self.ysiz = read_u32_be(file).map_err(|_| OjphError::Codec {
559            code: 0x00050046,
560            message: "error reading SIZ marker".into(),
561        })?;
562        let xo = read_u32_be(file).map_err(|_| OjphError::Codec {
563            code: 0x00050047,
564            message: "error reading SIZ marker".into(),
565        })?;
566        let yo = read_u32_be(file).map_err(|_| OjphError::Codec {
567            code: 0x00050048,
568            message: "error reading SIZ marker".into(),
569        })?;
570        self.set_image_offset(Point::new(xo, yo));
571        let xtw = read_u32_be(file).map_err(|_| OjphError::Codec {
572            code: 0x00050049,
573            message: "error reading SIZ marker".into(),
574        })?;
575        let yth = read_u32_be(file).map_err(|_| OjphError::Codec {
576            code: 0x0005004A,
577            message: "error reading SIZ marker".into(),
578        })?;
579        self.set_tile_size(Size::new(xtw, yth));
580        let xto = read_u32_be(file).map_err(|_| OjphError::Codec {
581            code: 0x0005004B,
582            message: "error reading SIZ marker".into(),
583        })?;
584        let yto = read_u32_be(file).map_err(|_| OjphError::Codec {
585            code: 0x0005004C,
586            message: "error reading SIZ marker".into(),
587        })?;
588        self.set_tile_offset(Point::new(xto, yto));
589        self.csiz = read_u16_be(file).map_err(|_| OjphError::Codec {
590            code: 0x0005004D,
591            message: "error reading SIZ marker".into(),
592        })?;
593        if self.csiz as i32 != num_comps {
594            return Err(OjphError::Codec {
595                code: 0x0005004E,
596                message: "Csiz does not match the SIZ marker size".into(),
597            });
598        }
599        self.set_num_components(self.csiz as u32);
600        for c in 0..self.csiz as usize {
601            self.components[c].ssiz = read_u8(file).map_err(|_| OjphError::Codec {
602                code: 0x00050051,
603                message: "error reading SIZ marker".into(),
604            })?;
605            self.components[c].xr_siz = read_u8(file).map_err(|_| OjphError::Codec {
606                code: 0x00050052,
607                message: "error reading SIZ marker".into(),
608            })?;
609            self.components[c].yr_siz = read_u8(file).map_err(|_| OjphError::Codec {
610                code: 0x00050053,
611                message: "error reading SIZ marker".into(),
612            })?;
613            if (self.components[c].ssiz & 0x7F) > 37 {
614                return Err(OjphError::Codec {
615                    code: 0x00050054,
616                    message: format!("Wrong SIZ-SSiz value of {}", self.components[c].ssiz),
617                });
618            }
619            if self.components[c].xr_siz == 0 {
620                return Err(OjphError::Codec {
621                    code: 0x00050055,
622                    message: format!("Wrong SIZ-XRsiz value of {}", self.components[c].xr_siz),
623                });
624            }
625            if self.components[c].yr_siz == 0 {
626                return Err(OjphError::Codec {
627                    code: 0x00050056,
628                    message: format!("Wrong SIZ-YRsiz value of {}", self.components[c].yr_siz),
629                });
630            }
631        }
632        self.check_validity()?;
633        Ok(())
634    }
635}
636
637// =========================================================================
638// COD/COC SPcod sub-structure
639// =========================================================================
640
641#[derive(Debug, Clone)]
642pub(crate) struct CodSPcod {
643    pub num_decomp: u8,
644    pub block_width: u8,
645    pub block_height: u8,
646    pub block_style: u8,
647    pub wavelet_trans: u8,
648    pub precinct_size: [u8; 33],
649}
650
651impl Default for CodSPcod {
652    fn default() -> Self {
653        Self {
654            num_decomp: 5,
655            block_width: 4,    // 2^(4+2)=64
656            block_height: 4,   // 2^(4+2)=64
657            block_style: 0x40, // HT mode
658            wavelet_trans: 0,  // reversible 5/3
659            precinct_size: [0; 33],
660        }
661    }
662}
663
664impl CodSPcod {
665    pub fn get_log_block_dims(&self) -> Size {
666        Size::new(
667            (self.block_width + 2) as u32,
668            (self.block_height + 2) as u32,
669        )
670    }
671
672    pub fn get_block_dims(&self) -> Size {
673        let t = self.get_log_block_dims();
674        Size::new(1 << t.w, 1 << t.h)
675    }
676
677    pub fn get_log_precinct_size(&self, res_num: u32) -> Size {
678        let p = self.precinct_size[res_num as usize];
679        Size::new((p & 0xF) as u32, (p >> 4) as u32)
680    }
681}
682
683// COD SGcod sub-structure
684#[derive(Debug, Clone)]
685pub(crate) struct CodSGcod {
686    pub prog_order: u8,
687    pub num_layers: u16,
688    pub mc_trans: u8,
689}
690
691impl Default for CodSGcod {
692    fn default() -> Self {
693        Self {
694            prog_order: ProgressionOrder::RPCL as u8,
695            num_layers: 1,
696            mc_trans: 0,
697        }
698    }
699}
700
701// =========================================================================
702// Block coding style constants
703// =========================================================================
704
705#[allow(dead_code)]
706pub(crate) const VERT_CAUSAL_MODE: u8 = 0x8;
707pub(crate) const HT_MODE: u8 = 0x40;
708
709// DWT type
710pub(crate) const DWT_IRV97: u8 = 0;
711pub(crate) const DWT_REV53: u8 = 1;
712
713// COD type
714#[derive(Debug, Clone, Copy, PartialEq, Eq)]
715pub(crate) enum CodType {
716    #[allow(dead_code)]
717    Undefined,
718    CodMain,
719    CocMain,
720}
721
722// =========================================================================
723// param_cod — Coding Style Default / Component
724// =========================================================================
725
726pub(crate) const COD_DEFAULT_COMP: u16 = 65535;
727#[allow(dead_code)]
728pub(crate) const COD_UNKNOWN_COMP: u16 = 65534;
729
730/// COD/COC marker segment — coding style parameters.
731///
732/// Holds default coding parameters (COD) and optionally per-component
733/// overrides (COC). Controls the wavelet transform, block coder settings,
734/// precinct sizes, and progression order.
735#[derive(Debug, Clone)]
736pub struct ParamCod {
737    pub(crate) cod_type: CodType,
738    pub(crate) lcod: u16,
739    pub(crate) scod: u8,
740    pub(crate) sg_cod: CodSGcod,
741    pub(crate) sp_cod: CodSPcod,
742    pub(crate) comp_idx: u16,
743    /// COC children chained here
744    pub(crate) children: Vec<ParamCod>,
745}
746
747impl Default for ParamCod {
748    fn default() -> Self {
749        Self {
750            cod_type: CodType::CodMain,
751            lcod: 0,
752            scod: 0,
753            sg_cod: CodSGcod::default(),
754            sp_cod: CodSPcod::default(),
755            comp_idx: COD_DEFAULT_COMP,
756            children: Vec::new(),
757        }
758    }
759}
760
761impl ParamCod {
762    /// Creates a new COC (coding style component) instance for the given
763    /// component index.
764    pub fn new_coc(comp_idx: u16) -> Self {
765        Self {
766            cod_type: CodType::CocMain,
767            lcod: 0,
768            scod: 0,
769            sg_cod: CodSGcod::default(),
770            sp_cod: CodSPcod::default(),
771            comp_idx,
772            children: Vec::new(),
773        }
774    }
775
776    /// Sets whether the wavelet transform is reversible (lossless 5/3) or
777    /// irreversible (lossy 9/7).
778    pub fn set_reversible(&mut self, reversible: bool) {
779        self.sp_cod.wavelet_trans = if reversible { DWT_REV53 } else { DWT_IRV97 };
780    }
781
782    /// Enables or disables the multi-component (color) transform.
783    ///
784    /// When enabled with a reversible transform, RCT is used; with
785    /// irreversible, ICT is used. Requires ≥ 3 components.
786    pub fn set_color_transform(&mut self, ct: bool) {
787        self.sg_cod.mc_trans = if ct { 1 } else { 0 };
788    }
789
790    /// Sets the number of DWT decomposition levels (0–32).
791    pub fn set_num_decomposition(&mut self, num: u32) {
792        self.sp_cod.num_decomp = num as u8;
793    }
794
795    /// Sets the code block dimensions. Both `width` and `height` must be
796    /// powers of two in the range 4–1024 and their product ≤ 4096.
797    pub fn set_block_dims(&mut self, width: u32, height: u32) {
798        self.sp_cod.block_width = (width as f64).log2() as u8 - 2;
799        self.sp_cod.block_height = (height as f64).log2() as u8 - 2;
800    }
801
802    /// Sets custom precinct sizes per resolution level.
803    pub fn set_precinct_size(&mut self, num_levels: i32, sizes: &[Size]) {
804        if num_levels > 0 && !sizes.is_empty() {
805            self.scod |= 1;
806            for i in 0..=self.sp_cod.num_decomp as usize {
807                let idx = i.min(sizes.len() - 1);
808                let w = (sizes[idx].w as f64).log2() as u8;
809                let h = (sizes[idx].h as f64).log2() as u8;
810                self.sp_cod.precinct_size[i] = w | (h << 4);
811            }
812        }
813    }
814
815    /// Sets the progression order by name (e.g. `"LRCP"`, `"RPCL"`).
816    ///
817    /// # Errors
818    ///
819    /// Returns [`OjphError::InvalidParam`] if `name` is not a recognised
820    /// progression order string.
821    pub fn set_progression_order(&mut self, name: &str) -> Result<()> {
822        match ProgressionOrder::from_str(name) {
823            Some(po) => {
824                self.sg_cod.prog_order = po as u8;
825                Ok(())
826            }
827            None => Err(OjphError::InvalidParam(format!(
828                "unknown progression order: {}",
829                name
830            ))),
831        }
832    }
833
834    /// Returns the number of DWT decomposition levels.
835    pub fn get_num_decompositions(&self) -> u8 {
836        if self.cod_type == CodType::CocMain && self.is_dfs_defined() {
837            self.sp_cod.num_decomp & 0x7F
838        } else {
839            self.sp_cod.num_decomp
840        }
841    }
842
843    /// Returns the code block dimensions.
844    pub fn get_block_dims(&self) -> Size {
845        self.sp_cod.get_block_dims()
846    }
847
848    /// Returns the log₂ code block dimensions.
849    pub fn get_log_block_dims(&self) -> Size {
850        self.sp_cod.get_log_block_dims()
851    }
852
853    /// Returns the wavelet kernel type (0 = irreversible 9/7, 1 = reversible 5/3).
854    pub fn get_wavelet_kern(&self) -> u8 {
855        self.sp_cod.wavelet_trans
856    }
857
858    /// Returns `true` if using the reversible (lossless) 5/3 wavelet.
859    pub fn is_reversible(&self) -> bool {
860        self.sp_cod.wavelet_trans == DWT_REV53
861    }
862
863    /// Returns `true` if the multi-component color transform is enabled.
864    pub fn is_employing_color_transform(&self) -> bool {
865        self.sg_cod.mc_trans == 1
866    }
867
868    /// Returns the precinct size at the given resolution level.
869    pub fn get_precinct_size(&self, res_num: u32) -> Size {
870        let t = self.get_log_precinct_size(res_num);
871        Size::new(1 << t.w, 1 << t.h)
872    }
873
874    /// Returns the log₂ precinct size at the given resolution level.
875    pub fn get_log_precinct_size(&self, res_num: u32) -> Size {
876        if self.scod & 1 != 0 {
877            self.sp_cod.get_log_precinct_size(res_num)
878        } else {
879            Size::new(15, 15)
880        }
881    }
882
883    /// Returns `true` if SOP markers may be used in packets.
884    pub fn packets_may_use_sop(&self) -> bool {
885        if self.cod_type == CodType::CodMain {
886            (self.scod & 2) == 2
887        } else {
888            false
889        }
890    }
891
892    /// Returns `true` if EPH markers are used in packets.
893    pub fn packets_use_eph(&self) -> bool {
894        if self.cod_type == CodType::CodMain {
895            (self.scod & 4) == 4
896        } else {
897            false
898        }
899    }
900
901    /// Returns `true` if vertical causal context is enabled.
902    pub fn get_block_vertical_causality(&self) -> bool {
903        (self.sp_cod.block_style & VERT_CAUSAL_MODE) != 0
904    }
905
906    /// Returns the progression order as an integer.
907    pub fn get_progression_order(&self) -> i32 {
908        self.sg_cod.prog_order as i32
909    }
910
911    /// Returns the progression order as a four-character string.
912    pub fn get_progression_order_as_string(&self) -> &'static str {
913        ProgressionOrder::from_i32(self.sg_cod.prog_order as i32)
914            .unwrap_or(ProgressionOrder::LRCP)
915            .as_str()
916    }
917
918    /// Returns the number of quality layers.
919    pub fn get_num_layers(&self) -> i32 {
920        self.sg_cod.num_layers as i32
921    }
922
923    /// Returns `true` if a DFS marker is referenced.
924    pub fn is_dfs_defined(&self) -> bool {
925        (self.sp_cod.num_decomp & 0x80) != 0
926    }
927
928    /// Returns the DFS marker index.
929    #[allow(dead_code)]
930    pub fn get_dfs_index(&self) -> u16 {
931        (self.sp_cod.num_decomp & 0xF) as u16
932    }
933
934    /// Returns the component index this COC applies to.
935    pub fn get_comp_idx(&self) -> u16 {
936        self.comp_idx
937    }
938
939    /// Returns the COC for a specific component, falling back to the COD
940    /// defaults if no per-component override exists.
941    pub fn get_coc(&self, comp_idx: u32) -> &ParamCod {
942        for child in &self.children {
943            if child.comp_idx == comp_idx as u16 {
944                return child;
945            }
946        }
947        self
948    }
949
950    /// Returns a mutable reference to the COC for a specific component.
951    pub fn get_coc_mut(&mut self, comp_idx: u32) -> &mut ParamCod {
952        for i in 0..self.children.len() {
953            if self.children[i].comp_idx == comp_idx as u16 {
954                return &mut self.children[i];
955            }
956        }
957        self
958    }
959
960    /// Adds a new COC override for the specified component.
961    pub fn add_coc(&mut self, comp_idx: u32) -> &mut ParamCod {
962        let coc = ParamCod::new_coc(comp_idx as u16);
963        self.children.push(coc);
964        self.children.last_mut().unwrap()
965    }
966
967    pub fn check_validity(&self, siz: &ParamSiz) -> Result<()> {
968        debug_assert!(self.cod_type == CodType::CodMain);
969        let num_comps = siz.get_num_components();
970        if self.sg_cod.mc_trans == 1 && num_comps < 3 {
971            return Err(OjphError::Codec {
972                code: 0x00040011,
973                message: "color transform needs 3+ components".into(),
974            });
975        }
976        if self.sg_cod.mc_trans == 1 {
977            let p = siz.get_downsampling(0);
978            let bd = siz.get_bit_depth(0);
979            let s = siz.is_signed(0);
980            for i in 1..3u32 {
981                let pi = siz.get_downsampling(i);
982                if p.x != pi.x || p.y != pi.y {
983                    return Err(OjphError::Codec {
984                        code: 0x00040012,
985                        message:
986                            "color transform requires same downsampling for first 3 components"
987                                .into(),
988                    });
989                }
990                if bd != siz.get_bit_depth(i) {
991                    return Err(OjphError::Codec {
992                        code: 0x00040014,
993                        message: "color transform requires same bit depth for first 3 components"
994                            .into(),
995                    });
996                }
997                if s != siz.is_signed(i) {
998                    return Err(OjphError::Codec {
999                        code: 0x00040015,
1000                        message: "color transform requires same signedness for first 3 components"
1001                            .into(),
1002                    });
1003                }
1004            }
1005        }
1006        Ok(())
1007    }
1008
1009    pub fn write(&mut self, file: &mut dyn OutfileBase) -> Result<bool> {
1010        debug_assert!(self.cod_type == CodType::CodMain);
1011        self.lcod = 12;
1012        if self.scod & 1 != 0 {
1013            self.lcod += 1 + self.sp_cod.num_decomp as u16;
1014        }
1015        let mut ok = true;
1016        ok &= write_u16_be(file, markers::COD)?;
1017        ok &= write_u16_be(file, self.lcod)?;
1018        ok &= write_u8(file, self.scod)?;
1019        ok &= write_u8(file, self.sg_cod.prog_order)?;
1020        ok &= write_u16_be(file, self.sg_cod.num_layers)?;
1021        ok &= write_u8(file, self.sg_cod.mc_trans)?;
1022        ok &= write_u8(file, self.sp_cod.num_decomp)?;
1023        ok &= write_u8(file, self.sp_cod.block_width)?;
1024        ok &= write_u8(file, self.sp_cod.block_height)?;
1025        ok &= write_u8(file, self.sp_cod.block_style)?;
1026        ok &= write_u8(file, self.sp_cod.wavelet_trans)?;
1027        if self.scod & 1 != 0 {
1028            for i in 0..=self.sp_cod.num_decomp as usize {
1029                ok &= write_u8(file, self.sp_cod.precinct_size[i])?;
1030            }
1031        }
1032        Ok(ok)
1033    }
1034
1035    pub fn write_coc(&self, file: &mut dyn OutfileBase, num_comps: u32) -> Result<bool> {
1036        let mut ok = true;
1037        for child in &self.children {
1038            if (child.comp_idx as u32) < num_comps {
1039                ok &= child.internal_write_coc(file, num_comps)?;
1040            }
1041        }
1042        Ok(ok)
1043    }
1044
1045    fn internal_write_coc(&self, file: &mut dyn OutfileBase, num_comps: u32) -> Result<bool> {
1046        let lcod: u16 = if num_comps < 257 { 9 } else { 10 }
1047            + if self.scod & 1 != 0 {
1048                1 + self.sp_cod.num_decomp as u16
1049            } else {
1050                0
1051            };
1052        let mut ok = true;
1053        ok &= write_u16_be(file, markers::COC)?;
1054        ok &= write_u16_be(file, lcod)?;
1055        if num_comps < 257 {
1056            ok &= write_u8(file, self.comp_idx as u8)?;
1057        } else {
1058            ok &= write_u16_be(file, self.comp_idx)?;
1059        }
1060        ok &= write_u8(file, self.scod)?;
1061        ok &= write_u8(file, self.sp_cod.num_decomp)?;
1062        ok &= write_u8(file, self.sp_cod.block_width)?;
1063        ok &= write_u8(file, self.sp_cod.block_height)?;
1064        ok &= write_u8(file, self.sp_cod.block_style)?;
1065        ok &= write_u8(file, self.sp_cod.wavelet_trans)?;
1066        if self.scod & 1 != 0 {
1067            for i in 0..=self.sp_cod.num_decomp as usize {
1068                ok &= write_u8(file, self.sp_cod.precinct_size[i])?;
1069            }
1070        }
1071        Ok(ok)
1072    }
1073
1074    pub fn read(&mut self, file: &mut dyn InfileBase) -> Result<()> {
1075        self.lcod = read_u16_be(file).map_err(|_| OjphError::Codec {
1076            code: 0x00050071,
1077            message: "error reading COD segment".into(),
1078        })?;
1079        self.scod = read_u8(file).map_err(|_| OjphError::Codec {
1080            code: 0x00050072,
1081            message: "error reading COD segment".into(),
1082        })?;
1083        self.sg_cod.prog_order = read_u8(file).map_err(|_| OjphError::Codec {
1084            code: 0x00050073,
1085            message: "error reading COD segment".into(),
1086        })?;
1087        self.sg_cod.num_layers = read_u16_be(file).map_err(|_| OjphError::Codec {
1088            code: 0x00050074,
1089            message: "error reading COD segment".into(),
1090        })?;
1091        self.sg_cod.mc_trans = read_u8(file).map_err(|_| OjphError::Codec {
1092            code: 0x00050075,
1093            message: "error reading COD segment".into(),
1094        })?;
1095        self.sp_cod.num_decomp = read_u8(file).map_err(|_| OjphError::Codec {
1096            code: 0x00050076,
1097            message: "error reading COD segment".into(),
1098        })?;
1099        self.sp_cod.block_width = read_u8(file).map_err(|_| OjphError::Codec {
1100            code: 0x00050077,
1101            message: "error reading COD segment".into(),
1102        })?;
1103        self.sp_cod.block_height = read_u8(file).map_err(|_| OjphError::Codec {
1104            code: 0x00050078,
1105            message: "error reading COD segment".into(),
1106        })?;
1107        self.sp_cod.block_style = read_u8(file).map_err(|_| OjphError::Codec {
1108            code: 0x00050079,
1109            message: "error reading COD segment".into(),
1110        })?;
1111        self.sp_cod.wavelet_trans = read_u8(file).map_err(|_| OjphError::Codec {
1112            code: 0x0005007A,
1113            message: "error reading COD segment".into(),
1114        })?;
1115
1116        if self.get_num_decompositions() > 32
1117            || self.sp_cod.block_width > 8
1118            || self.sp_cod.block_height > 8
1119            || self.sp_cod.block_width + self.sp_cod.block_height > 8
1120            || (self.sp_cod.block_style & HT_MODE) != HT_MODE
1121            || (self.sp_cod.block_style & 0xB7) != 0x00
1122        {
1123            return Err(OjphError::Codec {
1124                code: 0x0005007D,
1125                message: "wrong settings in COD-SPcod parameter".into(),
1126            });
1127        }
1128
1129        let nd = self.get_num_decompositions();
1130        if self.scod & 1 != 0 {
1131            for i in 0..=nd as usize {
1132                self.sp_cod.precinct_size[i] = read_u8(file).map_err(|_| OjphError::Codec {
1133                    code: 0x0005007B,
1134                    message: "error reading COD segment".into(),
1135                })?;
1136            }
1137        }
1138        let expected = 12
1139            + if self.scod & 1 != 0 {
1140                1 + self.sp_cod.num_decomp as u16
1141            } else {
1142                0
1143            };
1144        if self.lcod != expected {
1145            return Err(OjphError::Codec {
1146                code: 0x0005007C,
1147                message: "error in COD segment length".into(),
1148            });
1149        }
1150        Ok(())
1151    }
1152
1153    pub fn read_coc(&mut self, file: &mut dyn InfileBase, num_comps: u32) -> Result<()> {
1154        self.cod_type = CodType::CocMain;
1155        self.lcod = read_u16_be(file).map_err(|_| OjphError::Codec {
1156            code: 0x00050121,
1157            message: "error reading COC segment".into(),
1158        })?;
1159        if num_comps < 257 {
1160            self.comp_idx = read_u8(file).map_err(|_| OjphError::Codec {
1161                code: 0x00050122,
1162                message: "error reading COC segment".into(),
1163            })? as u16;
1164        } else {
1165            self.comp_idx = read_u16_be(file).map_err(|_| OjphError::Codec {
1166                code: 0x00050123,
1167                message: "error reading COC segment".into(),
1168            })?;
1169        }
1170        self.scod = read_u8(file).map_err(|_| OjphError::Codec {
1171            code: 0x00050124,
1172            message: "error reading COC segment".into(),
1173        })?;
1174        self.sp_cod.num_decomp = read_u8(file).map_err(|_| OjphError::Codec {
1175            code: 0x00050125,
1176            message: "error reading COC segment".into(),
1177        })?;
1178        self.sp_cod.block_width = read_u8(file).map_err(|_| OjphError::Codec {
1179            code: 0x00050126,
1180            message: "error reading COC segment".into(),
1181        })?;
1182        self.sp_cod.block_height = read_u8(file).map_err(|_| OjphError::Codec {
1183            code: 0x00050127,
1184            message: "error reading COC segment".into(),
1185        })?;
1186        self.sp_cod.block_style = read_u8(file).map_err(|_| OjphError::Codec {
1187            code: 0x00050128,
1188            message: "error reading COC segment".into(),
1189        })?;
1190        self.sp_cod.wavelet_trans = read_u8(file).map_err(|_| OjphError::Codec {
1191            code: 0x00050129,
1192            message: "error reading COC segment".into(),
1193        })?;
1194
1195        if self.get_num_decompositions() > 32
1196            || self.sp_cod.block_width > 8
1197            || self.sp_cod.block_height > 8
1198            || (self.sp_cod.block_style & HT_MODE) != HT_MODE
1199            || (self.sp_cod.block_style & 0xB7) != 0x00
1200        {
1201            return Err(OjphError::Codec {
1202                code: 0x0005012C,
1203                message: "wrong settings in COC-SPcoc parameter".into(),
1204            });
1205        }
1206
1207        let nd = self.get_num_decompositions();
1208        if self.scod & 1 != 0 {
1209            for i in 0..=nd as usize {
1210                self.sp_cod.precinct_size[i] = read_u8(file).map_err(|_| OjphError::Codec {
1211                    code: 0x0005012A,
1212                    message: "error reading COC segment".into(),
1213                })?;
1214            }
1215        }
1216        let mut expected: u32 = 9 + if num_comps < 257 { 0 } else { 1 };
1217        expected += if self.scod & 1 != 0 { 1 + nd as u32 } else { 0 };
1218        if self.lcod as u32 != expected {
1219            return Err(OjphError::Codec {
1220                code: 0x0005012B,
1221                message: "error in COC segment length".into(),
1222            });
1223        }
1224        Ok(())
1225    }
1226}
1227
1228// =========================================================================
1229// param_qcd — Quantization Default / Component
1230// =========================================================================
1231
1232pub(crate) const QCD_DEFAULT_COMP: u16 = 65535;
1233
1234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1235pub(crate) enum QcdType {
1236    QcdMain,
1237    QccMain,
1238}
1239
1240/// Quantization step data — reversible uses u8, irreversible uses u16.
1241#[derive(Debug, Clone)]
1242pub(crate) enum SpqcdData {
1243    Reversible(Vec<u8>),
1244    Irreversible(Vec<u16>),
1245}
1246
1247impl Default for SpqcdData {
1248    fn default() -> Self {
1249        SpqcdData::Reversible(Vec::new())
1250    }
1251}
1252
1253/// QCD/QCC marker segment — quantization parameters.
1254///
1255/// Defines the quantization step sizes for all subbands (QCD = default,
1256/// QCC = per-component override). For reversible transforms, the step
1257/// sizes encode exponent information; for irreversible transforms,
1258/// mantissa+exponent pairs are stored.
1259#[derive(Debug, Clone)]
1260pub struct ParamQcd {
1261    pub(crate) qcd_type: QcdType,
1262    pub(crate) lqcd: u16,
1263    pub(crate) sqcd: u8,
1264    pub(crate) sp_qcd: SpqcdData,
1265    pub(crate) num_subbands: u32,
1266    pub(crate) base_delta: f32,
1267    pub(crate) enabled: bool,
1268    pub(crate) comp_idx: u16,
1269    /// QCC children chained here (owned)
1270    pub(crate) children: Vec<ParamQcd>,
1271}
1272
1273impl Default for ParamQcd {
1274    fn default() -> Self {
1275        Self {
1276            qcd_type: QcdType::QcdMain,
1277            lqcd: 0,
1278            sqcd: 0,
1279            sp_qcd: SpqcdData::default(),
1280            num_subbands: 0,
1281            base_delta: -1.0,
1282            enabled: true,
1283            comp_idx: QCD_DEFAULT_COMP,
1284            children: Vec::new(),
1285        }
1286    }
1287}
1288
1289// BIBO gain tables for reversible quantization
1290mod bibo_gains {
1291    pub fn get_bibo_gain_l(num_decomps: u32, _rev: bool) -> f64 {
1292        static GAINS_L: [f64; 34] = [
1293            1.0,
1294            1.5,
1295            2.0,
1296            2.75,
1297            3.6875,
1298            4.96875,
1299            6.671875,
1300            8.953125,
1301            12.015625,
1302            16.125,
1303            21.65625,
1304            29.078125,
1305            39.046875,
1306            52.4375,
1307            70.421875,
1308            94.578125,
1309            127.015625,
1310            170.53125,
1311            229.015625,
1312            307.578125,
1313            413.015625,
1314            554.578125,
1315            744.734375,
1316            1000.234375,
1317            1343.015625,
1318            1803.234375,
1319            2420.734375,
1320            3250.734375,
1321            4365.234375,
1322            5862.234375,
1323            7871.234375,
1324            10571.234375,
1325            14198.734375,
1326            19066.734375,
1327        ];
1328        GAINS_L[num_decomps.min(33) as usize]
1329    }
1330
1331    pub fn get_bibo_gain_h(num_decomps: u32, _rev: bool) -> f64 {
1332        static GAINS_H: [f64; 34] = [
1333            2.0,
1334            2.75,
1335            3.6875,
1336            4.96875,
1337            6.671875,
1338            8.953125,
1339            12.015625,
1340            16.125,
1341            21.65625,
1342            29.078125,
1343            39.046875,
1344            52.4375,
1345            70.421875,
1346            94.578125,
1347            127.015625,
1348            170.53125,
1349            229.015625,
1350            307.578125,
1351            413.015625,
1352            554.578125,
1353            744.734375,
1354            1000.234375,
1355            1343.015625,
1356            1803.234375,
1357            2420.734375,
1358            3250.734375,
1359            4365.234375,
1360            5862.234375,
1361            7871.234375,
1362            10571.234375,
1363            14198.734375,
1364            19066.734375,
1365            25606.234375,
1366            34394.234375,
1367        ];
1368        GAINS_H[num_decomps.min(33) as usize]
1369    }
1370}
1371
1372// Sqrt energy gains for irreversible quantization
1373#[allow(clippy::excessive_precision)]
1374mod sqrt_energy_gains {
1375    pub fn get_gain_l(num_decomps: u32, _rev: bool) -> f32 {
1376        static GAINS_L: [f32; 34] = [
1377            1.0, 1.4021, 1.9692, 2.7665, 3.8873, 5.4645, 7.6816, 10.7968, 15.1781, 21.3348,
1378            29.9852, 42.1538, 59.2485, 83.2750, 117.0424, 164.4989, 231.2285, 325.0069, 456.8019,
1379            641.9960, 902.5009, 1268.6768, 1783.6345, 2506.8855, 3522.8044, 4952.2319, 6960.2544,
1380            9781.8203, 13748.4473, 19325.1445, 27167.4688, 38181.2344, 53668.6953, 75428.9531,
1381        ];
1382        GAINS_L[num_decomps.min(33) as usize]
1383    }
1384
1385    pub fn get_gain_h(num_decomps: u32, _rev: bool) -> f32 {
1386        static GAINS_H: [f32; 34] = [
1387            1.0, 1.4425, 2.0286, 2.8525, 4.0104, 5.6381, 7.9270, 11.1440, 15.6658, 22.0236,
1388            30.9537, 43.5079, 61.1495, 85.9350, 120.8194, 169.8440, 238.7607, 335.6575, 471.8611,
1389            663.2765, 932.3842, 1310.5906, 1842.0957, 2589.3047, 3639.6855, 5116.5957, 7193.1211,
1390            10112.9805, 14213.5547, 19979.6094, 28089.3984, 39483.7109, 55517.6484, 78048.0000,
1391        ];
1392        GAINS_H[num_decomps.min(33) as usize]
1393    }
1394}
1395
1396impl ParamQcd {
1397    /// Creates a new QCC (per-component quantization) instance.
1398    pub fn new_qcc(comp_idx: u16) -> Self {
1399        Self {
1400            qcd_type: QcdType::QccMain,
1401            comp_idx,
1402            ..Default::default()
1403        }
1404    }
1405
1406    /// Sets the base quantization step size (Δ) for irreversible coding.
1407    ///
1408    /// Typical values are in the range 0.0001–1.0. Smaller values yield
1409    /// higher quality and larger files.
1410    pub fn set_delta(&mut self, delta: f32) {
1411        self.base_delta = delta;
1412    }
1413
1414    /// Sets the base quantization step size for a specific component.
1415    pub fn set_delta_for_comp(&mut self, comp_idx: u32, delta: f32) {
1416        let qcc = self.get_or_add_qcc(comp_idx);
1417        qcc.base_delta = delta;
1418    }
1419
1420    /// Returns the number of guard bits.
1421    pub fn get_num_guard_bits(&self) -> u32 {
1422        (self.sqcd >> 5) as u32
1423    }
1424
1425    fn decode_spqcd(&self, v: u8) -> u8 {
1426        v >> 3
1427    }
1428
1429    fn encode_spqcd(&self, v: u8) -> u8 {
1430        v << 3
1431    }
1432
1433    pub fn get_magb(&self) -> u32 {
1434        let mut b = 0u32;
1435        self.compute_magb_for(&mut b);
1436        for child in &self.children {
1437            child.compute_magb_for(&mut b);
1438        }
1439        b
1440    }
1441
1442    fn compute_magb_for(&self, b: &mut u32) {
1443        let num_decomps = (self.num_subbands.saturating_sub(1)) / 3;
1444        let irrev = self.sqcd & 0x1F;
1445        if irrev == 0 {
1446            if let SpqcdData::Reversible(ref data) = self.sp_qcd {
1447                for i in 0..self.num_subbands.min(data.len() as u32) {
1448                    let t =
1449                        self.decode_spqcd(data[i as usize]) as u32 + self.get_num_guard_bits() - 1;
1450                    *b = (*b).max(t);
1451                }
1452            }
1453        } else if irrev == 2 {
1454            if let SpqcdData::Irreversible(ref data) = self.sp_qcd {
1455                for i in 0..self.num_subbands.min(data.len() as u32) {
1456                    let nb = num_decomps - if i > 0 { (i - 1) / 3 } else { 0 };
1457                    let t = (data[i as usize] >> 11) as u32 + self.get_num_guard_bits() - nb;
1458                    *b = (*b).max(t);
1459                }
1460            }
1461        }
1462    }
1463
1464    pub fn get_kmax(&self, _num_decompositions: u32, resolution: u32, subband: u32) -> u32 {
1465        let idx = if resolution > 0 {
1466            (resolution - 1) * 3 + subband
1467        } else {
1468            0
1469        };
1470        let idx = idx.min(self.num_subbands.saturating_sub(1));
1471        let irrev = self.sqcd & 0x1F;
1472        let num_bits = if irrev == 0 {
1473            if let SpqcdData::Reversible(ref data) = self.sp_qcd {
1474                let v = self.decode_spqcd(data[idx as usize]);
1475                if v == 0 {
1476                    0u32
1477                } else {
1478                    v as u32 - 1
1479                }
1480            } else {
1481                0
1482            }
1483        } else if irrev == 2 {
1484            if let SpqcdData::Irreversible(ref data) = self.sp_qcd {
1485                (data[idx as usize] >> 11) as u32 - 1
1486            } else {
1487                0
1488            }
1489        } else {
1490            0
1491        };
1492        num_bits + self.get_num_guard_bits()
1493    }
1494
1495    pub fn get_irrev_delta(&self, _num_decompositions: u32, resolution: u32, subband: u32) -> f32 {
1496        let arr: [f32; 4] = [1.0, 2.0, 2.0, 4.0];
1497        let idx = if resolution > 0 {
1498            (resolution - 1) * 3 + subband
1499        } else {
1500            0
1501        };
1502        let idx = idx.min(self.num_subbands.saturating_sub(1));
1503        if let SpqcdData::Irreversible(ref data) = self.sp_qcd {
1504            let eps = data[idx as usize] >> 11;
1505            let mut mantissa =
1506                ((data[idx as usize] & 0x7FF) | 0x800) as f32 * arr[subband as usize];
1507            mantissa /= (1u32 << 11) as f32;
1508            mantissa /= (1u32 << eps) as f32;
1509            mantissa
1510        } else {
1511            1.0
1512        }
1513    }
1514
1515    fn set_rev_quant(&mut self, num_decomps: u32, bit_depth: u32, employing_ct: bool) {
1516        // Keep one extra magnitude bit in the reversible quantization exponents so
1517        // the full centered sample range (e.g. unsigned 8-bit -> [-128, 127]) is
1518        // representable after the JPEG 2000 sign-magnitude conversion.
1519        let b = bit_depth + if employing_ct { 1 } else { 0 } + 1;
1520        let ns = 1 + 3 * num_decomps;
1521        let mut sp = vec![0u8; ns as usize];
1522        let mut s = 0usize;
1523        let bibo_l = bibo_gains::get_bibo_gain_l(num_decomps, true);
1524        let x = (bibo_l * bibo_l).ln() / LN_2;
1525        let x = x.ceil() as u32;
1526        sp[s] = (b + x) as u8;
1527        let mut max_bpx = b + x;
1528        s += 1;
1529        for d in (1..=num_decomps).rev() {
1530            let bl = bibo_gains::get_bibo_gain_l(d, true);
1531            let bh = bibo_gains::get_bibo_gain_h(d - 1, true);
1532            let x = ((bh * bl).ln() / LN_2).ceil() as u32;
1533            sp[s] = (b + x) as u8;
1534            max_bpx = max_bpx.max(b + x);
1535            s += 1;
1536            sp[s] = (b + x) as u8;
1537            max_bpx = max_bpx.max(b + x);
1538            s += 1;
1539            let x = ((bh * bh).ln() / LN_2).ceil() as u32;
1540            sp[s] = (b + x) as u8;
1541            max_bpx = max_bpx.max(b + x);
1542            s += 1;
1543        }
1544        let guard_bits = 1i32.max(max_bpx as i32 - 31);
1545        self.sqcd = (guard_bits as u8) << 5;
1546        for v in sp.iter_mut() {
1547            *v = self.encode_spqcd((*v as i32 - guard_bits) as u8);
1548        }
1549        self.sp_qcd = SpqcdData::Reversible(sp);
1550        self.num_subbands = ns;
1551    }
1552
1553    fn set_irrev_quant(&mut self, num_decomps: u32) {
1554        let guard_bits = 1u8;
1555        self.sqcd = (guard_bits << 5) | 0x2;
1556        let ns = 1 + 3 * num_decomps;
1557        let mut sp = vec![0u16; ns as usize];
1558        let mut s = 0usize;
1559
1560        let gain_l = sqrt_energy_gains::get_gain_l(num_decomps, false);
1561        let delta_b = self.base_delta / (gain_l * gain_l);
1562        let (exp, mantissa) = quantize_delta(delta_b);
1563        sp[s] = (exp << 11) | mantissa;
1564        s += 1;
1565
1566        for d in (1..=num_decomps).rev() {
1567            let gl = sqrt_energy_gains::get_gain_l(d, false);
1568            let gh = sqrt_energy_gains::get_gain_h(d - 1, false);
1569
1570            let delta_b = self.base_delta / (gl * gh);
1571            let (exp, mantissa) = quantize_delta(delta_b);
1572            sp[s] = (exp << 11) | mantissa;
1573            s += 1;
1574            sp[s] = (exp << 11) | mantissa;
1575            s += 1;
1576
1577            let delta_b = self.base_delta / (gh * gh);
1578            let (exp, mantissa) = quantize_delta(delta_b);
1579            sp[s] = (exp << 11) | mantissa;
1580            s += 1;
1581        }
1582        self.sp_qcd = SpqcdData::Irreversible(sp);
1583        self.num_subbands = ns;
1584    }
1585
1586    pub fn check_validity(&mut self, siz: &ParamSiz, cod: &ParamCod) -> Result<()> {
1587        let num_comps = siz.get_num_components() as u32;
1588
1589        let qcd_num_decomps = cod.get_num_decompositions() as u32;
1590        let qcd_bit_depth = siz.get_bit_depth(0);
1591        let qcd_wavelet_kern = cod.get_wavelet_kern();
1592        let employing_ct = cod.is_employing_color_transform();
1593
1594        self.num_subbands = 1 + 3 * qcd_num_decomps;
1595        if qcd_wavelet_kern == DWT_REV53 {
1596            self.set_rev_quant(qcd_num_decomps, qcd_bit_depth, employing_ct);
1597        } else {
1598            if self.base_delta < 0.0 {
1599                self.base_delta = 1.0 / (1u32 << qcd_bit_depth) as f32;
1600            }
1601            self.set_irrev_quant(qcd_num_decomps);
1602        }
1603
1604        // Process QCC children
1605        for child in &mut self.children {
1606            if child.comp_idx >= num_comps as u16 {
1607                child.enabled = false;
1608                continue;
1609            }
1610            let c = child.comp_idx as u32;
1611            let cp = cod.get_coc(c);
1612            let nd = cp.get_num_decompositions() as u32;
1613            child.num_subbands = 1 + 3 * nd;
1614            let bd = siz.get_bit_depth(c);
1615            if cp.get_wavelet_kern() == DWT_REV53 {
1616                child.set_rev_quant(nd, bd, if c < 3 { employing_ct } else { false });
1617            } else {
1618                if child.base_delta < 0.0 {
1619                    child.base_delta = 1.0 / (1u32 << bd) as f32;
1620                }
1621                child.set_irrev_quant(nd);
1622            }
1623        }
1624        Ok(())
1625    }
1626
1627    /// Get QCC for a component, or self if not found
1628    pub fn get_qcc(&self, comp_idx: u32) -> &ParamQcd {
1629        for child in &self.children {
1630            if child.comp_idx == comp_idx as u16 {
1631                return child;
1632            }
1633        }
1634        self
1635    }
1636
1637    pub fn get_or_add_qcc(&mut self, comp_idx: u32) -> &mut ParamQcd {
1638        for i in 0..self.children.len() {
1639            if self.children[i].comp_idx == comp_idx as u16 {
1640                return &mut self.children[i];
1641            }
1642        }
1643        let qcc = ParamQcd::new_qcc(comp_idx as u16);
1644        self.children.push(qcc);
1645        self.children.last_mut().unwrap()
1646    }
1647
1648    pub fn write(&mut self, file: &mut dyn OutfileBase) -> Result<bool> {
1649        let irrev = self.sqcd & 0x1F;
1650        self.lqcd = 3;
1651        if irrev == 0 {
1652            self.lqcd += self.num_subbands as u16;
1653        } else if irrev == 2 {
1654            self.lqcd += 2 * self.num_subbands as u16;
1655        }
1656        let mut ok = true;
1657        ok &= write_u16_be(file, markers::QCD)?;
1658        ok &= write_u16_be(file, self.lqcd)?;
1659        ok &= write_u8(file, self.sqcd)?;
1660        match &self.sp_qcd {
1661            SpqcdData::Reversible(data) => {
1662                for item in data.iter().take(self.num_subbands as usize) {
1663                    ok &= write_u8(file, *item)?;
1664                }
1665            }
1666            SpqcdData::Irreversible(data) => {
1667                for item in data.iter().take(self.num_subbands as usize) {
1668                    ok &= write_u16_be(file, *item)?;
1669                }
1670            }
1671        }
1672        Ok(ok)
1673    }
1674
1675    pub fn write_qcc(&self, file: &mut dyn OutfileBase, num_comps: u32) -> Result<bool> {
1676        let mut ok = true;
1677        for child in &self.children {
1678            if child.enabled {
1679                ok &= child.internal_write_qcc(file, num_comps)?;
1680            }
1681        }
1682        Ok(ok)
1683    }
1684
1685    fn internal_write_qcc(&self, file: &mut dyn OutfileBase, num_comps: u32) -> Result<bool> {
1686        let irrev = self.sqcd & 0x1F;
1687        let mut lqcd: u16 = 4 + if num_comps < 257 { 0 } else { 1 };
1688        if irrev == 0 {
1689            lqcd += self.num_subbands as u16;
1690        } else if irrev == 2 {
1691            lqcd += 2 * self.num_subbands as u16;
1692        }
1693        let mut ok = true;
1694        ok &= write_u16_be(file, markers::QCC)?;
1695        ok &= write_u16_be(file, lqcd)?;
1696        if num_comps < 257 {
1697            ok &= write_u8(file, self.comp_idx as u8)?;
1698        } else {
1699            ok &= write_u16_be(file, self.comp_idx)?;
1700        }
1701        ok &= write_u8(file, self.sqcd)?;
1702        match &self.sp_qcd {
1703            SpqcdData::Reversible(data) => {
1704                for item in data.iter().take(self.num_subbands as usize) {
1705                    ok &= write_u8(file, *item)?;
1706                }
1707            }
1708            SpqcdData::Irreversible(data) => {
1709                for item in data.iter().take(self.num_subbands as usize) {
1710                    ok &= write_u16_be(file, *item)?;
1711                }
1712            }
1713        }
1714        Ok(ok)
1715    }
1716
1717    pub fn read(&mut self, file: &mut dyn InfileBase) -> Result<()> {
1718        self.lqcd = read_u16_be(file).map_err(|_| OjphError::Codec {
1719            code: 0x00050081,
1720            message: "error reading QCD marker".into(),
1721        })?;
1722        self.sqcd = read_u8(file).map_err(|_| OjphError::Codec {
1723            code: 0x00050082,
1724            message: "error reading QCD marker".into(),
1725        })?;
1726        let irrev = self.sqcd & 0x1F;
1727        if irrev == 0 {
1728            self.num_subbands = (self.lqcd - 3) as u32;
1729            if self.num_subbands > 97 || self.lqcd != 3 + self.num_subbands as u16 {
1730                return Err(OjphError::Codec {
1731                    code: 0x00050083,
1732                    message: format!("wrong Lqcd value of {} in QCD marker", self.lqcd),
1733                });
1734            }
1735            let mut data = vec![0u8; self.num_subbands as usize];
1736            for item in data.iter_mut().take(self.num_subbands as usize) {
1737                *item = read_u8(file).map_err(|_| OjphError::Codec {
1738                    code: 0x00050084,
1739                    message: "error reading QCD marker".into(),
1740                })?;
1741            }
1742            self.sp_qcd = SpqcdData::Reversible(data);
1743        } else if irrev == 2 {
1744            self.num_subbands = ((self.lqcd - 3) / 2) as u32;
1745            if self.num_subbands > 97 || self.lqcd != 3 + 2 * self.num_subbands as u16 {
1746                return Err(OjphError::Codec {
1747                    code: 0x00050086,
1748                    message: format!("wrong Lqcd value of {} in QCD marker", self.lqcd),
1749                });
1750            }
1751            let mut data = vec![0u16; self.num_subbands as usize];
1752            for item in data.iter_mut().take(self.num_subbands as usize) {
1753                *item = read_u16_be(file).map_err(|_| OjphError::Codec {
1754                    code: 0x00050087,
1755                    message: "error reading QCD marker".into(),
1756                })?;
1757            }
1758            self.sp_qcd = SpqcdData::Irreversible(data);
1759        } else {
1760            return Err(OjphError::Codec {
1761                code: 0x00050088,
1762                message: "wrong Sqcd value in QCD marker".into(),
1763            });
1764        }
1765        Ok(())
1766    }
1767
1768    pub fn read_qcc(&mut self, file: &mut dyn InfileBase, num_comps: u32) -> Result<()> {
1769        self.qcd_type = QcdType::QccMain;
1770        self.lqcd = read_u16_be(file).map_err(|_| OjphError::Codec {
1771            code: 0x000500A1,
1772            message: "error reading QCC marker".into(),
1773        })?;
1774        if num_comps < 257 {
1775            self.comp_idx = read_u8(file).map_err(|_| OjphError::Codec {
1776                code: 0x000500A2,
1777                message: "error reading QCC marker".into(),
1778            })? as u16;
1779        } else {
1780            self.comp_idx = read_u16_be(file).map_err(|_| OjphError::Codec {
1781                code: 0x000500A3,
1782                message: "error reading QCC marker".into(),
1783            })?;
1784        }
1785        self.sqcd = read_u8(file).map_err(|_| OjphError::Codec {
1786            code: 0x000500A4,
1787            message: "error reading QCC marker".into(),
1788        })?;
1789        let offset: u32 = if num_comps < 257 { 4 } else { 5 };
1790        let irrev = self.sqcd & 0x1F;
1791        if irrev == 0 {
1792            self.num_subbands = (self.lqcd as u32).saturating_sub(offset);
1793            let mut data = vec![0u8; self.num_subbands as usize];
1794            for item in data.iter_mut().take(self.num_subbands as usize) {
1795                *item = read_u8(file).map_err(|_| OjphError::Codec {
1796                    code: 0x000500A6,
1797                    message: "error reading QCC marker".into(),
1798                })?;
1799            }
1800            self.sp_qcd = SpqcdData::Reversible(data);
1801        } else if irrev == 2 {
1802            self.num_subbands = ((self.lqcd as u32).saturating_sub(offset)) / 2;
1803            let mut data = vec![0u16; self.num_subbands as usize];
1804            for item in data.iter_mut().take(self.num_subbands as usize) {
1805                *item = read_u16_be(file).map_err(|_| OjphError::Codec {
1806                    code: 0x000500A9,
1807                    message: "error reading QCC marker".into(),
1808                })?;
1809            }
1810            self.sp_qcd = SpqcdData::Irreversible(data);
1811        } else {
1812            return Err(OjphError::Codec {
1813                code: 0x000500AA,
1814                message: "wrong Sqcc value in QCC marker".into(),
1815            });
1816        }
1817        Ok(())
1818    }
1819}
1820
1821fn quantize_delta(mut delta_b: f32) -> (u16, u16) {
1822    let mut exp: u16 = 0;
1823    while delta_b < 1.0 {
1824        exp += 1;
1825        delta_b *= 2.0;
1826    }
1827    let mut mantissa = (delta_b * (1u32 << 11) as f32).round() as i32 - (1i32 << 11);
1828    mantissa = mantissa.min(0x7FF);
1829    (exp, mantissa as u16)
1830}
1831
1832// =========================================================================
1833// param_cap — Extended Capability marker
1834// =========================================================================
1835
1836/// CAP marker segment — extended capability descriptor.
1837///
1838/// Identifies HTJ2K (Part 15) capabilities and parameters such as the
1839/// Ccap value that encodes the wavelet type and magnitude bound.
1840#[derive(Debug, Clone)]
1841pub struct ParamCap {
1842    pub(crate) lcap: u16,
1843    pub(crate) pcap: u32,
1844    pub(crate) ccap: [u16; 32],
1845}
1846
1847impl Default for ParamCap {
1848    fn default() -> Self {
1849        let mut cap = Self {
1850            lcap: 8,
1851            pcap: 0x00020000,
1852            ccap: [0u16; 32],
1853        };
1854        cap.ccap[0] = 0;
1855        cap
1856    }
1857}
1858
1859impl ParamCap {
1860    pub fn check_validity(&mut self, cod: &ParamCod, qcd: &ParamQcd) {
1861        if cod.get_wavelet_kern() == DWT_REV53 {
1862            self.ccap[0] &= 0xFFDF;
1863        } else {
1864            self.ccap[0] |= 0x0020;
1865        }
1866        self.ccap[0] &= 0xFFE0;
1867        let b = qcd.get_magb();
1868        let bp = if b <= 8 {
1869            0
1870        } else if b < 28 {
1871            b - 8
1872        } else {
1873            13 + (b >> 2)
1874        };
1875        self.ccap[0] |= bp as u16;
1876    }
1877
1878    pub fn write(&self, file: &mut dyn OutfileBase) -> Result<bool> {
1879        let mut ok = true;
1880        ok &= write_u16_be(file, markers::CAP)?;
1881        ok &= write_u16_be(file, self.lcap)?;
1882        ok &= write_u32_be(file, self.pcap)?;
1883        ok &= write_u16_be(file, self.ccap[0])?;
1884        Ok(ok)
1885    }
1886
1887    pub fn read(&mut self, file: &mut dyn InfileBase) -> Result<()> {
1888        self.lcap = read_u16_be(file).map_err(|_| OjphError::Codec {
1889            code: 0x00050061,
1890            message: "error reading CAP marker".into(),
1891        })?;
1892        self.pcap = read_u32_be(file).map_err(|_| OjphError::Codec {
1893            code: 0x00050062,
1894            message: "error reading CAP marker".into(),
1895        })?;
1896        let count = population_count(self.pcap);
1897        if (self.pcap & 0x00020000) == 0 {
1898            return Err(OjphError::Codec {
1899                code: 0x00050064,
1900                message: "Pcap should have its 15th MSB set. Not a JPH file".into(),
1901            });
1902        }
1903        for i in 0..count as usize {
1904            self.ccap[i] = read_u16_be(file).map_err(|_| OjphError::Codec {
1905                code: 0x00050065,
1906                message: "error reading CAP marker".into(),
1907            })?;
1908        }
1909        if self.lcap != 6 + 2 * count as u16 {
1910            return Err(OjphError::Codec {
1911                code: 0x00050066,
1912                message: "error in CAP marker length".into(),
1913            });
1914        }
1915        Ok(())
1916    }
1917}
1918
1919// =========================================================================
1920// param_sot — Start of Tile-Part
1921// =========================================================================
1922
1923/// SOT marker segment — start of tile-part header.
1924///
1925/// Contains the tile index, tile-part length, tile-part index,
1926/// and total number of tile-parts.
1927#[derive(Debug, Clone, Default)]
1928pub struct ParamSot {
1929    pub(crate) isot: u16,
1930    pub(crate) psot: u32,
1931    pub(crate) tp_sot: u8,
1932    pub(crate) tn_sot: u8,
1933}
1934
1935impl ParamSot {
1936    pub fn init(
1937        &mut self,
1938        payload_length: u32,
1939        tile_idx: u16,
1940        tile_part_index: u8,
1941        num_tile_parts: u8,
1942    ) {
1943        self.psot = payload_length + 12;
1944        self.isot = tile_idx;
1945        self.tp_sot = tile_part_index;
1946        self.tn_sot = num_tile_parts;
1947    }
1948
1949    pub fn get_tile_index(&self) -> u16 {
1950        self.isot
1951    }
1952    pub fn get_payload_length(&self) -> u32 {
1953        if self.psot > 0 {
1954            self.psot - 12
1955        } else {
1956            0
1957        }
1958    }
1959    pub fn get_tile_part_index(&self) -> u8 {
1960        self.tp_sot
1961    }
1962    #[allow(dead_code)]
1963    pub fn get_num_tile_parts(&self) -> u8 {
1964        self.tn_sot
1965    }
1966
1967    pub fn write(&mut self, file: &mut dyn OutfileBase, payload_len: u32) -> Result<bool> {
1968        self.psot = payload_len + 14;
1969        let mut ok = true;
1970        ok &= write_u16_be(file, markers::SOT)?;
1971        ok &= write_u16_be(file, 10)?; // Lsot is always 10
1972        ok &= write_u16_be(file, self.isot)?;
1973        ok &= write_u32_be(file, self.psot)?;
1974        ok &= write_u8(file, self.tp_sot)?;
1975        ok &= write_u8(file, self.tn_sot)?;
1976        Ok(ok)
1977    }
1978
1979    pub fn read(&mut self, file: &mut dyn InfileBase, resilient: bool) -> Result<bool> {
1980        if resilient {
1981            let lsot = match read_u16_be(file) {
1982                Ok(v) => v,
1983                Err(_) => {
1984                    self.clear();
1985                    return Ok(false);
1986                }
1987            };
1988            if lsot != 10 {
1989                self.clear();
1990                return Ok(false);
1991            }
1992            self.isot = match read_u16_be(file) {
1993                Ok(v) => v,
1994                Err(_) => {
1995                    self.clear();
1996                    return Ok(false);
1997                }
1998            };
1999            if self.isot == 0xFFFF {
2000                self.clear();
2001                return Ok(false);
2002            }
2003            self.psot = match read_u32_be(file) {
2004                Ok(v) => v,
2005                Err(_) => {
2006                    self.clear();
2007                    return Ok(false);
2008                }
2009            };
2010            self.tp_sot = match read_u8(file) {
2011                Ok(v) => v,
2012                Err(_) => {
2013                    self.clear();
2014                    return Ok(false);
2015                }
2016            };
2017            self.tn_sot = match read_u8(file) {
2018                Ok(v) => v,
2019                Err(_) => {
2020                    self.clear();
2021                    return Ok(false);
2022                }
2023            };
2024        } else {
2025            let lsot = read_u16_be(file).map_err(|_| OjphError::Codec {
2026                code: 0x00050091,
2027                message: "error reading SOT marker".into(),
2028            })?;
2029            if lsot != 10 {
2030                return Err(OjphError::Codec {
2031                    code: 0x00050092,
2032                    message: "error in SOT length".into(),
2033                });
2034            }
2035            self.isot = read_u16_be(file).map_err(|_| OjphError::Codec {
2036                code: 0x00050093,
2037                message: "error reading SOT marker".into(),
2038            })?;
2039            if self.isot == 0xFFFF {
2040                return Err(OjphError::Codec {
2041                    code: 0x00050094,
2042                    message: "tile index in SOT marker cannot be 0xFFFF".into(),
2043                });
2044            }
2045            self.psot = read_u32_be(file).map_err(|_| OjphError::Codec {
2046                code: 0x00050095,
2047                message: "error reading SOT marker".into(),
2048            })?;
2049            self.tp_sot = read_u8(file).map_err(|_| OjphError::Codec {
2050                code: 0x00050096,
2051                message: "error reading SOT marker".into(),
2052            })?;
2053            self.tn_sot = read_u8(file).map_err(|_| OjphError::Codec {
2054                code: 0x00050097,
2055                message: "error reading SOT marker".into(),
2056            })?;
2057        }
2058        Ok(true)
2059    }
2060
2061    fn clear(&mut self) {
2062        self.isot = 0;
2063        self.psot = 0;
2064        self.tp_sot = 0;
2065        self.tn_sot = 0;
2066    }
2067}
2068
2069// =========================================================================
2070// param_tlm — Tile-part Length Marker
2071// =========================================================================
2072
2073/// A single (tile-index, tile-part-length) pair in a TLM marker.
2074#[derive(Debug, Clone, Default)]
2075pub struct TtlmPtlmPair {
2076    /// Tile index.
2077    pub ttlm: u16,
2078    /// Tile-part length (including the SOT header).
2079    pub ptlm: u32,
2080}
2081
2082/// TLM marker segment — tile-part length index.
2083///
2084/// Allows random access to tile-parts without sequentially parsing the
2085/// entire codestream.
2086#[derive(Debug, Clone, Default)]
2087pub struct ParamTlm {
2088    pub(crate) ltlm: u16,
2089    pub(crate) ztlm: u8,
2090    pub(crate) stlm: u8,
2091    pub(crate) pairs: Vec<TtlmPtlmPair>,
2092    pub(crate) next_pair_index: u32,
2093}
2094
2095impl ParamTlm {
2096    pub fn init(&mut self, num_pairs: u32) {
2097        self.pairs
2098            .resize(num_pairs as usize, TtlmPtlmPair::default());
2099        self.ltlm = 4 + 6 * num_pairs as u16;
2100        self.ztlm = 0;
2101        self.stlm = 0x60;
2102        self.next_pair_index = 0;
2103    }
2104
2105    pub fn set_next_pair(&mut self, ttlm: u16, ptlm: u32) {
2106        let idx = self.next_pair_index as usize;
2107        self.pairs[idx].ttlm = ttlm;
2108        self.pairs[idx].ptlm = ptlm + 14;
2109        self.next_pair_index += 1;
2110    }
2111
2112    pub fn write(&self, file: &mut dyn OutfileBase) -> Result<bool> {
2113        let mut ok = true;
2114        ok &= write_u16_be(file, markers::TLM)?;
2115        ok &= write_u16_be(file, self.ltlm)?;
2116        ok &= write_u8(file, self.ztlm)?;
2117        ok &= write_u8(file, self.stlm)?;
2118        for pair in &self.pairs {
2119            ok &= write_u16_be(file, pair.ttlm)?;
2120            ok &= write_u32_be(file, pair.ptlm)?;
2121        }
2122        Ok(ok)
2123    }
2124}
2125
2126// =========================================================================
2127// param_nlt — Non-Linearity Point Transformation
2128// =========================================================================
2129
2130pub(crate) const NLT_ALL_COMPS: u16 = 65535;
2131pub(crate) const NLT_NO_NLT: u8 = 0;
2132#[allow(dead_code)]
2133pub(crate) const NLT_BINARY_COMPLEMENT: u8 = 3;
2134pub(crate) const NLT_UNDEFINED: u8 = 255;
2135
2136/// NLT marker segment — non-linearity point transformation.
2137///
2138/// Provides per-component non-linear transforms (e.g. two's-complement
2139/// conversion for signed data).
2140#[derive(Debug, Clone)]
2141pub struct ParamNlt {
2142    pub(crate) lnlt: u16,
2143    pub(crate) cnlt: u16,
2144    pub(crate) bd_nlt: u8,
2145    pub(crate) tnlt: u8,
2146    pub(crate) enabled: bool,
2147    pub(crate) children: Vec<ParamNlt>,
2148}
2149
2150impl Default for ParamNlt {
2151    fn default() -> Self {
2152        Self {
2153            lnlt: 6,
2154            cnlt: NLT_ALL_COMPS,
2155            bd_nlt: 0,
2156            tnlt: NLT_UNDEFINED,
2157            enabled: false,
2158            children: Vec::new(),
2159        }
2160    }
2161}
2162
2163impl ParamNlt {
2164    /// Sets the non-linear transform type for a specific component.
2165    ///
2166    /// - `nl_type = 0` — no NLT
2167    /// - `nl_type = 3` — binary complement (two's-complement conversion)
2168    ///
2169    /// # Errors
2170    ///
2171    /// Returns [`OjphError::Unsupported`] if `nl_type` is not 0 or 3.
2172    pub fn set_nonlinear_transform(&mut self, comp_num: u32, nl_type: u8) -> Result<()> {
2173        if nl_type != NLT_NO_NLT && nl_type != NLT_BINARY_COMPLEMENT {
2174            return Err(OjphError::Unsupported(
2175                "only NLT types 0 and 3 are supported".into(),
2176            ));
2177        }
2178        let child = self.get_or_add_child(comp_num);
2179        child.tnlt = nl_type;
2180        child.enabled = true;
2181        Ok(())
2182    }
2183
2184    /// Returns `(bit_depth, is_signed, nl_type)` for a component,
2185    /// or `None` if no NLT is configured.
2186    pub fn get_nonlinear_transform(&self, comp_num: u32) -> Option<(u8, bool, u8)> {
2187        for child in &self.children {
2188            if child.cnlt == comp_num as u16 && child.enabled {
2189                let bd = (child.bd_nlt & 0x7F) + 1;
2190                let is_signed = (child.bd_nlt & 0x80) == 0x80;
2191                return Some((bd.min(38), is_signed, child.tnlt));
2192            }
2193        }
2194        if self.enabled {
2195            let bd = (self.bd_nlt & 0x7F) + 1;
2196            let is_signed = (self.bd_nlt & 0x80) == 0x80;
2197            return Some((bd.min(38), is_signed, self.tnlt));
2198        }
2199        None
2200    }
2201
2202    /// Returns `true` if any component has an NLT configured.
2203    pub fn is_any_enabled(&self) -> bool {
2204        if self.enabled {
2205            return true;
2206        }
2207        self.children.iter().any(|c| c.enabled)
2208    }
2209
2210    fn get_or_add_child(&mut self, comp_num: u32) -> &mut ParamNlt {
2211        for i in 0..self.children.len() {
2212            if self.children[i].cnlt == comp_num as u16 {
2213                return &mut self.children[i];
2214            }
2215        }
2216        let child = ParamNlt {
2217            cnlt: comp_num as u16,
2218            ..Default::default()
2219        };
2220        self.children.push(child);
2221        self.children.last_mut().unwrap()
2222    }
2223
2224    pub fn write(&self, file: &mut dyn OutfileBase) -> Result<bool> {
2225        if !self.is_any_enabled() {
2226            return Ok(true);
2227        }
2228        let mut ok = true;
2229        if self.enabled {
2230            ok &= write_u16_be(file, markers::NLT)?;
2231            ok &= write_u16_be(file, self.lnlt)?;
2232            ok &= write_u16_be(file, self.cnlt)?;
2233            ok &= write_u8(file, self.bd_nlt)?;
2234            ok &= write_u8(file, self.tnlt)?;
2235        }
2236        for child in &self.children {
2237            if child.enabled {
2238                ok &= write_u16_be(file, markers::NLT)?;
2239                ok &= write_u16_be(file, child.lnlt)?;
2240                ok &= write_u16_be(file, child.cnlt)?;
2241                ok &= write_u8(file, child.bd_nlt)?;
2242                ok &= write_u8(file, child.tnlt)?;
2243            }
2244        }
2245        Ok(ok)
2246    }
2247
2248    pub fn read(&mut self, file: &mut dyn InfileBase) -> Result<()> {
2249        let mut buf = [0u8; 6];
2250        let mut offset = 0;
2251        while offset < 6 {
2252            let n = file.read(&mut buf[offset..])?;
2253            if n == 0 {
2254                return Err(OjphError::Codec {
2255                    code: 0x00050141,
2256                    message: "error reading NLT marker".into(),
2257                });
2258            }
2259            offset += n;
2260        }
2261        let length = u16::from_be_bytes([buf[0], buf[1]]);
2262        if length != 6 || (buf[5] != 3 && buf[5] != 0) {
2263            return Err(OjphError::Codec {
2264                code: 0x00050142,
2265                message: format!("Unsupported NLT type {}", buf[5]),
2266            });
2267        }
2268        let comp = u16::from_be_bytes([buf[2], buf[3]]);
2269        let child = self.get_or_add_child(comp as u32);
2270        child.enabled = true;
2271        child.cnlt = comp;
2272        child.bd_nlt = buf[4];
2273        child.tnlt = buf[5];
2274        Ok(())
2275    }
2276}
2277
2278// =========================================================================
2279// param_dfs — Downsampling Factor Styles
2280// =========================================================================
2281
2282#[allow(clippy::enum_variant_names)]
2283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2284#[repr(u8)]
2285pub enum DfsDwtType {
2286    NoDwt = 0,
2287    BidirDwt = 1,
2288    HorzDwt = 2,
2289    VertDwt = 3,
2290}
2291
2292/// DFS marker segment — downsampling factor styles.
2293///
2294/// Defines per-decomposition-level DWT type (bidirectional, horizontal-only,
2295/// vertical-only, or none).
2296#[derive(Debug, Clone, Default)]
2297pub struct ParamDfs {
2298    pub(crate) ldfs: u16,
2299    pub(crate) sdfs: u16,
2300    pub(crate) ids: u8,
2301    pub(crate) ddfs: [u8; 8],
2302    pub(crate) children: Vec<ParamDfs>,
2303}
2304
2305impl ParamDfs {
2306    pub fn exists(&self) -> bool {
2307        self.ldfs != 0
2308    }
2309
2310    #[allow(dead_code)]
2311    pub fn get_dfs(&self, index: i32) -> Option<&ParamDfs> {
2312        if self.sdfs == index as u16 {
2313            return Some(self);
2314        }
2315        self.children
2316            .iter()
2317            .find(|child| child.sdfs == index as u16)
2318    }
2319
2320    pub fn get_dwt_type(&self, decomp_level: u32) -> DfsDwtType {
2321        let dl = decomp_level.min(self.ids as u32);
2322        let d = dl - 1;
2323        let idx = d >> 2;
2324        let bits = d & 0x3;
2325        let val = (self.ddfs[idx as usize] >> (6 - 2 * bits)) & 0x3;
2326        match val {
2327            0 => DfsDwtType::NoDwt,
2328            1 => DfsDwtType::BidirDwt,
2329            2 => DfsDwtType::HorzDwt,
2330            3 => DfsDwtType::VertDwt,
2331            _ => DfsDwtType::BidirDwt,
2332        }
2333    }
2334
2335    pub fn read(&mut self, file: &mut dyn InfileBase) -> Result<bool> {
2336        if self.ldfs != 0 {
2337            let mut child = ParamDfs::default();
2338            let ok = child.read(file)?;
2339            self.children.push(child);
2340            return Ok(ok);
2341        }
2342        self.ldfs = read_u16_be(file).map_err(|_| OjphError::Codec {
2343            code: 0x000500D1,
2344            message: "error reading DFS-Ldfs".into(),
2345        })?;
2346        self.sdfs = read_u16_be(file).map_err(|_| OjphError::Codec {
2347            code: 0x000500D2,
2348            message: "error reading DFS-Sdfs".into(),
2349        })?;
2350        let l_ids = read_u8(file).map_err(|_| OjphError::Codec {
2351            code: 0x000500D4,
2352            message: "error reading DFS-Ids".into(),
2353        })?;
2354        let max_ddfs = (self.ddfs.len() * 4) as u8;
2355        self.ids = l_ids.min(max_ddfs);
2356        for i in (0..self.ids).step_by(4) {
2357            self.ddfs[(i / 4) as usize] = read_u8(file).map_err(|_| OjphError::Codec {
2358                code: 0x000500D6,
2359                message: "error reading DFS-Ddfs".into(),
2360            })?;
2361        }
2362        for _ in (self.ids..l_ids).step_by(4) {
2363            let _ = read_u8(file);
2364        }
2365        Ok(true)
2366    }
2367}
2368
2369// =========================================================================
2370// Comment Exchange
2371// =========================================================================
2372
2373/// COM marker data for exchange between caller and encoder.
2374///
2375/// Holds the comment body and registration value (Rcom):
2376/// - `rcom = 0` — binary data
2377/// - `rcom = 1` — Latin text (ISO 8859-15)
2378#[derive(Debug, Clone, Default)]
2379pub struct CommentExchange {
2380    /// Comment body bytes.
2381    pub data: Vec<u8>,
2382    /// Registration value (0 = binary, 1 = Latin text).
2383    pub rcom: u16,
2384}
2385
2386impl CommentExchange {
2387    /// Sets the comment to a Latin text string (Rcom = 1).
2388    pub fn set_string(&mut self, s: &str) {
2389        self.data = s.as_bytes().to_vec();
2390        self.rcom = 1; // Latin (ISO 8859-15)
2391    }
2392
2393    /// Sets the comment to binary data (Rcom = 0).
2394    pub fn set_data(&mut self, data: &[u8]) {
2395        self.data = data.to_vec();
2396        self.rcom = 0; // binary
2397    }
2398}
2399
2400// =========================================================================
2401// Tests
2402// =========================================================================
2403
2404#[cfg(test)]
2405mod tests {
2406    use super::*;
2407    use crate::file::{MemInfile, MemOutfile};
2408    use crate::types::{Point, Size};
2409
2410    // -----------------------------------------------------------------
2411    // ParamSiz tests
2412    // -----------------------------------------------------------------
2413
2414    #[test]
2415    fn param_siz_set_get() {
2416        let mut siz = ParamSiz::default();
2417        siz.set_image_extent(Point::new(1920, 1080));
2418        siz.set_tile_size(Size::new(512, 512));
2419        siz.set_image_offset(Point::new(10, 20));
2420        siz.set_tile_offset(Point::new(5, 10));
2421
2422        assert_eq!(siz.get_image_extent(), Point::new(1920, 1080));
2423        assert_eq!(siz.get_tile_size(), Size::new(512, 512));
2424        assert_eq!(siz.get_image_offset(), Point::new(10, 20));
2425        assert_eq!(siz.get_tile_offset(), Point::new(5, 10));
2426    }
2427
2428    #[test]
2429    fn param_siz_components() {
2430        let mut siz = ParamSiz::default();
2431        siz.set_num_components(3);
2432        assert_eq!(siz.get_num_components(), 3);
2433
2434        // Component 0: 8-bit unsigned, no downsampling
2435        siz.set_comp_info(0, Point::new(1, 1), 8, false);
2436        // Component 1: 12-bit signed, 2x2 downsampling
2437        siz.set_comp_info(1, Point::new(2, 2), 12, true);
2438        // Component 2: 16-bit unsigned, 1x2 downsampling
2439        siz.set_comp_info(2, Point::new(1, 2), 16, false);
2440
2441        assert_eq!(siz.get_bit_depth(0), 8);
2442        assert!(!siz.is_signed(0));
2443        assert_eq!(siz.get_downsampling(0), Point::new(1, 1));
2444
2445        assert_eq!(siz.get_bit_depth(1), 12);
2446        assert!(siz.is_signed(1));
2447        assert_eq!(siz.get_downsampling(1), Point::new(2, 2));
2448
2449        assert_eq!(siz.get_bit_depth(2), 16);
2450        assert!(!siz.is_signed(2));
2451        assert_eq!(siz.get_downsampling(2), Point::new(1, 2));
2452    }
2453
2454    #[test]
2455    fn param_siz_validity_ok() {
2456        let mut siz = ParamSiz::default();
2457        siz.set_image_extent(Point::new(1920, 1080));
2458        siz.set_tile_size(Size::new(512, 512));
2459        siz.set_image_offset(Point::new(0, 0));
2460        siz.set_tile_offset(Point::new(0, 0));
2461        assert!(siz.check_validity().is_ok());
2462    }
2463
2464    #[test]
2465    fn param_siz_validity_zero_extent() {
2466        let mut siz = ParamSiz::default();
2467        siz.set_image_extent(Point::new(0, 0));
2468        siz.set_tile_size(Size::new(512, 512));
2469        assert!(siz.check_validity().is_err());
2470    }
2471
2472    #[test]
2473    fn param_siz_validity_bad_offset() {
2474        let mut siz = ParamSiz::default();
2475        siz.set_image_extent(Point::new(1920, 1080));
2476        siz.set_tile_size(Size::new(512, 512));
2477        // tile offset > image offset should fail
2478        siz.set_image_offset(Point::new(10, 10));
2479        siz.set_tile_offset(Point::new(20, 20));
2480        assert!(siz.check_validity().is_err());
2481    }
2482
2483    #[test]
2484    fn param_siz_write_read_roundtrip() {
2485        let mut siz = ParamSiz::default();
2486        siz.set_image_extent(Point::new(1920, 1080));
2487        siz.set_tile_size(Size::new(512, 512));
2488        siz.set_num_components(3);
2489        for i in 0..3u32 {
2490            siz.set_comp_info(i, Point::new(1, 1), 8, false);
2491        }
2492
2493        let mut out = MemOutfile::new();
2494        siz.write(&mut out).expect("write failed");
2495
2496        // Skip the 2-byte marker code (0xFF51)
2497        let data = out.get_data();
2498        let mut inp = MemInfile::new(&data[2..]);
2499        let mut siz2 = ParamSiz::default();
2500        siz2.read(&mut inp).expect("read failed");
2501
2502        assert_eq!(siz2.xsiz, 1920);
2503        assert_eq!(siz2.ysiz, 1080);
2504        assert_eq!(siz2.xt_siz, 512);
2505        assert_eq!(siz2.yt_siz, 512);
2506        assert_eq!(siz2.csiz, 3);
2507        assert_eq!(siz2.get_bit_depth(0), 8);
2508        assert_eq!(siz2.get_bit_depth(1), 8);
2509        assert_eq!(siz2.get_bit_depth(2), 8);
2510        assert!(!siz2.is_signed(0));
2511        assert_eq!(siz2.get_downsampling(0), Point::new(1, 1));
2512    }
2513
2514    #[test]
2515    fn param_siz_get_width_height() {
2516        let mut siz = ParamSiz::default();
2517        siz.set_image_extent(Point::new(1920, 1080));
2518        siz.set_image_offset(Point::new(100, 50));
2519        siz.set_tile_size(Size::new(1920, 1080));
2520        siz.set_num_components(2);
2521        // Component 0: no downsampling
2522        siz.set_comp_info(0, Point::new(1, 1), 8, false);
2523        // Component 1: 2x2 downsampling
2524        siz.set_comp_info(1, Point::new(2, 2), 8, false);
2525
2526        // width = ceil(1920/1) - ceil(100/1) = 1920 - 100 = 1820
2527        assert_eq!(siz.get_width(0), 1820);
2528        // height = ceil(1080/1) - ceil(50/1) = 1080 - 50 = 1030
2529        assert_eq!(siz.get_height(0), 1030);
2530
2531        // width = ceil(1920/2) - ceil(100/2) = 960 - 50 = 910
2532        assert_eq!(siz.get_width(1), 910);
2533        // height = ceil(1080/2) - ceil(50/2) = 540 - 25 = 515
2534        assert_eq!(siz.get_height(1), 515);
2535    }
2536
2537    // -----------------------------------------------------------------
2538    // ParamCod tests
2539    // -----------------------------------------------------------------
2540
2541    #[test]
2542    fn param_cod_set_get() {
2543        let mut cod = ParamCod::default();
2544        cod.set_reversible(true);
2545        assert!(cod.is_reversible());
2546        assert_eq!(cod.get_wavelet_kern(), DWT_REV53);
2547
2548        cod.set_reversible(false);
2549        assert!(!cod.is_reversible());
2550        assert_eq!(cod.get_wavelet_kern(), DWT_IRV97);
2551
2552        cod.set_num_decomposition(5);
2553        assert_eq!(cod.get_num_decompositions(), 5);
2554
2555        cod.set_color_transform(true);
2556        assert!(cod.is_employing_color_transform());
2557        cod.set_color_transform(false);
2558        assert!(!cod.is_employing_color_transform());
2559    }
2560
2561    #[test]
2562    fn param_cod_progression_order() {
2563        let mut cod = ParamCod::default();
2564        cod.set_progression_order("CPRL")
2565            .expect("valid progression");
2566        assert_eq!(cod.get_progression_order_as_string(), "CPRL");
2567
2568        cod.set_progression_order("lrcp").expect("case-insensitive");
2569        assert_eq!(cod.get_progression_order_as_string(), "LRCP");
2570    }
2571
2572    #[test]
2573    fn param_cod_invalid_progression() {
2574        let mut cod = ParamCod::default();
2575        assert!(cod.set_progression_order("INVALID").is_err());
2576    }
2577
2578    #[test]
2579    fn param_cod_block_dims() {
2580        let mut cod = ParamCod::default();
2581        cod.set_block_dims(32, 32);
2582        assert_eq!(cod.get_block_dims(), Size::new(32, 32));
2583
2584        cod.set_block_dims(64, 64);
2585        assert_eq!(cod.get_block_dims(), Size::new(64, 64));
2586
2587        cod.set_block_dims(32, 64);
2588        assert_eq!(cod.get_block_dims(), Size::new(32, 64));
2589    }
2590
2591    #[test]
2592    fn param_cod_write_read_roundtrip() {
2593        let mut cod = ParamCod::default();
2594        cod.set_reversible(true);
2595        cod.set_num_decomposition(5);
2596        cod.set_block_dims(64, 64);
2597
2598        let mut out = MemOutfile::new();
2599        cod.write(&mut out).expect("write failed");
2600
2601        let data = out.get_data();
2602        let mut inp = MemInfile::new(&data[2..]); // skip marker
2603        let mut cod2 = ParamCod::default();
2604        cod2.read(&mut inp).expect("read failed");
2605
2606        assert_eq!(cod2.get_num_decompositions(), 5);
2607        assert!(cod2.is_reversible());
2608        assert_eq!(cod2.get_block_dims(), Size::new(64, 64));
2609    }
2610
2611    // -----------------------------------------------------------------
2612    // ParamQcd tests
2613    // -----------------------------------------------------------------
2614
2615    #[test]
2616    fn param_qcd_reversible() {
2617        let mut siz = ParamSiz::default();
2618        siz.set_image_extent(Point::new(256, 256));
2619        siz.set_tile_size(Size::new(256, 256));
2620        siz.set_num_components(1);
2621        siz.set_comp_info(0, Point::new(1, 1), 8, false);
2622
2623        let mut cod = ParamCod::default();
2624        cod.set_reversible(true);
2625        cod.set_num_decomposition(5);
2626
2627        let mut qcd = ParamQcd::default();
2628        qcd.check_validity(&siz, &cod)
2629            .expect("check_validity failed");
2630
2631        // After check_validity with reversible, sqcd lower 5 bits should be 0
2632        assert_eq!(qcd.sqcd & 0x1F, 0);
2633        assert!(qcd.get_num_guard_bits() >= 1);
2634        assert_eq!(qcd.num_subbands, 1 + 3 * 5);
2635    }
2636
2637    #[test]
2638    fn param_qcd_reversible_no_dwt_8bit_uses_kmax_8() {
2639        let mut siz = ParamSiz::default();
2640        siz.set_image_extent(Point::new(33, 33));
2641        siz.set_tile_size(Size::new(33, 33));
2642        siz.set_num_components(1);
2643        siz.set_comp_info(0, Point::new(1, 1), 8, false);
2644
2645        let mut cod = ParamCod::default();
2646        cod.set_reversible(true);
2647        cod.set_num_decomposition(0);
2648
2649        let mut qcd = ParamQcd::default();
2650        qcd.check_validity(&siz, &cod)
2651            .expect("check_validity failed");
2652
2653        assert_eq!(qcd.get_kmax(0, 0, 0), 8);
2654        assert_eq!(qcd.get_magb(), 8);
2655    }
2656
2657    #[test]
2658    fn param_qcd_write_read_roundtrip() {
2659        let mut siz = ParamSiz::default();
2660        siz.set_image_extent(Point::new(256, 256));
2661        siz.set_tile_size(Size::new(256, 256));
2662        siz.set_num_components(1);
2663        siz.set_comp_info(0, Point::new(1, 1), 8, false);
2664
2665        let mut cod = ParamCod::default();
2666        cod.set_reversible(true);
2667        cod.set_num_decomposition(5);
2668
2669        let mut qcd = ParamQcd::default();
2670        qcd.check_validity(&siz, &cod)
2671            .expect("check_validity failed");
2672
2673        let mut out = MemOutfile::new();
2674        qcd.write(&mut out).expect("write failed");
2675
2676        let data = out.get_data();
2677        let mut inp = MemInfile::new(&data[2..]); // skip marker
2678        let mut qcd2 = ParamQcd::default();
2679        qcd2.read(&mut inp).expect("read failed");
2680
2681        assert_eq!(qcd2.sqcd, qcd.sqcd);
2682        assert_eq!(qcd2.num_subbands, qcd.num_subbands);
2683        assert_eq!(qcd2.get_num_guard_bits(), qcd.get_num_guard_bits());
2684
2685        // Compare subband data
2686        match (&qcd.sp_qcd, &qcd2.sp_qcd) {
2687            (SpqcdData::Reversible(a), SpqcdData::Reversible(b)) => {
2688                assert_eq!(a, b);
2689            }
2690            _ => panic!("expected reversible quantization data"),
2691        }
2692    }
2693
2694    // -----------------------------------------------------------------
2695    // ProgressionOrder tests
2696    // -----------------------------------------------------------------
2697
2698    #[test]
2699    fn progression_order_from_str() {
2700        assert_eq!(
2701            ProgressionOrder::from_str("LRCP"),
2702            Some(ProgressionOrder::LRCP)
2703        );
2704        assert_eq!(
2705            ProgressionOrder::from_str("RLCP"),
2706            Some(ProgressionOrder::RLCP)
2707        );
2708        assert_eq!(
2709            ProgressionOrder::from_str("RPCL"),
2710            Some(ProgressionOrder::RPCL)
2711        );
2712        assert_eq!(
2713            ProgressionOrder::from_str("PCRL"),
2714            Some(ProgressionOrder::PCRL)
2715        );
2716        assert_eq!(
2717            ProgressionOrder::from_str("CPRL"),
2718            Some(ProgressionOrder::CPRL)
2719        );
2720        // case-insensitive
2721        assert_eq!(
2722            ProgressionOrder::from_str("lrcp"),
2723            Some(ProgressionOrder::LRCP)
2724        );
2725        // invalid
2726        assert_eq!(ProgressionOrder::from_str("INVALID"), None);
2727    }
2728
2729    #[test]
2730    fn progression_order_as_str() {
2731        assert_eq!(ProgressionOrder::LRCP.as_str(), "LRCP");
2732        assert_eq!(ProgressionOrder::RLCP.as_str(), "RLCP");
2733        assert_eq!(ProgressionOrder::RPCL.as_str(), "RPCL");
2734        assert_eq!(ProgressionOrder::PCRL.as_str(), "PCRL");
2735        assert_eq!(ProgressionOrder::CPRL.as_str(), "CPRL");
2736    }
2737
2738    #[test]
2739    fn progression_order_from_i32() {
2740        assert_eq!(ProgressionOrder::from_i32(0), Some(ProgressionOrder::LRCP));
2741        assert_eq!(ProgressionOrder::from_i32(1), Some(ProgressionOrder::RLCP));
2742        assert_eq!(ProgressionOrder::from_i32(2), Some(ProgressionOrder::RPCL));
2743        assert_eq!(ProgressionOrder::from_i32(3), Some(ProgressionOrder::PCRL));
2744        assert_eq!(ProgressionOrder::from_i32(4), Some(ProgressionOrder::CPRL));
2745        assert_eq!(ProgressionOrder::from_i32(5), None);
2746        assert_eq!(ProgressionOrder::from_i32(-1), None);
2747    }
2748}