signinum_j2k_native/j2c/
recode.rs1use 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#[derive(Debug, Clone)]
20pub struct Reversible53CoefficientImage {
21 pub image: PrecomputedHtj2k53Image,
23 pub use_mct: bool,
25 pub code_block_width_exp: u8,
27 pub code_block_height_exp: u8,
29 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}