Skip to main content

j2k_native/image/
output_api.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Native, coefficient, and borrowed-component decode output surfaces.
4
5use alloc::vec::Vec;
6
7use crate::color::{ComponentPlane, DecodedComponents, DecodedNativeComponents, RawBitmap};
8use crate::error::{bail, DecodingError, Result, ValidationError};
9use crate::j2c::{self, ComponentData, DecoderContext, Reversible53CoefficientImage};
10use crate::jp2::DecodedImage;
11use crate::CpuDecodeParallelism;
12use crate::{
13    checked_decode_byte_len3, checked_decode_byte_len4, checked_decode_sample_count,
14    native_bytes_per_sample, try_reserve_decode_elements, validate_roi, HtCodeBlockDecoder,
15};
16
17use super::native::{try_clone_color_space, NativeOutputBudget};
18use super::Image;
19
20impl<'a> Image<'a> {
21    /// Extract selected native wavelet bands without inverse DWT or color conversion.
22    ///
23    /// See [`crate::WaveletSelection`] for selection and coefficient semantics.
24    ///
25    /// # Errors
26    /// Returns an error for invalid selectors or unsupported coding contracts.
27    pub fn decode_wavelets_with_context(
28        &self,
29        context: &mut DecoderContext<'a>,
30        selection: &crate::WaveletSelection,
31    ) -> Result<crate::WaveletImage> {
32        j2c::wavelets::extract(
33            self.codestream,
34            &self.header,
35            self.retained_metadata_bytes()?,
36            context,
37            selection,
38        )
39    }
40
41    /// Decode the image at native bit depth without scaling to 8-bit.
42    ///
43    /// For images with bit depth ≤ 8, returns pixel data as `Vec<u8>`.
44    /// For images with bit depth > 8 (e.g., 12-bit or 16-bit), returns
45    /// pixel data as little-endian `u16` values packed into `Vec<u8>`.
46    ///
47    /// This is essential for medical imaging (DICOM) where 12-bit and 16-bit
48    /// images must preserve their full dynamic range.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error when decoding or native sample packing fails.
53    pub fn decode_native(&self) -> Result<RawBitmap> {
54        let mut decoder_context = DecoderContext::default();
55        self.decode_native_with_context(&mut decoder_context)
56    }
57
58    /// Decode at native bit depth while accounting an already-live external
59    /// allocation, such as the encoded `Vec` being round-trip validated.
60    ///
61    /// `retained_capacity` must be the allocator capacity of that external
62    /// owner, not merely its logical length.
63    ///
64    /// # Errors
65    ///
66    /// Returns an error when aggregate retained allocation accounting,
67    /// decoding, sizing, or native sample packing fails.
68    #[doc(hidden)]
69    pub fn decode_native_with_retained_capacity(
70        &self,
71        retained_capacity: usize,
72    ) -> Result<RawBitmap> {
73        let retained_baseline_bytes = super::allocation::combine_retained_bytes(
74            retained_capacity,
75            self.retained_metadata_bytes()?,
76        )?;
77        let mut decoder_context = DecoderContext::default();
78        self.decode_native_with_context_and_retained_baseline(
79            &mut decoder_context,
80            retained_baseline_bytes,
81        )
82    }
83
84    /// Extract reversible 5/3 wavelet coefficients for coefficient-domain
85    /// classic JPEG 2000 to HTJ2K recoding.
86    ///
87    /// This decodes classic Tier-1 code-blocks into dequantized reversible
88    /// wavelet coefficients, but does not run inverse DWT or color conversion.
89    #[doc(hidden)]
90    pub fn decode_reversible_53_coefficients(&self) -> Result<Reversible53CoefficientImage> {
91        let mut decoder_context = DecoderContext::default();
92        self.decode_reversible_53_coefficients_with_context(&mut decoder_context)
93    }
94
95    /// Extract reversible 5/3 wavelet coefficients using a caller-provided
96    /// decoder context.
97    #[doc(hidden)]
98    pub fn decode_reversible_53_coefficients_with_context(
99        &self,
100        decoder_context: &mut DecoderContext<'a>,
101    ) -> Result<Reversible53CoefficientImage> {
102        j2c::recode::extract_reversible_53_coefficients(
103            self.codestream,
104            &self.header,
105            self.retained_metadata_bytes()?,
106            decoder_context,
107        )
108    }
109
110    /// Decode a region of the image at native bit depth.
111    ///
112    /// # Errors
113    ///
114    /// Returns an error when the region is invalid or decoding and packing fail.
115    pub fn decode_native_region(&self, roi: (u32, u32, u32, u32)) -> Result<RawBitmap> {
116        self.decode_native_region_with_context(roi, &mut DecoderContext::default())
117    }
118
119    /// Decode a source-coordinate region into owned native-bit-depth component
120    /// planes.
121    ///
122    /// # Errors
123    ///
124    /// Returns an error when the region is invalid or decoding and packing fail.
125    pub fn decode_native_region_components(
126        &self,
127        roi: (u32, u32, u32, u32),
128    ) -> Result<DecodedNativeComponents> {
129        self.decode_native_region_components_with_context(roi, &mut DecoderContext::default())
130    }
131
132    /// Decode the image at native bit depth using a caller-provided decoder
133    /// context so allocations can be reused across repeated decodes.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error when decoding, sizing, or native sample packing fails.
138    pub fn decode_native_with_context(
139        &self,
140        decoder_context: &mut DecoderContext<'a>,
141    ) -> Result<RawBitmap> {
142        let retained_baseline_bytes = self.retained_metadata_bytes()?;
143        self.decode_native_with_context_and_retained_baseline(
144            decoder_context,
145            retained_baseline_bytes,
146        )
147    }
148
149    pub(crate) fn decode_native_with_context_and_retained_baseline(
150        &self,
151        decoder_context: &mut DecoderContext<'a>,
152        retained_baseline_bytes: usize,
153    ) -> Result<RawBitmap> {
154        let bit_depth = self.uniform_header_bit_depth()?;
155        let mut ht_decoder = None;
156        let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
157        decoder_context.set_output_region(None);
158        decoder_context.set_round_irreversible_output(true);
159        let decode_result = j2c::decode(
160            self.codestream,
161            &self.header,
162            retained_baseline_bytes,
163            decoder_context,
164            &mut ht_decoder,
165        );
166        decoder_context.set_output_region(None);
167        decoder_context.set_round_irreversible_output(false);
168        decode_result?;
169
170        let components = &decoder_context.tile_decode_context.channel_data;
171        let component_owner_capacity = components.capacity();
172        let num_components =
173            u16::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
174        let width = self.width();
175        let height = self.height();
176        let pixel_count = checked_decode_sample_count(width, height)?;
177        let bytes_per_sample = native_bytes_per_sample(bit_depth)?;
178        let capacity =
179            checked_decode_byte_len3(pixel_count, usize::from(num_components), bytes_per_sample)?;
180        let mut budget = NativeOutputBudget::for_decoded_channels(
181            retained_baseline_bytes,
182            components,
183            component_owner_capacity,
184        )?;
185        budget.include_bit_capacity(components.len())?;
186        budget.include_elements::<u8>(capacity)?;
187        let component_signed = Self::try_component_signedness(components)?;
188        budget.include_bit_capacity_overage(components.len(), component_signed.capacity())?;
189        let signed = component_signed.iter().all(|signed| *signed);
190        let mut data = Vec::new();
191        try_reserve_decode_elements(&mut data, capacity)?;
192        budget.include_capacity_overage::<u8>(capacity, data.capacity())?;
193        if let [component] = components.as_slice() {
194            Self::extend_component_native_sample_bytes(
195                &mut data,
196                component,
197                0..pixel_count,
198                bit_depth,
199                parallel,
200            );
201        } else {
202            for index in 0..pixel_count {
203                for component in components {
204                    Self::push_component_native_sample_bytes(
205                        &mut data, component, index, bit_depth,
206                    );
207                }
208            }
209        }
210        if data.len() != capacity {
211            bail!(DecodingError::CodeBlockDecodeFailure);
212        }
213        let bitmap = RawBitmap {
214            data,
215            width,
216            height,
217            bit_depth,
218            signed,
219            component_signed,
220            num_components,
221            bytes_per_sample: u8::try_from(bytes_per_sample)
222                .map_err(|_| ValidationError::ImageTooLarge)?,
223        };
224        NativeOutputBudget::validate_raw_pack(
225            retained_baseline_bytes,
226            components,
227            component_owner_capacity,
228            &bitmap,
229        )?;
230        Ok(bitmap)
231    }
232
233    /// Decode a region of the image at native bit depth using a caller-provided
234    /// decoder context.
235    ///
236    /// # Errors
237    ///
238    /// Returns an error when the region is invalid or decoding, sizing, and packing fail.
239    pub fn decode_native_region_with_context(
240        &self,
241        roi: (u32, u32, u32, u32),
242        decoder_context: &mut DecoderContext<'a>,
243    ) -> Result<RawBitmap> {
244        validate_roi((self.width(), self.height()), roi)?;
245        if self.requires_exact_integer_decode() {
246            return self.decode_native_region_via_full_decode(roi, decoder_context);
247        }
248        let bit_depth = self.uniform_header_bit_depth()?;
249        let retained_image_bytes = self.retained_metadata_bytes()?;
250        let mut ht_decoder = None;
251        let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
252        decoder_context.set_output_region(Some(roi));
253        decoder_context.set_round_irreversible_output(true);
254        decoder_context.permit_line_region_idwt(self.permits_line_region_idwt());
255        let decode_result = j2c::decode(
256            self.codestream,
257            &self.header,
258            retained_image_bytes,
259            decoder_context,
260            &mut ht_decoder,
261        );
262        decoder_context.set_output_region(None);
263        decoder_context.set_round_irreversible_output(false);
264        decoder_context.permit_line_region_idwt(false);
265        decode_result?;
266
267        let components = &decoder_context.tile_decode_context.channel_data;
268        let component_owner_capacity = components.capacity();
269        let num_components =
270            u16::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
271        let bytes_per_sample = native_bytes_per_sample(bit_depth)?;
272        let (_x, _y, width, height) = roi;
273        let capacity = checked_decode_byte_len4(
274            width as usize,
275            height as usize,
276            usize::from(num_components),
277            bytes_per_sample,
278        )?;
279        let mut budget = NativeOutputBudget::for_decoded_channels(
280            retained_image_bytes,
281            components,
282            component_owner_capacity,
283        )?;
284        budget.include_bit_capacity(components.len())?;
285        budget.include_elements::<u8>(capacity)?;
286        let mut data = Vec::new();
287        let component_signed = Self::try_component_signedness(components)?;
288        budget.include_bit_capacity_overage(components.len(), component_signed.capacity())?;
289        try_reserve_decode_elements(&mut data, capacity)?;
290        budget.include_capacity_overage::<u8>(capacity, data.capacity())?;
291        let signed = component_signed.iter().all(|signed| *signed);
292
293        let pixel_count = width as usize * height as usize;
294        if let [component] = components.as_slice() {
295            Self::extend_component_native_sample_bytes(
296                &mut data,
297                component,
298                0..pixel_count,
299                bit_depth,
300                parallel,
301            );
302        } else {
303            for index in 0..pixel_count {
304                for component in components {
305                    Self::push_component_native_sample_bytes(
306                        &mut data, component, index, bit_depth,
307                    );
308                }
309            }
310        }
311        if data.len() != capacity {
312            bail!(DecodingError::CodeBlockDecodeFailure);
313        }
314
315        let bitmap = RawBitmap {
316            data,
317            width,
318            height,
319            bit_depth,
320            signed,
321            component_signed,
322            num_components,
323            bytes_per_sample: u8::try_from(bytes_per_sample)
324                .map_err(|_| ValidationError::ImageTooLarge)?,
325        };
326        NativeOutputBudget::validate_raw_pack(
327            retained_image_bytes,
328            components,
329            component_owner_capacity,
330            &bitmap,
331        )?;
332        Ok(bitmap)
333    }
334
335    fn try_component_signedness(components: &[ComponentData]) -> Result<Vec<bool>> {
336        let mut signedness = Vec::new();
337        try_reserve_decode_elements(&mut signedness, components.len())?;
338        signedness.extend(components.iter().map(|component| component.signed));
339        Ok(signedness)
340    }
341
342    pub(super) fn component_plane_sampling_at(&self, component_idx: usize) -> (u8, u8) {
343        if self.settings.resolve_palette_indices && self.boxes.palette.is_some() {
344            return (1, 1);
345        }
346        self.header
347            .component_infos
348            .get(component_idx)
349            .map_or((1, 1), |component| {
350                (
351                    component.size_info.horizontal_resolution,
352                    component.size_info.vertical_resolution,
353                )
354            })
355    }
356
357    pub(super) fn try_borrow_component_planes<'ctx>(
358        &self,
359        components: &'ctx [ComponentData],
360        component_owner_capacity: usize,
361        dimensions: (u32, u32),
362    ) -> Result<DecodedComponents<'ctx>> {
363        let retained_image_bytes = self.retained_metadata_bytes()?;
364        self.try_borrow_component_planes_with_retained_baseline(
365            components,
366            component_owner_capacity,
367            dimensions,
368            retained_image_bytes,
369        )
370    }
371
372    pub(super) fn try_borrow_component_planes_with_retained_baseline<'ctx>(
373        &self,
374        components: &'ctx [ComponentData],
375        component_owner_capacity: usize,
376        dimensions: (u32, u32),
377        retained_image_bytes: usize,
378    ) -> Result<DecodedComponents<'ctx>> {
379        let mut budget = NativeOutputBudget::for_decoded_channels(
380            retained_image_bytes,
381            components,
382            component_owner_capacity,
383        )?;
384        budget.include_elements::<ComponentPlane<'_>>(components.len())?;
385        budget.include_color_space_clone(&self.color_space)?;
386        let color_space = try_clone_color_space(&self.color_space)?;
387        budget.include_color_space_clone_overage(&self.color_space, &color_space)?;
388        let mut planes = Vec::new();
389        try_reserve_decode_elements(&mut planes, components.len())?;
390        budget
391            .include_capacity_overage::<ComponentPlane<'_>>(components.len(), planes.capacity())?;
392        for (component_idx, component) in components.iter().enumerate() {
393            planes.push(ComponentPlane {
394                samples: component.container.truncated(),
395                dimensions,
396                bit_depth: component.bit_depth,
397                signed: component.signed,
398                sampling: self.component_plane_sampling_at(component_idx),
399            });
400        }
401
402        let mut packed = DecodedComponents {
403            dimensions,
404            color_space,
405            has_alpha: self.has_alpha,
406            planes,
407            live_bytes: 0,
408        };
409        packed.live_bytes = NativeOutputBudget::validate_borrowed_pack(
410            retained_image_bytes,
411            components,
412            component_owner_capacity,
413            &packed,
414        )?;
415        Ok(packed)
416    }
417
418    fn uniform_header_bit_depth(&self) -> Result<u8> {
419        let Some(first) = self.header.component_infos.first() else {
420            bail!(DecodingError::CodeBlockDecodeFailure);
421        };
422        if self
423            .header
424            .component_infos
425            .iter()
426            .any(|component| component.size_info.precision != first.size_info.precision)
427        {
428            bail!(DecodingError::UnsupportedFeature(
429                "decode_native requires uniform component bit depths; use decode_components for mixed-depth images"
430            ));
431        }
432        if first.size_info.precision > 38 {
433            bail!(DecodingError::UnsupportedFeature(
434                "decode_native supports JPEG 2000 Part 1 component precision up to 38 bits"
435            ));
436        }
437        Ok(first.size_info.precision)
438    }
439
440    pub(super) fn validate_component_plane_precision(&self) -> Result<()> {
441        if self
442            .header
443            .component_infos
444            .iter()
445            .any(|component| component.size_info.precision > 24)
446        {
447            bail!(DecodingError::UnsupportedFeature(
448                "decode_components currently supports component planes up to 24 bits per component"
449            ));
450        }
451        Ok(())
452    }
453}
454
455/// Borrowed component planes rounded for integer output.
456///
457/// The plain component-plane methods return unrounded irreversible (9/7)
458/// samples with the unsigned level shift already added. Rounding those planes
459/// afterwards differs from [`Image::decode`] on exact ties, because adding the
460/// shift costs one bit of f32 precision. These methods round each centered
461/// sample ties-to-even before the shift, as the interleaved decode paths do, so
462/// converting their planes to integer samples reproduces that output.
463/// Reversible (5/3) samples are unchanged.
464impl<'a> Image<'a> {
465    /// Rounded-for-integer-output counterpart of
466    /// [`Self::decode_components_with_context`].
467    ///
468    /// # Errors
469    ///
470    /// Returns an error when component precision is unsupported or decoding fails.
471    #[doc(hidden)]
472    pub fn decode_components_for_integer_output_with_context<'ctx>(
473        &self,
474        decoder_context: &'ctx mut DecoderContext<'a>,
475    ) -> Result<DecodedComponents<'ctx>> {
476        self.decode_borrowed_component_planes(decoder_context, None, None, true)
477    }
478
479    /// Rounded-for-integer-output counterpart of
480    /// [`Self::decode_region_components_with_context`].
481    ///
482    /// # Errors
483    ///
484    /// Returns an error when the region is invalid, precision is unsupported, or decoding fails.
485    #[doc(hidden)]
486    pub fn decode_region_components_for_integer_output_with_context<'ctx>(
487        &self,
488        roi: (u32, u32, u32, u32),
489        decoder_context: &'ctx mut DecoderContext<'a>,
490    ) -> Result<DecodedComponents<'ctx>> {
491        self.decode_borrowed_component_planes(decoder_context, Some(roi), None, true)
492    }
493
494    /// Rounded-for-integer-output counterpart of
495    /// [`Self::decode_components_with_ht_decoder`].
496    ///
497    /// # Errors
498    ///
499    /// Returns an error when component precision is unsupported or decoding fails.
500    #[doc(hidden)]
501    pub fn decode_components_for_integer_output_with_ht_decoder<'ctx>(
502        &self,
503        decoder_context: &'ctx mut DecoderContext<'a>,
504        ht_decoder: &mut dyn HtCodeBlockDecoder,
505    ) -> Result<DecodedComponents<'ctx>> {
506        self.decode_borrowed_component_planes(decoder_context, None, Some(ht_decoder), true)
507    }
508
509    /// Rounded-for-integer-output counterpart of
510    /// [`Self::decode_region_components_with_ht_decoder`].
511    ///
512    /// # Errors
513    ///
514    /// Returns an error when the region is invalid, precision is unsupported, or decoding fails.
515    #[doc(hidden)]
516    pub fn decode_region_components_for_integer_output_with_ht_decoder<'ctx>(
517        &self,
518        decoder_context: &'ctx mut DecoderContext<'a>,
519        roi: (u32, u32, u32, u32),
520        ht_decoder: &mut dyn HtCodeBlockDecoder,
521    ) -> Result<DecodedComponents<'ctx>> {
522        self.decode_borrowed_component_planes(decoder_context, Some(roi), Some(ht_decoder), true)
523    }
524
525    pub(super) fn decode_borrowed_component_planes<'ctx>(
526        &self,
527        decoder_context: &'ctx mut DecoderContext<'a>,
528        roi: Option<(u32, u32, u32, u32)>,
529        ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
530        round_irreversible_output: bool,
531    ) -> Result<DecodedComponents<'ctx>> {
532        let dimensions = match roi {
533            Some(roi) => {
534                validate_roi((self.width(), self.height()), roi)?;
535                (roi.2, roi.3)
536            }
537            None => (self.width(), self.height()),
538        };
539        self.validate_component_plane_precision()?;
540        let decoded_image = self.decode_image(
541            decoder_context,
542            roi,
543            ht_decoder,
544            round_irreversible_output,
545            self.retained_metadata_bytes()?,
546        )?;
547        let DecodedImage {
548            decoded_components,
549            boxes: _,
550        } = decoded_image;
551        self.try_borrow_component_planes(
552            decoded_components.as_slice(),
553            decoded_components.capacity(),
554            dimensions,
555        )
556    }
557}