1use alloc::vec::Vec;
4
5use super::build::{self, Decomposition, SubBand};
6use super::codestream::{ComponentInfo, Header, QuantizationStyle, WaveletTransform};
7use super::decode::{
8 decode_component_tile_bit_planes_budgeted, DecodeAllocationBudget, DecoderContext,
9 DecompositionStorage,
10};
11use super::encode::allocation::{checked_add_bytes, checked_element_bytes};
12use super::segment;
13use super::tile::{self, Tile};
14use crate::error::{bail, DecodingError, Result, TileError, ValidationError};
15use crate::reader::BitReader;
16use crate::{
17 try_reserve_decode_elements, EncodeOptions, EncodeResult, J2kForwardDwt53Level,
18 J2kForwardDwt53Output, PrecomputedHtj2k53Component, PrecomputedHtj2k53Image,
19};
20
21#[cfg(test)]
22mod tests;
23
24#[derive(Debug)]
26pub struct Reversible53CoefficientImage {
27 pub image: PrecomputedHtj2k53Image,
29 pub use_mct: bool,
31 pub code_block_width_exp: u8,
33 pub code_block_height_exp: u8,
35 pub guard_bits: u8,
37}
38
39impl Reversible53CoefficientImage {
40 #[doc(hidden)]
47 pub fn encode_htj2k(&self, options: &EncodeOptions) -> EncodeResult<Vec<u8>> {
48 let retained_bytes = self.checked_retained_capacity_bytes()?;
49 super::encode::encode_precomputed_htj2k_53_with_mct_and_retained_owner(
50 &self.image,
51 options,
52 self.use_mct,
53 self,
54 retained_bytes,
55 )
56 }
57
58 fn checked_retained_capacity_bytes(&self) -> EncodeResult<usize> {
60 let mut bytes = checked_element_bytes::<PrecomputedHtj2k53Component>(
61 self.image.components.capacity(),
62 "reversible coefficient component capacity",
63 )?;
64 for component in &self.image.components {
65 bytes = checked_add_bytes(
66 bytes,
67 checked_element_bytes::<f32>(
68 component.dwt.ll.capacity(),
69 "reversible coefficient LL capacity",
70 )?,
71 "reversible coefficient image capacity",
72 )?;
73 bytes = checked_add_bytes(
74 bytes,
75 checked_element_bytes::<J2kForwardDwt53Level>(
76 component.dwt.levels.capacity(),
77 "reversible coefficient level capacity",
78 )?,
79 "reversible coefficient image capacity",
80 )?;
81 for level in &component.dwt.levels {
82 for band in [&level.hl, &level.lh, &level.hh] {
83 bytes = checked_add_bytes(
84 bytes,
85 checked_element_bytes::<f32>(
86 band.capacity(),
87 "reversible coefficient detail-band capacity",
88 )?,
89 "reversible coefficient image capacity",
90 )?;
91 }
92 }
93 }
94 Ok(bytes)
95 }
96}
97
98pub(crate) fn extract_reversible_53_coefficients<'a>(
99 data: &'a [u8],
100 header: &Header<'a>,
101 retained_image_bytes: usize,
102 ctx: &mut DecoderContext<'a>,
103) -> Result<Reversible53CoefficientImage> {
104 ctx.release_reusable_allocations();
105 let result = extract_reversible_53_coefficients_inner(data, header, retained_image_bytes, ctx);
106 ctx.release_reusable_allocations();
107 result
108}
109
110fn extract_reversible_53_coefficients_inner<'a>(
111 data: &'a [u8],
112 header: &Header<'a>,
113 retained_image_bytes: usize,
114 ctx: &mut DecoderContext<'a>,
115) -> Result<Reversible53CoefficientImage> {
116 validate_header_for_reversible_53_recode(header)?;
117
118 let mut reader = BitReader::new(data);
119 let tiles = tile::parse(&mut reader, header, retained_image_bytes)?;
120 if tiles.len() != 1 {
121 bail!(DecodingError::UnsupportedFeature(
122 "coefficient-domain 5/3 recode currently supports single-tile codestreams"
123 ));
124 }
125
126 let tile = &tiles[0];
127 validate_tile_for_reversible_53_recode(tile)?;
128
129 ctx.tile_decode_context.channel_data.clear();
130 ctx.storage.reset_for_next_tile();
131
132 build::build(
133 tile,
134 &mut ctx.storage,
135 tiles.structural_workspace_bytes(),
136 false,
137 build::BuildWorkspace::CoefficientsOnly,
138 None,
139 )?;
140 segment::parse(tile, header, &mut ctx.storage)?;
141
142 let mut no_ht_decoder = None;
143 let cpu_decode_parallelism = ctx.cpu_decode_parallelism();
144 decode_component_tile_bit_planes_budgeted(
145 tile,
146 0..tile.component_infos.len(),
147 &mut ctx.tile_decode_context,
148 &mut ctx.storage,
149 header,
150 &mut no_ht_decoder,
151 cpu_decode_parallelism,
152 false,
153 )?;
154
155 let output_plan = RecodeOutputPlan::for_storage(tile, &ctx.storage)?;
156 let mut budget = DecodeAllocationBudget::for_storage(&ctx.storage)?;
157 output_plan.include_in(&mut budget)?;
158 let image =
159 precomputed_image_from_storage(header, tile, &ctx.storage, &output_plan, &mut budget)?;
160 let first = tile.component_infos.first().ok_or(TileError::Invalid)?;
161 let params = &first.coding_style.parameters;
162 Ok(Reversible53CoefficientImage {
163 image,
164 use_mct: tile.mct,
165 code_block_width_exp: params.code_block_width.saturating_sub(2),
166 code_block_height_exp: params.code_block_height.saturating_sub(2),
167 guard_bits: first.quantization_info.guard_bits,
168 })
169}
170
171struct RecodeOutputPlan {
172 components: usize,
173 levels: usize,
174 coefficients: usize,
175}
176
177impl RecodeOutputPlan {
178 fn for_storage(tile: &Tile<'_>, storage: &DecompositionStorage<'_>) -> Result<Self> {
179 let level_count =
180 storage
181 .tile_decompositions
182 .iter()
183 .try_fold(0usize, |count, decomposition| {
184 count
185 .checked_add(decomposition.decompositions.len())
186 .ok_or(ValidationError::ImageTooLarge)
187 })?;
188 Ok(Self {
189 components: tile.component_infos.len(),
190 levels: level_count,
191 coefficients: storage.coefficients.len(),
192 })
193 }
194
195 fn include_in(&self, budget: &mut DecodeAllocationBudget) -> Result<()> {
196 budget.include_elements::<PrecomputedHtj2k53Component>(self.components)?;
197 budget.include_elements::<J2kForwardDwt53Level>(self.levels)?;
198 budget.include_elements::<f32>(self.coefficients)
199 }
200}
201
202fn validate_header_for_reversible_53_recode(header: &Header<'_>) -> Result<()> {
203 if header.skipped_resolution_levels != 0 {
204 bail!(DecodingError::UnsupportedFeature(
205 "coefficient-domain 5/3 recode requires full-resolution decode settings"
206 ));
207 }
208 if header.size_data.num_tiles() != 1 {
209 bail!(DecodingError::UnsupportedFeature(
210 "coefficient-domain 5/3 recode currently supports single-tile codestreams"
211 ));
212 }
213 if header.size_data.image_area_x_offset != 0
214 || header.size_data.image_area_y_offset != 0
215 || header.size_data.tile_x_offset != 0
216 || header.size_data.tile_y_offset != 0
217 {
218 bail!(DecodingError::UnsupportedFeature(
219 "coefficient-domain 5/3 recode currently requires zero image and tile origins"
220 ));
221 }
222 Ok(())
223}
224
225fn validate_tile_for_reversible_53_recode(tile: &Tile<'_>) -> Result<()> {
226 if !matches!(tile.component_infos.len(), 1 | 3) {
227 bail!(DecodingError::UnsupportedFeature(
228 "coefficient-domain 5/3 recode supports only grayscale or RGB codestreams"
229 ));
230 }
231 if tile.mct && tile.component_infos.len() != 3 {
232 bail!(DecodingError::UnsupportedFeature(
233 "reversible color transform requires three components"
234 ));
235 }
236
237 let first = tile.component_infos.first().ok_or(TileError::Invalid)?;
238 let first_params = &first.coding_style.parameters;
239 let first_bit_depth = first.size_info.precision;
240 let first_guard_bits = first.quantization_info.guard_bits;
241
242 for component in &tile.component_infos {
243 validate_component_for_reversible_53_recode(component)?;
244 if component.size_info.precision != first_bit_depth {
245 bail!(DecodingError::UnsupportedFeature(
246 "coefficient-domain 5/3 recode requires equal component bit depths"
247 ));
248 }
249 if component.quantization_info.guard_bits != first_guard_bits {
250 bail!(DecodingError::UnsupportedFeature(
251 "coefficient-domain 5/3 recode requires equal component guard bits"
252 ));
253 }
254 let params = &component.coding_style.parameters;
255 if params.num_decomposition_levels != first_params.num_decomposition_levels
256 || params.code_block_width != first_params.code_block_width
257 || params.code_block_height != first_params.code_block_height
258 {
259 bail!(DecodingError::UnsupportedFeature(
260 "coefficient-domain 5/3 recode requires matching component coding geometry"
261 ));
262 }
263 }
264
265 Ok(())
266}
267
268fn validate_component_for_reversible_53_recode(component: &ComponentInfo) -> Result<()> {
269 if component.wavelet_transform() != WaveletTransform::Reversible53 {
270 bail!(DecodingError::UnsupportedFeature(
271 "coefficient-domain lossless recode currently supports only reversible 5/3 sources"
272 ));
273 }
274 if component.num_decomposition_levels() == 0 {
275 bail!(DecodingError::UnsupportedFeature(
276 "coefficient-domain 5/3 recode requires at least one decomposition level"
277 ));
278 }
279 if component.quantization_info.quantization_style != QuantizationStyle::NoQuantization {
280 bail!(DecodingError::UnsupportedFeature(
281 "coefficient-domain 5/3 recode requires no-quantization QCD/QCC"
282 ));
283 }
284 if component
285 .coding_style
286 .parameters
287 .code_block_style
288 .uses_high_throughput_block_coding()
289 {
290 bail!(DecodingError::UnsupportedFeature(
291 "source already uses HT block coding"
292 ));
293 }
294 Ok(())
295}
296
297fn precomputed_image_from_storage(
298 header: &Header<'_>,
299 tile: &Tile<'_>,
300 storage: &DecompositionStorage<'_>,
301 output_plan: &RecodeOutputPlan,
302 budget: &mut DecodeAllocationBudget,
303) -> Result<PrecomputedHtj2k53Image> {
304 let mut components = Vec::new();
305 try_reserve_decode_elements(&mut components, output_plan.components)?;
306 budget.include_capacity_overage::<PrecomputedHtj2k53Component>(
307 output_plan.components,
308 components.capacity(),
309 )?;
310 for (component_index, component_info) in tile.component_infos.iter().enumerate() {
311 let tile_decomposition = storage
312 .tile_decompositions
313 .get(component_index)
314 .ok_or(TileError::Invalid)?;
315 components.push(PrecomputedHtj2k53Component {
316 x_rsiz: component_info.size_info.horizontal_resolution,
317 y_rsiz: component_info.size_info.vertical_resolution,
318 dwt: component_dwt_from_storage(tile_decomposition, storage, budget)?,
319 });
320 }
321
322 let first = tile.component_infos.first().ok_or(TileError::Invalid)?;
323 Ok(PrecomputedHtj2k53Image {
324 width: header.size_data.image_width(),
325 height: header.size_data.image_height(),
326 bit_depth: first.size_info.precision,
327 signed: false,
328 components,
329 })
330}
331
332fn component_dwt_from_storage(
333 tile_decomposition: &super::decode::TileDecompositions,
334 storage: &DecompositionStorage<'_>,
335 budget: &mut DecodeAllocationBudget,
336) -> Result<J2kForwardDwt53Output> {
337 let ll = storage
338 .sub_bands
339 .get(tile_decomposition.first_ll_sub_band)
340 .ok_or(TileError::Invalid)?;
341
342 let level_count = tile_decomposition.decompositions.len();
343 let mut levels = Vec::new();
344 try_reserve_decode_elements(&mut levels, level_count)?;
345 budget.include_capacity_overage::<J2kForwardDwt53Level>(level_count, levels.capacity())?;
346 for idx in tile_decomposition.decompositions.clone() {
347 let decomposition = storage.decompositions.get(idx).ok_or(TileError::Invalid)?;
348 levels.push(level_from_decomposition(decomposition, storage, budget)?);
349 }
350
351 Ok(J2kForwardDwt53Output {
352 ll: subband_coefficients(ll, storage, budget)?,
353 ll_width: ll.rect.width(),
354 ll_height: ll.rect.height(),
355 levels,
356 })
357}
358
359fn level_from_decomposition(
360 decomposition: &Decomposition,
361 storage: &DecompositionStorage<'_>,
362 budget: &mut DecodeAllocationBudget,
363) -> Result<J2kForwardDwt53Level> {
364 let hl = storage
365 .sub_bands
366 .get(decomposition.sub_bands[0])
367 .ok_or(TileError::Invalid)?;
368 let lh = storage
369 .sub_bands
370 .get(decomposition.sub_bands[1])
371 .ok_or(TileError::Invalid)?;
372 let hh = storage
373 .sub_bands
374 .get(decomposition.sub_bands[2])
375 .ok_or(TileError::Invalid)?;
376 Ok(J2kForwardDwt53Level {
377 hl: subband_coefficients(hl, storage, budget)?,
378 lh: subband_coefficients(lh, storage, budget)?,
379 hh: subband_coefficients(hh, storage, budget)?,
380 width: decomposition.rect.width(),
381 height: decomposition.rect.height(),
382 low_width: lh.rect.width(),
383 low_height: hl.rect.height(),
384 high_width: hl.rect.width(),
385 high_height: lh.rect.height(),
386 })
387}
388
389fn subband_coefficients(
390 subband: &SubBand,
391 storage: &DecompositionStorage<'_>,
392 budget: &mut DecodeAllocationBudget,
393) -> Result<Vec<f32>> {
394 let coefficients = storage
395 .coefficients
396 .get(subband.coefficients.clone())
397 .ok_or(TileError::Invalid)?;
398 let mut copied = Vec::new();
399 try_reserve_decode_elements(&mut copied, coefficients.len())?;
400 budget.include_capacity_overage::<f32>(coefficients.len(), copied.capacity())?;
401 copied.extend_from_slice(coefficients);
402 Ok(copied)
403}