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