Skip to main content

signinum_j2k_native/j2c/
recode.rs

1//! Coefficient-domain JPEG 2000 family recode helpers.
2
3use alloc::vec::Vec;
4
5use super::build::{self, Decomposition, SubBand};
6use super::codestream::{ComponentInfo, Header, QuantizationStyle, WaveletTransform};
7use super::decode::{decode_component_tile_bit_planes, DecoderContext, DecompositionStorage};
8use super::progression::progression_iterator;
9use super::segment;
10use super::tile::{self, Tile};
11use crate::error::{bail, DecodingError, Result, TileError};
12use crate::reader::BitReader;
13use crate::{
14    J2kForwardDwt53Level, J2kForwardDwt53Output, PrecomputedHtj2k53Component,
15    PrecomputedHtj2k53Image,
16};
17
18/// Reversible 5/3 source coefficients ready for HTJ2K code-block recoding.
19#[derive(Debug, Clone)]
20pub struct Reversible53CoefficientImage {
21    /// Precomputed wavelet coefficients in the native HTJ2K encoder shape.
22    pub image: PrecomputedHtj2k53Image,
23    /// Source COD multi-component transform flag to preserve in output.
24    pub use_mct: bool,
25    /// Source code-block width exponent minus two.
26    pub code_block_width_exp: u8,
27    /// Source code-block height exponent minus two.
28    pub code_block_height_exp: u8,
29    /// Source quantization guard-bit count.
30    pub guard_bits: u8,
31}
32
33pub(crate) fn extract_reversible_53_coefficients<'a>(
34    data: &'a [u8],
35    header: &Header<'a>,
36    ctx: &mut DecoderContext<'a>,
37) -> Result<Reversible53CoefficientImage> {
38    validate_header_for_reversible_53_recode(header)?;
39
40    let mut reader = BitReader::new(data);
41    let tiles = tile::parse(&mut reader, header)?;
42    if tiles.len() != 1 {
43        bail!(DecodingError::UnsupportedFeature(
44            "coefficient-domain 5/3 recode currently supports single-tile codestreams"
45        ));
46    }
47
48    let tile = &tiles[0];
49    validate_tile_for_reversible_53_recode(tile)?;
50
51    ctx.tile_decode_context.channel_data.clear();
52    ctx.storage.reset();
53
54    build::build(tile, &mut ctx.storage)?;
55    segment::parse(tile, progression_iterator(tile)?, header, &mut ctx.storage)?;
56
57    let mut no_ht_decoder = None;
58    let cpu_decode_parallelism = ctx.cpu_decode_parallelism();
59    decode_component_tile_bit_planes(
60        tile,
61        &mut ctx.tile_decode_context,
62        &mut ctx.storage,
63        header,
64        &mut no_ht_decoder,
65        cpu_decode_parallelism,
66        false,
67    )?;
68
69    let image = precomputed_image_from_storage(header, tile, &ctx.storage)?;
70    let first = tile.component_infos.first().ok_or(TileError::Invalid)?;
71    let params = &first.coding_style.parameters;
72    Ok(Reversible53CoefficientImage {
73        image,
74        use_mct: tile.mct,
75        code_block_width_exp: params.code_block_width.saturating_sub(2),
76        code_block_height_exp: params.code_block_height.saturating_sub(2),
77        guard_bits: first.quantization_info.guard_bits,
78    })
79}
80
81fn validate_header_for_reversible_53_recode(header: &Header<'_>) -> Result<()> {
82    if header.skipped_resolution_levels != 0 {
83        bail!(DecodingError::UnsupportedFeature(
84            "coefficient-domain 5/3 recode requires full-resolution decode settings"
85        ));
86    }
87    if header.size_data.num_tiles() != 1 {
88        bail!(DecodingError::UnsupportedFeature(
89            "coefficient-domain 5/3 recode currently supports single-tile codestreams"
90        ));
91    }
92    if header.size_data.image_area_x_offset != 0
93        || header.size_data.image_area_y_offset != 0
94        || header.size_data.tile_x_offset != 0
95        || header.size_data.tile_y_offset != 0
96    {
97        bail!(DecodingError::UnsupportedFeature(
98            "coefficient-domain 5/3 recode currently requires zero image and tile origins"
99        ));
100    }
101    Ok(())
102}
103
104fn validate_tile_for_reversible_53_recode(tile: &Tile<'_>) -> Result<()> {
105    if !matches!(tile.component_infos.len(), 1 | 3) {
106        bail!(DecodingError::UnsupportedFeature(
107            "coefficient-domain 5/3 recode supports only grayscale or RGB codestreams"
108        ));
109    }
110    if tile.mct && tile.component_infos.len() != 3 {
111        bail!(DecodingError::UnsupportedFeature(
112            "reversible color transform requires three components"
113        ));
114    }
115
116    let first = tile.component_infos.first().ok_or(TileError::Invalid)?;
117    let first_params = &first.coding_style.parameters;
118    let first_bit_depth = first.size_info.precision;
119    let first_guard_bits = first.quantization_info.guard_bits;
120
121    for component in &tile.component_infos {
122        validate_component_for_reversible_53_recode(component)?;
123        if component.size_info.precision != first_bit_depth {
124            bail!(DecodingError::UnsupportedFeature(
125                "coefficient-domain 5/3 recode requires equal component bit depths"
126            ));
127        }
128        if component.quantization_info.guard_bits != first_guard_bits {
129            bail!(DecodingError::UnsupportedFeature(
130                "coefficient-domain 5/3 recode requires equal component guard bits"
131            ));
132        }
133        let params = &component.coding_style.parameters;
134        if params.num_decomposition_levels != first_params.num_decomposition_levels
135            || params.code_block_width != first_params.code_block_width
136            || params.code_block_height != first_params.code_block_height
137        {
138            bail!(DecodingError::UnsupportedFeature(
139                "coefficient-domain 5/3 recode requires matching component coding geometry"
140            ));
141        }
142    }
143
144    Ok(())
145}
146
147fn validate_component_for_reversible_53_recode(component: &ComponentInfo) -> Result<()> {
148    if component.wavelet_transform() != WaveletTransform::Reversible53 {
149        bail!(DecodingError::UnsupportedFeature(
150            "coefficient-domain lossless recode currently supports only reversible 5/3 sources"
151        ));
152    }
153    if component.num_decomposition_levels() == 0 {
154        bail!(DecodingError::UnsupportedFeature(
155            "coefficient-domain 5/3 recode requires at least one decomposition level"
156        ));
157    }
158    if component.quantization_info.quantization_style != QuantizationStyle::NoQuantization {
159        bail!(DecodingError::UnsupportedFeature(
160            "coefficient-domain 5/3 recode requires no-quantization QCD/QCC"
161        ));
162    }
163    if component
164        .coding_style
165        .parameters
166        .code_block_style
167        .uses_high_throughput_block_coding()
168    {
169        bail!(DecodingError::UnsupportedFeature(
170            "source already uses HT block coding"
171        ));
172    }
173    Ok(())
174}
175
176fn precomputed_image_from_storage(
177    header: &Header<'_>,
178    tile: &Tile<'_>,
179    storage: &DecompositionStorage<'_>,
180) -> Result<PrecomputedHtj2k53Image> {
181    let mut components = Vec::with_capacity(tile.component_infos.len());
182    for (component_index, component_info) in tile.component_infos.iter().enumerate() {
183        let tile_decomposition = storage
184            .tile_decompositions
185            .get(component_index)
186            .ok_or(TileError::Invalid)?;
187        components.push(PrecomputedHtj2k53Component {
188            x_rsiz: component_info.size_info.horizontal_resolution,
189            y_rsiz: component_info.size_info.vertical_resolution,
190            dwt: component_dwt_from_storage(tile_decomposition, storage)?,
191        });
192    }
193
194    let first = tile.component_infos.first().ok_or(TileError::Invalid)?;
195    Ok(PrecomputedHtj2k53Image {
196        width: header.size_data.image_width(),
197        height: header.size_data.image_height(),
198        bit_depth: first.size_info.precision,
199        signed: false,
200        components,
201    })
202}
203
204fn component_dwt_from_storage(
205    tile_decomposition: &super::decode::TileDecompositions,
206    storage: &DecompositionStorage<'_>,
207) -> Result<J2kForwardDwt53Output> {
208    let ll = storage
209        .sub_bands
210        .get(tile_decomposition.first_ll_sub_band)
211        .ok_or(TileError::Invalid)?;
212
213    let mut levels = Vec::with_capacity(tile_decomposition.decompositions.len());
214    for idx in tile_decomposition.decompositions.clone() {
215        let decomposition = storage.decompositions.get(idx).ok_or(TileError::Invalid)?;
216        levels.push(level_from_decomposition(decomposition, storage));
217    }
218
219    Ok(J2kForwardDwt53Output {
220        ll: subband_coefficients(ll, storage),
221        ll_width: ll.rect.width(),
222        ll_height: ll.rect.height(),
223        levels,
224    })
225}
226
227fn level_from_decomposition(
228    decomposition: &Decomposition,
229    storage: &DecompositionStorage<'_>,
230) -> J2kForwardDwt53Level {
231    let hl = &storage.sub_bands[decomposition.sub_bands[0]];
232    let lh = &storage.sub_bands[decomposition.sub_bands[1]];
233    let hh = &storage.sub_bands[decomposition.sub_bands[2]];
234    J2kForwardDwt53Level {
235        hl: subband_coefficients(hl, storage),
236        lh: subband_coefficients(lh, storage),
237        hh: subband_coefficients(hh, storage),
238        width: decomposition.rect.width(),
239        height: decomposition.rect.height(),
240        low_width: lh.rect.width(),
241        low_height: hl.rect.height(),
242        high_width: hl.rect.width(),
243        high_height: lh.rect.height(),
244    }
245}
246
247fn subband_coefficients(subband: &SubBand, storage: &DecompositionStorage<'_>) -> Vec<f32> {
248    storage.coefficients[subband.coefficients.clone()].to_vec()
249}