Skip to main content

j2k_native/
image.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use alloc::vec::Vec;
4
5use crate::error::{err, ValidationError};
6use crate::j2c::mct::DeferredMct;
7use crate::j2c::{self, ComponentData, Header};
8use crate::jp2::colr::EnumeratedColorspace;
9use crate::jp2::{self, DecodedImage, ImageBoxes};
10use crate::CpuDecodeParallelism;
11use crate::{
12    checked_decode_byte_len3, convert_color_space, interleave_and_convert_after_mct,
13    interleave_and_convert_region, resolve_palette_indices, try_resize_decode_elements,
14    validate_and_reorder_channels, validate_interleaved_output_buffer, validate_roi, Bitmap,
15    ColorSpace, DecodedComponents, DecodedNativeComponents, DecoderContext, DecodingError,
16    FormatError, HtCodeBlockDecoder, Result, CODESTREAM_MAGIC, JP2_MAGIC,
17};
18
19mod allocation;
20mod compare;
21#[cfg(test)]
22mod contract_tests;
23mod direct_api;
24mod native;
25mod output_api;
26#[cfg(feature = "std")]
27pub(crate) mod packet_directory;
28use self::allocation::retained_metadata_bytes;
29pub(crate) use self::allocation::{retained_container_metadata_bytes, DecodeOwnerBudget};
30use self::native::{try_clone_color_space, NativeOutputBudget};
31
32/// What [`Image::decode_image_for_rgb8`] leaves to the RGB8 pack: the
33/// deferred MCT, the first component's LL band of a last level the MCT
34/// carries, and the image.
35type Rgb8DecodedImage<'ctx, 'boxes> = (
36    Option<DeferredMct>,
37    &'ctx [f32],
38    [&'ctx mut [f32]; 2],
39    DecodedImage<'ctx, 'boxes>,
40    usize,
41);
42
43/// Settings to apply during decoding.
44#[derive(Debug, Copy, Clone)]
45pub struct DecodeSettings {
46    /// Whether palette indices should be resolved.
47    ///
48    /// JPEG2000 images can be stored in two different ways. First, by storing
49    /// RGB values (depending on the color space) for each pixel. Secondly, by
50    /// only storing a single index for each channel, and then resolving the
51    /// actual color using the index.
52    ///
53    /// If you disable this option, in case you have an image with palette
54    /// indices, they will not be resolved, but instead a grayscale image
55    /// will be returned, with each pixel value corresponding to the palette
56    /// index of the location.
57    pub resolve_palette_indices: bool,
58    /// Whether strict mode should be enabled when decoding.
59    ///
60    /// The default is strict. Lenient mode is limited to the recoveries listed
61    /// on [`DecodeSettings::lenient`]; it never relaxes codestream, bounds,
62    /// overflow, allocation, or resource-limit validation.
63    pub strict: bool,
64    /// A hint for the target resolution that the image should be decoded at.
65    pub target_resolution: Option<(u32, u32)>,
66}
67
68impl DecodeSettings {
69    /// Compatibility settings for explicitly recoverable JP2/JPH metadata.
70    ///
71    /// Lenient mode may:
72    ///
73    /// - ignore a malformed trailing top-level box after the required image
74    ///   header and codestream boxes were parsed;
75    /// - ignore a malformed trailing child box after the required `ihdr` and
76    ///   `colr` boxes were parsed;
77    /// - ignore a malformed optional `cdef` or `pclr` box, preserving any
78    ///   earlier complete value; and
79    /// - infer an undeclared alpha channel when an otherwise consistent JP2
80    ///   color declaration has exactly one extra codestream component.
81    ///
82    /// Raw codestream validation and entropy decoding remain strict. Bounds,
83    /// integer-overflow, allocation, and resource-limit checks are identical
84    /// in both modes.
85    #[must_use]
86    pub const fn lenient() -> Self {
87        Self {
88            resolve_palette_indices: true,
89            strict: false,
90            target_resolution: None,
91        }
92    }
93
94    /// Strict decode settings: malformed metadata is an error.
95    #[must_use]
96    pub const fn strict() -> Self {
97        Self {
98            resolve_palette_indices: true,
99            strict: true,
100            target_resolution: None,
101        }
102    }
103
104    /// Whether the settings permit lenient tolerance of malformed optional
105    /// metadata.
106    #[must_use]
107    pub const fn lenient_tolerance_enabled(&self) -> bool {
108        !self.strict
109    }
110}
111
112impl Default for DecodeSettings {
113    fn default() -> Self {
114        Self::strict()
115    }
116}
117
118/// A JPEG2000 image or codestream.
119pub struct Image<'a> {
120    /// Complete encoded input retained by the caller. Referenced execution
121    /// plans express compressed payload ranges relative to this owner.
122    pub(crate) encoded_input: &'a [u8],
123    /// The tile-part payload used by the legacy JPEG 2000 decoder.
124    pub(crate) codestream: &'a [u8],
125    /// The header of the J2C codestream.
126    pub(crate) header: Header<'a>,
127    /// The JP2 boxes of the image. In the case of a raw codestream, we
128    /// will synthesize the necessary boxes.
129    pub(crate) boxes: ImageBoxes,
130    /// Settings that should be applied during decoding.
131    pub(crate) settings: DecodeSettings,
132    /// Whether parsing used one of the explicitly documented lenient
133    /// container-metadata recoveries.
134    pub(crate) used_lenient_metadata_recovery: bool,
135    /// Whether the image has an alpha channel.
136    pub(crate) has_alpha: bool,
137    /// The color space of the image.
138    pub(crate) color_space: ColorSpace,
139    /// Codec-owned bytes already live when this image was parsed, set by
140    /// `new_with_retained_baseline`. Only the planning entry points charge it:
141    /// `prepare_region_decoder_with_context` and the `build_*_plan*_with_context`
142    /// direct and referenced planners. Pixel decode entry points (`decode*`,
143    /// `decode_native*`, `decode_into`) charge only this image's metadata;
144    /// callers that need the baseline there pass it explicitly through the
145    /// `*_with_retained_capacity` variants.
146    retained_baseline_bytes: usize,
147    #[cfg(feature = "std")]
148    pub(crate) packet_cache: packet_directory::PacketDirectoryCache,
149}
150
151/// Scoped region decoder that retains one parsed tile graph across calls.
152///
153/// This implementation-facing session is used by bounded row decode so each
154/// stripe repeats only the ROI decode work, not codestream tile parsing.
155#[doc(hidden)]
156pub struct PreparedRegionDecoder<'image, 'context, 'a> {
157    image: &'image Image<'a>,
158    decoder_context: &'context mut DecoderContext<'a>,
159    tiles: j2c::ParsedTiles<'a>,
160    retained_image_bytes: usize,
161    retained_session_bytes: usize,
162}
163
164#[derive(Clone, Copy)]
165pub(crate) struct ImageSource<'a> {
166    encoded_input: &'a [u8],
167    codestream: &'a [u8],
168}
169
170impl<'a> ImageSource<'a> {
171    pub(crate) const fn new(encoded_input: &'a [u8], codestream: &'a [u8]) -> Self {
172        Self {
173            encoded_input,
174            codestream,
175        }
176    }
177}
178
179pub(crate) struct ImageProperties {
180    boxes: ImageBoxes,
181    settings: DecodeSettings,
182    color_space: ColorSpace,
183    has_alpha: bool,
184    used_lenient_metadata_recovery: bool,
185}
186
187impl ImageProperties {
188    pub(crate) const fn new(
189        boxes: ImageBoxes,
190        settings: DecodeSettings,
191        color_space: ColorSpace,
192        has_alpha: bool,
193        used_lenient_metadata_recovery: bool,
194    ) -> Self {
195        Self {
196            boxes,
197            settings,
198            color_space,
199            has_alpha,
200            used_lenient_metadata_recovery,
201        }
202    }
203}
204
205impl<'a> Image<'a> {
206    /// Parse and retain the tile graph for repeated region decode calls.
207    ///
208    /// # Errors
209    ///
210    /// Returns an error when tile parsing or aggregate allocation validation fails.
211    #[doc(hidden)]
212    pub fn prepare_region_decoder_with_context<'image, 'context>(
213        &'image self,
214        decoder_context: &'context mut DecoderContext<'a>,
215    ) -> Result<PreparedRegionDecoder<'image, 'context, 'a>> {
216        let retained_image_bytes = allocation::combine_retained_bytes(
217            self.retained_baseline_bytes,
218            self.retained_header_metadata_bytes()?,
219        )?;
220        let tiles = self.with_packet_cache_retry(|| {
221            j2c::prepare_region_tiles(
222                self.codestream,
223                &self.header,
224                self.retained_planning_bytes()?,
225                decoder_context,
226            )
227        })?;
228        let retained_session_bytes = retained_image_bytes
229            .checked_add(tiles.metadata_owner_bytes())
230            .ok_or(ValidationError::ImageTooLarge)?;
231        Ok(PreparedRegionDecoder {
232            image: self,
233            decoder_context,
234            tiles,
235            retained_image_bytes,
236            retained_session_bytes,
237        })
238    }
239
240    pub(crate) fn from_parsed_parts(
241        source: ImageSource<'a>,
242        header: Header<'a>,
243        properties: ImageProperties,
244    ) -> Result<Self> {
245        Self::from_parsed_parts_with_retained_baseline(source, header, properties, 0)
246    }
247
248    pub(crate) fn from_parsed_parts_with_retained_baseline(
249        source: ImageSource<'a>,
250        header: Header<'a>,
251        properties: ImageProperties,
252        retained_baseline_bytes: usize,
253    ) -> Result<Self> {
254        let ImageProperties {
255            boxes,
256            settings,
257            color_space,
258            has_alpha,
259            used_lenient_metadata_recovery,
260        } = properties;
261        let metadata_bytes = retained_metadata_bytes(&header, &boxes, &color_space)?;
262        allocation::combine_retained_bytes(retained_baseline_bytes, metadata_bytes)?;
263        Ok(Self {
264            encoded_input: source.encoded_input,
265            codestream: source.codestream,
266            header,
267            boxes,
268            settings,
269            used_lenient_metadata_recovery,
270            has_alpha,
271            color_space,
272            retained_baseline_bytes,
273            #[cfg(feature = "std")]
274            packet_cache: packet_directory::PacketDirectoryCache::default(),
275        })
276    }
277
278    fn retained_header_metadata_bytes(&self) -> Result<usize> {
279        retained_metadata_bytes(&self.header, &self.boxes, &self.color_space)
280    }
281
282    pub(crate) fn retained_metadata_bytes(&self) -> Result<usize> {
283        let bytes = self.retained_header_metadata_bytes()?;
284        #[cfg(feature = "std")]
285        let bytes = allocation::combine_retained_bytes(bytes, self.packet_cache.retained_bytes())?;
286        Ok(bytes)
287    }
288
289    pub(crate) fn retained_planning_bytes(&self) -> Result<usize> {
290        allocation::combine_retained_bytes(
291            self.retained_baseline_bytes,
292            self.retained_metadata_bytes()?,
293        )
294    }
295
296    /// Return the allocator capacities retained by this parsed image.
297    ///
298    /// # Errors
299    ///
300    /// Returns an error if nested metadata capacity arithmetic overflows or
301    /// exceeds the native decode cap.
302    #[doc(hidden)]
303    pub fn retained_allocation_bytes(&self) -> Result<usize> {
304        self.retained_metadata_bytes()
305    }
306
307    /// Whether parsing used an explicitly documented lenient metadata recovery.
308    #[doc(hidden)]
309    #[must_use]
310    pub const fn used_lenient_metadata_recovery(&self) -> bool {
311        self.used_lenient_metadata_recovery
312    }
313
314    /// Try to create a new JPEG2000 image from the given data.
315    ///
316    /// # Errors
317    ///
318    /// Returns an error when the input signature, container, or codestream is invalid.
319    pub fn new(data: &'a [u8], settings: &DecodeSettings) -> Result<Self> {
320        if data.starts_with(JP2_MAGIC) {
321            jp2::parse(data, *settings, None)
322        } else if data.starts_with(CODESTREAM_MAGIC) {
323            j2c::parse(data, settings, None)
324        } else {
325            err!(FormatError::InvalidSignature)
326        }
327    }
328
329    /// Parse an image at an exact JPEG 2000 resolution reduction level.
330    ///
331    /// This low-level adapter exists for the `j2k` facade. A reduction of zero
332    /// preserves full resolution; each additional level halves both axes.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error when a target-resolution hint is also configured, the
337    /// reduction is not representable, the codestream has too few resolution
338    /// levels, or the input is invalid.
339    #[doc(hidden)]
340    pub fn new_with_reduction(
341        data: &'a [u8],
342        settings: &DecodeSettings,
343        reduction_levels: u8,
344    ) -> Result<Self> {
345        if settings.target_resolution.is_some() {
346            return err!(DecodingError::UnsupportedFeature(
347                "exact reduction cannot be combined with a target-resolution hint",
348            ));
349        }
350        if data.starts_with(JP2_MAGIC) {
351            jp2::parse(data, *settings, Some(reduction_levels))
352        } else if data.starts_with(CODESTREAM_MAGIC) {
353            j2c::parse(data, settings, Some(reduction_levels))
354        } else {
355            err!(FormatError::InvalidSignature)
356        }
357    }
358
359    /// Parse an image while accounting already-live codec-owned allocations.
360    ///
361    /// This adapter is used when validation parses another image while an
362    /// encoded output and earlier parsed metadata remain live. The baseline is
363    /// retained for later tile and direct-plan allocation phases on this image.
364    ///
365    /// # Errors
366    ///
367    /// Returns an error when the input is invalid or aggregate parser-owned
368    /// allocations exceed the native decode cap.
369    #[doc(hidden)]
370    pub fn new_with_retained_baseline(
371        data: &'a [u8],
372        settings: &DecodeSettings,
373        retained_baseline_bytes: usize,
374    ) -> Result<Self> {
375        if retained_baseline_bytes == 0 {
376            return Self::new(data, settings);
377        }
378        if data.starts_with(JP2_MAGIC) {
379            jp2::parse_with_retained_baseline(data, *settings, retained_baseline_bytes, None)
380        } else if data.starts_with(CODESTREAM_MAGIC) {
381            j2c::parse_with_retained_baseline(data, settings, retained_baseline_bytes, None)
382        } else {
383            err!(FormatError::InvalidSignature)
384        }
385    }
386
387    /// Whether the image has an alpha channel.
388    #[must_use]
389    pub fn has_alpha(&self) -> bool {
390        self.has_alpha
391    }
392
393    /// The color space of the image.
394    #[must_use]
395    pub fn color_space(&self) -> &ColorSpace {
396        &self.color_space
397    }
398
399    /// Return the primary JP2 restricted ICC profile, when present.
400    #[doc(hidden)]
401    #[must_use]
402    pub fn primary_icc_profile(&self) -> Option<&[u8]> {
403        match self
404            .boxes
405            .primary_color_specification()
406            .map(|specification| &specification.color_space)
407        {
408            Some(jp2::colr::ColorSpace::Icc(profile)) => Some(profile),
409            _ => None,
410        }
411    }
412
413    /// The width of the image.
414    #[must_use]
415    pub fn width(&self) -> u32 {
416        self.header.size_data.image_width()
417    }
418
419    /// The height of the image.
420    #[must_use]
421    pub fn height(&self) -> u32 {
422        self.header.size_data.image_height()
423    }
424
425    /// Whether the main header's default coding style selects HT block coding.
426    #[doc(hidden)]
427    #[must_use]
428    pub fn uses_high_throughput_block_coding(&self) -> bool {
429        self.header
430            .global_coding_style
431            .component_parameters
432            .parameters
433            .code_block_style
434            .uses_high_throughput_block_coding()
435    }
436
437    /// The original bit depth of the image. You usually don't need to do anything
438    /// with this parameter, it just exists for informational purposes.
439    #[must_use]
440    pub fn original_bit_depth(&self) -> u8 {
441        // Note that this only works if all components have the same precision.
442        self.header.component_infos[0].size_info.precision
443    }
444
445    /// Whether decode finishes with additional host-side component mutation or reordering.
446    #[doc(hidden)]
447    #[must_use]
448    pub fn supports_direct_device_plane_reuse(&self) -> bool {
449        if self.settings.resolve_palette_indices && self.boxes.palette.is_some() {
450            return false;
451        }
452        if self.boxes.channel_definition.is_some() {
453            return false;
454        }
455        !matches!(
456            self.boxes
457                .primary_color_specification()
458                .map(|spec| &spec.color_space),
459            Some(jp2::colr::ColorSpace::Enumerated(
460                EnumeratedColorspace::Sycc | EnumeratedColorspace::CieLab(_)
461            ))
462        )
463    }
464
465    /// Number of tiles in the codestream's tile grid.
466    #[doc(hidden)]
467    #[must_use]
468    pub fn tile_count(&self) -> u32 {
469        self.header.size_data.num_tiles()
470    }
471
472    /// Decode the image and return its decoded result as a `Vec<u8>`, with each
473    /// channel interleaved.
474    ///
475    /// # Errors
476    ///
477    /// Returns an error when image validation, decoding, or output allocation fails.
478    pub fn decode(&self) -> Result<Vec<u8>> {
479        let bitmap = self.decode_with_context(&mut DecoderContext::default())?;
480        Ok(bitmap.data)
481    }
482
483    /// Decode the image and return its decoded result using a caller-provided
484    /// decoder context so allocations can be reused across repeated decodes.
485    ///
486    /// # Errors
487    ///
488    /// Returns an error when image validation, decoding, or output allocation fails.
489    pub fn decode_with_context(&self, decoder_context: &mut DecoderContext<'a>) -> Result<Bitmap> {
490        self.with_packet_cache_retry(|| {
491            let retained_image_bytes = self.retained_header_metadata_bytes()?;
492            let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
493            let (mct, first_ll, scratch, mut decoded_image, retained_image_bytes) =
494                self.decode_image_for_rgb8(decoder_context, None, retained_image_bytes)?;
495            let component_owner_capacity = decoded_image.decoded_components.capacity();
496            let buffer_size = checked_decode_byte_len3(
497                self.width() as usize,
498                self.height() as usize,
499                decoded_image.decoded_components.len(),
500            )?;
501            let mut budget = NativeOutputBudget::for_decoded_channels(
502                retained_image_bytes,
503                decoded_image.decoded_components,
504                component_owner_capacity,
505            )?;
506            budget.include_elements::<u8>(buffer_size)?;
507            budget.include_color_space_clone(&self.color_space)?;
508
509            let color_space = try_clone_color_space(&self.color_space)?;
510            budget.include_color_space_clone_overage(&self.color_space, &color_space)?;
511            let mut data = Vec::new();
512            try_resize_decode_elements(&mut data, buffer_size, 0_u8)?;
513            budget.include_capacity_overage::<u8>(buffer_size, data.capacity())?;
514            validate_interleaved_output_buffer(&decoded_image, &data)?;
515            interleave_and_convert_after_mct(
516                &mut decoded_image,
517                &mut data,
518                mct,
519                first_ll,
520                scratch,
521                parallel,
522            )?;
523            let bitmap = Bitmap {
524                color_space,
525                data,
526                has_alpha: self.has_alpha,
527                width: self.width(),
528                height: self.height(),
529                original_bit_depth: self.original_bit_depth(),
530            };
531            NativeOutputBudget::validate_bitmap_pack(
532                retained_image_bytes,
533                decoded_image.decoded_components,
534                component_owner_capacity,
535                &bitmap,
536            )?;
537            Ok(bitmap)
538        })
539    }
540
541    /// Decode the image into borrowed component planes using a caller-provided
542    /// decoder context so allocations can be reused across repeated decodes.
543    ///
544    /// # Errors
545    ///
546    /// Returns an error when component precision is unsupported or decoding fails.
547    pub fn decode_components_with_context<'ctx>(
548        &self,
549        decoder_context: &'ctx mut DecoderContext<'a>,
550    ) -> Result<DecodedComponents<'ctx>> {
551        self.decode_borrowed_component_planes(decoder_context, None, None, false)
552    }
553
554    /// Decode the image into owned native-bit-depth component planes.
555    ///
556    /// Unlike [`Self::decode_native`], this preserves per-component bit depth
557    /// and signedness metadata and does not require all components to share a
558    /// single packed interleaved representation.
559    ///
560    /// # Errors
561    ///
562    /// Returns an error when validation, decoding, or native sample packing fails.
563    pub fn decode_native_components(&self) -> Result<DecodedNativeComponents> {
564        let mut decoder_context = DecoderContext::default();
565        self.decode_native_components_with_context(&mut decoder_context)
566    }
567
568    /// Decode owned native component planes while accounting an already-live
569    /// external allocation, such as the encoded `Vec` being validated.
570    ///
571    /// `retained_capacity` must be the allocator capacity of that external
572    /// owner, not merely its logical length.
573    ///
574    /// # Errors
575    ///
576    /// Returns an error when aggregate retained allocation accounting,
577    /// decoding, or native component packing fails.
578    #[doc(hidden)]
579    pub fn decode_native_components_with_retained_capacity(
580        &self,
581        retained_capacity: usize,
582    ) -> Result<DecodedNativeComponents> {
583        let retained_baseline_bytes = allocation::combine_retained_bytes(
584            retained_capacity,
585            self.retained_header_metadata_bytes()?,
586        )?;
587        let mut decoder_context = DecoderContext::default();
588        self.decode_native_components_with_context_and_retained_baseline(
589            &mut decoder_context,
590            retained_baseline_bytes,
591        )
592    }
593
594    /// Decode the image into owned native-bit-depth component planes using a
595    /// caller-provided decoder context.
596    ///
597    /// # Errors
598    ///
599    /// Returns an error when validation, decoding, or native sample packing fails.
600    pub fn decode_native_components_with_context(
601        &self,
602        decoder_context: &mut DecoderContext<'a>,
603    ) -> Result<DecodedNativeComponents> {
604        let retained_baseline_bytes = self.retained_header_metadata_bytes()?;
605        self.decode_native_components_with_context_and_retained_baseline(
606            decoder_context,
607            retained_baseline_bytes,
608        )
609    }
610
611    fn decode_native_components_with_context_and_retained_baseline(
612        &self,
613        decoder_context: &mut DecoderContext<'a>,
614        retained_baseline_bytes: usize,
615    ) -> Result<DecodedNativeComponents> {
616        self.with_packet_cache_retry(|| {
617            let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
618            let (decoded_image, retained_baseline_bytes) =
619                self.decode_image(decoder_context, None, None, true, retained_baseline_bytes)?;
620            let DecodedImage {
621                decoded_components,
622                boxes: _,
623            } = decoded_image;
624            let component_owner_capacity = decoded_components.capacity();
625            self.pack_native_component_planes(
626                decoded_components,
627                component_owner_capacity,
628                (self.width(), self.height()),
629                retained_baseline_bytes,
630                parallel,
631            )
632        })
633    }
634
635    /// Decode borrowed component planes while delegating HTJ2K code-block decode.
636    #[doc(hidden)]
637    pub fn decode_components_with_ht_decoder<'ctx>(
638        &self,
639        decoder_context: &'ctx mut DecoderContext<'a>,
640        ht_decoder: &mut dyn HtCodeBlockDecoder,
641    ) -> Result<DecodedComponents<'ctx>> {
642        self.decode_borrowed_component_planes(decoder_context, None, Some(ht_decoder), false)
643    }
644
645    /// Decode borrowed component planes for a requested region using a
646    /// caller-provided decoder context.
647    ///
648    /// # Errors
649    ///
650    /// Returns an error when the region is invalid, precision is unsupported, or decoding fails.
651    pub fn decode_region_components_with_context<'ctx>(
652        &self,
653        roi: (u32, u32, u32, u32),
654        decoder_context: &'ctx mut DecoderContext<'a>,
655    ) -> Result<DecodedComponents<'ctx>> {
656        self.decode_borrowed_component_planes(decoder_context, Some(roi), None, false)
657    }
658
659    /// Decode a source-coordinate region into owned native-bit-depth component
660    /// planes using a caller-provided decoder context.
661    ///
662    /// # Errors
663    ///
664    /// Returns an error when the region is invalid or decoding and packing fail.
665    pub fn decode_native_region_components_with_context(
666        &self,
667        roi: (u32, u32, u32, u32),
668        decoder_context: &mut DecoderContext<'a>,
669    ) -> Result<DecodedNativeComponents> {
670        self.with_packet_cache_retry(|| {
671            validate_roi((self.width(), self.height()), roi)?;
672            if self.requires_exact_integer_decode() {
673                return self.decode_native_region_components_via_full_decode(roi, decoder_context);
674            }
675            let (_x, _y, width, height) = roi;
676            let retained_image_bytes = self.retained_header_metadata_bytes()?;
677            let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
678            let (decoded_image, retained_image_bytes) =
679                self.decode_image(decoder_context, Some(roi), None, true, retained_image_bytes)?;
680            let DecodedImage {
681                decoded_components,
682                boxes: _,
683            } = decoded_image;
684            let component_owner_capacity = decoded_components.capacity();
685            self.pack_native_component_planes(
686                decoded_components,
687                component_owner_capacity,
688                (width, height),
689                retained_image_bytes,
690                parallel,
691            )
692        })
693    }
694
695    /// Decode borrowed component planes for a requested region while
696    /// delegating code-block/transform stages through the adapter backend hook.
697    #[doc(hidden)]
698    pub fn decode_region_components_with_ht_decoder<'ctx>(
699        &self,
700        decoder_context: &'ctx mut DecoderContext<'a>,
701        roi: (u32, u32, u32, u32),
702        ht_decoder: &mut dyn HtCodeBlockDecoder,
703    ) -> Result<DecodedComponents<'ctx>> {
704        self.decode_borrowed_component_planes(decoder_context, Some(roi), Some(ht_decoder), false)
705    }
706
707    /// Decode a region of the image and return it as an 8-bit interleaved bitmap.
708    ///
709    /// # Errors
710    ///
711    /// Returns an error when the region is invalid or decoding fails.
712    pub fn decode_region(&self, roi: (u32, u32, u32, u32)) -> Result<Bitmap> {
713        self.decode_region_with_context(roi, &mut DecoderContext::default())
714    }
715
716    /// Decode a region of the image and return it as an 8-bit interleaved bitmap
717    /// using a caller-provided decoder context.
718    ///
719    /// # Errors
720    ///
721    /// Returns an error when the region is invalid, decoding fails, or output sizing overflows.
722    pub fn decode_region_with_context(
723        &self,
724        roi: (u32, u32, u32, u32),
725        decoder_context: &mut DecoderContext<'a>,
726    ) -> Result<Bitmap> {
727        validate_roi((self.width(), self.height()), roi)?;
728        self.with_packet_cache_retry(|| {
729            let retained_image_bytes = self.retained_header_metadata_bytes()?;
730            let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
731            let (mct, first_ll, scratch, mut decoded_image, retained_image_bytes) =
732                self.decode_image_for_rgb8(decoder_context, Some(roi), retained_image_bytes)?;
733            let component_owner_capacity = decoded_image.decoded_components.capacity();
734            let (_x, _y, width, height) = roi;
735            let data_len = checked_decode_byte_len3(
736                width as usize,
737                height as usize,
738                decoded_image.decoded_components.len(),
739            )?;
740            let mut budget = NativeOutputBudget::for_decoded_channels(
741                retained_image_bytes,
742                decoded_image.decoded_components,
743                component_owner_capacity,
744            )?;
745            budget.include_elements::<u8>(data_len)?;
746            budget.include_color_space_clone(&self.color_space)?;
747
748            let color_space = try_clone_color_space(&self.color_space)?;
749            budget.include_color_space_clone_overage(&self.color_space, &color_space)?;
750            let mut data = Vec::new();
751            try_resize_decode_elements(&mut data, data_len, 0_u8)?;
752            budget.include_capacity_overage::<u8>(data_len, data.capacity())?;
753            if mct.is_some() {
754                interleave_and_convert_after_mct(
755                    &mut decoded_image,
756                    &mut data,
757                    mct,
758                    first_ll,
759                    scratch,
760                    parallel,
761                )?;
762            } else {
763                interleave_and_convert_region(
764                    &mut decoded_image,
765                    width as usize,
766                    (0, 0, width, height),
767                    &mut data,
768                )?;
769            }
770            let bitmap = Bitmap {
771                color_space,
772                data,
773                has_alpha: self.has_alpha,
774                width,
775                height,
776                original_bit_depth: self.original_bit_depth(),
777            };
778            NativeOutputBudget::validate_bitmap_pack(
779                retained_image_bytes,
780                decoded_image.decoded_components,
781                component_owner_capacity,
782                &bitmap,
783            )?;
784            Ok(bitmap)
785        })
786    }
787
788    /// Decode the image into the given buffer.
789    ///
790    /// This method does the same as [`Image::decode`], but you can provide
791    /// a custom buffer for the output, as well as a decoder context. Doing
792    /// so allows the internal decode engine to reuse memory allocations, so
793    /// this is especially recommended if you plan on converting multiple
794    /// images in the same session.
795    ///
796    /// The buffer must have the correct size.
797    ///
798    /// # Errors
799    ///
800    /// Returns an error when decoding fails or `buf` is too small for the image.
801    pub fn decode_into(
802        &self,
803        buf: &mut [u8],
804        decoder_context: &mut DecoderContext<'a>,
805    ) -> Result<()> {
806        self.with_packet_cache_retry(|| {
807            let parallel = decoder_context.cpu_decode_parallelism() == CpuDecodeParallelism::Auto;
808            let retained = self.retained_header_metadata_bytes()?;
809            let (mct, first_ll, scratch, mut decoded_image, _retained_image_bytes) =
810                self.decode_image_for_rgb8(decoder_context, None, retained)?;
811            validate_interleaved_output_buffer(&decoded_image, buf)?;
812            interleave_and_convert_after_mct(
813                &mut decoded_image,
814                buf,
815                mct,
816                first_ll,
817                scratch,
818                parallel,
819            )?;
820
821            Ok(())
822        })
823    }
824
825    fn decode_image<'ctx>(
826        &self,
827        decoder_context: &'ctx mut DecoderContext<'a>,
828        output_region: Option<(u32, u32, u32, u32)>,
829        ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
830        round_irreversible_output: bool,
831        retained_baseline_bytes: usize,
832    ) -> Result<(DecodedImage<'ctx, '_>, usize)> {
833        let retained_baseline_bytes = self.run_decode(
834            decoder_context,
835            output_region,
836            ht_decoder,
837            round_irreversible_output,
838            retained_baseline_bytes,
839        )?;
840        let decoded = self.finish_decoded_image(
841            &mut decoder_context.tile_decode_context.channel_data,
842            retained_baseline_bytes,
843        )?;
844        Ok((decoded, retained_baseline_bytes))
845    }
846
847    /// [`Self::decode_image`] for an RGB8 pack. A plain colour image (see
848    /// [`Self::permits_line_region_idwt`]) leaves its inverse MCT, and
849    /// possibly its last IDWT level, to the pack, which receives them with
850    /// the image. Regions keep the streamed IDWT and only defer the MCT.
851    fn decode_image_for_rgb8<'ctx>(
852        &self,
853        decoder_context: &'ctx mut DecoderContext<'a>,
854        output_region: Option<(u32, u32, u32, u32)>,
855        retained_baseline_bytes: usize,
856    ) -> Result<Rgb8DecodedImage<'ctx, '_>> {
857        let defer_mct = self.permits_line_region_idwt();
858        #[cfg(test)]
859        let defer_mct = defer_mct && !decoder_context.tile_decode_context.staged_rgb8_pack;
860        decoder_context.set_defer_mct(defer_mct);
861        let decoded = self.run_decode(
862            decoder_context,
863            output_region,
864            None,
865            true,
866            retained_baseline_bytes,
867        );
868        decoder_context.set_defer_mct(false);
869        let retained_baseline_bytes = decoded?;
870        let tile_ctx = &mut decoder_context.tile_decode_context;
871        let mct = tile_ctx.deferred_mct.take();
872        let image =
873            self.finish_decoded_image(&mut tile_ctx.channel_data, retained_baseline_bytes)?;
874        Ok((
875            mct,
876            &tile_ctx.idwt_output.coefficients,
877            [
878                &mut tile_ctx.idwt_scratch_buffer,
879                &mut tile_ctx.idwt_band_scratch,
880            ],
881            image,
882            retained_baseline_bytes,
883        ))
884    }
885
886    // Capacity failures can occur after core decoding, while packing or
887    // planning output. Drop an idle cache and retry the complete operation.
888    #[cfg_attr(
889        not(feature = "std"),
890        expect(
891            clippy::unused_self,
892            reason = "without std there is no packet cache, so only the std retry reads self"
893        )
894    )]
895    fn with_packet_cache_retry<T>(&self, mut operation: impl FnMut() -> Result<T>) -> Result<T> {
896        let result = operation();
897        #[cfg(feature = "std")]
898        if result.as_ref().is_err_and(j2c::is_capacity_error) {
899            if let Some(directory) = self.packet_cache.snapshot() {
900                if self.packet_cache.evict_if_unshared(&directory) {
901                    drop(directory);
902                    return operation();
903                }
904            }
905        }
906        result
907    }
908
909    fn run_decode(
910        &self,
911        decoder_context: &mut DecoderContext<'a>,
912        output_region: Option<(u32, u32, u32, u32)>,
913        ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
914        round_irreversible_output: bool,
915        retained_baseline_bytes: usize,
916    ) -> Result<usize> {
917        self.run_decode_rows(
918            decoder_context,
919            output_region,
920            ht_decoder,
921            round_irreversible_output,
922            retained_baseline_bytes,
923            None,
924        )
925    }
926
927    /// [`Self::run_decode`] that hands a region's samples to `row_sink` as
928    /// they are finished when the decode can stream them, in which case it
929    /// leaves no component planes (see `TileDecodeContext::stream_rows`).
930    pub(super) fn run_decode_rows(
931        &self,
932        decoder_context: &mut DecoderContext<'a>,
933        output_region: Option<(u32, u32, u32, u32)>,
934        ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
935        round_irreversible_output: bool,
936        retained_baseline_bytes: usize,
937        row_sink: Option<&mut dyn crate::ComponentRowSink>,
938    ) -> Result<usize> {
939        let mut ht_decoder = ht_decoder;
940        let mut row_sink = row_sink;
941        decoder_context.storage.release_all_allocations();
942        #[cfg(feature = "std")]
943        let directory = self.region_packet_directory(
944            retained_baseline_bytes,
945            decoder_context,
946            output_region.is_some() && ht_decoder.is_none() && self.permits_line_region_idwt(),
947            None,
948        );
949        decoder_context.set_output_region(output_region);
950        decoder_context.set_round_irreversible_output(round_irreversible_output);
951        decoder_context.permit_line_region_idwt(self.permits_line_region_idwt());
952        #[cfg(feature = "std")]
953        let decode_result = allocation::combine_retained_bytes(
954            retained_baseline_bytes,
955            directory
956                .as_deref()
957                .map_or(0, packet_directory::PacketDirectoryCache::directory_bytes),
958        )
959        .and_then(|source_bytes| {
960            let index_directory = directory
961                .as_ref()
962                .filter(|_| output_region.is_some() && ht_decoder.is_none());
963            j2c::decode_rows(
964                self.codestream,
965                &self.header,
966                source_bytes,
967                decoder_context,
968                &mut ht_decoder,
969                &mut row_sink,
970                index_directory,
971            )
972            .map(|()| source_bytes)
973        });
974        #[cfg(not(feature = "std"))]
975        let decode_result = j2c::decode_rows(
976            self.codestream,
977            &self.header,
978            retained_baseline_bytes,
979            decoder_context,
980            &mut ht_decoder,
981            &mut row_sink,
982        )
983        .map(|()| retained_baseline_bytes);
984        // Rows already handed to the sink can't be taken back, so once any
985        // have streamed a rerun would only send them again.
986        #[cfg(feature = "std")]
987        let decode_result = if decode_result.as_ref().is_err_and(j2c::is_capacity_error)
988            && decoder_context.tile_decode_context.streamed_samples == 0
989            && directory
990                .as_ref()
991                .is_some_and(|directory| self.packet_cache.evict_if_unshared(directory))
992        {
993            drop(directory);
994            j2c::decode_rows(
995                self.codestream,
996                &self.header,
997                retained_baseline_bytes,
998                decoder_context,
999                &mut ht_decoder,
1000                &mut row_sink,
1001                None,
1002            )
1003            .map(|()| retained_baseline_bytes)
1004        } else {
1005            decode_result
1006        };
1007        decoder_context.set_output_region(None);
1008        decoder_context.set_round_irreversible_output(false);
1009        decoder_context.permit_line_region_idwt(false);
1010        decode_result
1011    }
1012
1013    #[cfg(feature = "std")]
1014    fn region_packet_directory(
1015        &self,
1016        retained_baseline_bytes: usize,
1017        decoder_context: &DecoderContext<'a>,
1018        prepare: bool,
1019        prepared_tiles: Option<&j2c::ParsedTiles<'a>>,
1020    ) -> Option<alloc::sync::Arc<j2c::PacketDirectory>> {
1021        if !prepare {
1022            return self.packet_cache.snapshot();
1023        }
1024        self.packet_cache.for_region(|limit| {
1025            let live = retained_baseline_bytes
1026                .checked_add(decoder_context.retained_decode_bytes().ok()?)?
1027                .checked_add(
1028                    core::mem::size_of::<j2c::PacketDirectory>()
1029                        + 2 * core::mem::size_of::<usize>(),
1030                )?;
1031            if let Some(tiles) = prepared_tiles {
1032                return tiles.build_packet_directory(&self.header, live, limit);
1033            }
1034            let tiles = j2c::prepare_region_tiles(
1035                self.codestream,
1036                &self.header,
1037                live,
1038                &mut DecoderContext::default(),
1039            )
1040            .ok()?;
1041            let retained = live.checked_add(tiles.metadata_owner_bytes())?;
1042            tiles.build_packet_directory(&self.header, retained, limit)
1043        })
1044    }
1045
1046    /// Whether a region decode of this image may use the row-streamed region
1047    /// IDWT: it has no palette, component-definition, sYCC or `CIELab`
1048    /// post-processing, which keep the whole-window path.
1049    pub(crate) fn permits_line_region_idwt(&self) -> bool {
1050        self.boxes.palette.is_none()
1051            && self.boxes.channel_definition.is_none()
1052            && !matches!(
1053                self.boxes
1054                    .primary_color_specification()
1055                    .map(|specification| &specification.color_space),
1056                Some(jp2::colr::ColorSpace::Enumerated(
1057                    EnumeratedColorspace::Sycc | EnumeratedColorspace::CieLab(_)
1058                ))
1059            )
1060    }
1061
1062    fn finish_decoded_image<'ctx>(
1063        &self,
1064        channel_data: &'ctx mut Vec<ComponentData>,
1065        retained_baseline_bytes: usize,
1066    ) -> Result<DecodedImage<'ctx, '_>> {
1067        let settings = &self.settings;
1068        let mut decoded_image = DecodedImage {
1069            decoded_components: channel_data,
1070            boxes: &self.boxes,
1071        };
1072
1073        if settings.resolve_palette_indices {
1074            let components = core::mem::take(decoded_image.decoded_components);
1075            *decoded_image.decoded_components =
1076                resolve_palette_indices(components, decoded_image.boxes, retained_baseline_bytes)?;
1077        }
1078
1079        if let Some(cdef) = decoded_image
1080            .boxes
1081            .primary_color_specification()
1082            .and(decoded_image.boxes.channel_definition.as_ref())
1083        {
1084            validate_and_reorder_channels(
1085                cdef,
1086                decoded_image.decoded_components,
1087                retained_baseline_bytes,
1088            )?;
1089        }
1090
1091        let bit_depth = decoded_image
1092            .decoded_components
1093            .first()
1094            .ok_or(DecodingError::CodeBlockDecodeFailure)?
1095            .bit_depth;
1096        convert_color_space(&mut decoded_image, bit_depth)?;
1097        Ok(decoded_image)
1098    }
1099}
1100
1101impl PreparedRegionDecoder<'_, '_, '_> {
1102    /// Decode one source-coordinate region using the retained tile graph.
1103    ///
1104    /// # Errors
1105    ///
1106    /// Returns an error when the region, component precision, or codestream is invalid.
1107    pub fn decode_region_components(
1108        &mut self,
1109        roi: (u32, u32, u32, u32),
1110    ) -> Result<DecodedComponents<'_>> {
1111        self.decode_region_component_planes(roi, false)
1112    }
1113
1114    /// Rounded-for-integer-output counterpart of
1115    /// [`Self::decode_region_components`]; see
1116    /// [`Image::decode_components_for_integer_output_with_context`].
1117    ///
1118    /// # Errors
1119    ///
1120    /// Returns an error when the region, component precision, or codestream is invalid.
1121    #[doc(hidden)]
1122    pub fn decode_region_components_for_integer_output(
1123        &mut self,
1124        roi: (u32, u32, u32, u32),
1125    ) -> Result<DecodedComponents<'_>> {
1126        self.decode_region_component_planes(roi, true)
1127    }
1128
1129    fn decode_region_component_planes(
1130        &mut self,
1131        roi: (u32, u32, u32, u32),
1132        round_irreversible_output: bool,
1133    ) -> Result<DecodedComponents<'_>> {
1134        self.image.decode_prepared_region_component_planes(
1135            &mut self.tiles,
1136            self.retained_image_bytes,
1137            self.retained_session_bytes,
1138            roi,
1139            round_irreversible_output,
1140            self.decoder_context,
1141        )
1142    }
1143}
1144
1145impl<'a> Image<'a> {
1146    fn decode_prepared_region_component_planes<'ctx>(
1147        &self,
1148        tiles: &mut j2c::ParsedTiles<'a>,
1149        retained_image_bytes: usize,
1150        retained_session_bytes: usize,
1151        roi: (u32, u32, u32, u32),
1152        round_irreversible_output: bool,
1153        decoder_context: &'ctx mut DecoderContext<'a>,
1154    ) -> Result<DecodedComponents<'ctx>> {
1155        validate_roi((self.width(), self.height()), roi)?;
1156        self.validate_component_plane_precision()?;
1157        #[cfg(feature = "std")]
1158        tiles.set_packet_directory(None);
1159        #[cfg(feature = "std")]
1160        let mut directory = self.region_packet_directory(
1161            retained_session_bytes,
1162            decoder_context,
1163            self.permits_line_region_idwt(),
1164            Some(tiles),
1165        );
1166        #[cfg(feature = "std")]
1167        let extra_bytes = directory
1168            .as_deref()
1169            .map_or(0, packet_directory::PacketDirectoryCache::directory_bytes);
1170        #[cfg(not(feature = "std"))]
1171        let extra_bytes = 0;
1172        #[cfg(feature = "std")]
1173        tiles.set_packet_directory(directory.clone());
1174        decoder_context.set_output_region(Some(roi));
1175        decoder_context.set_round_irreversible_output(round_irreversible_output);
1176        decoder_context.permit_line_region_idwt(self.permits_line_region_idwt());
1177        let decode_result = allocation::combine_retained_bytes(retained_image_bytes, extra_bytes)
1178            .and_then(|source_bytes| {
1179                let session_bytes =
1180                    allocation::combine_retained_bytes(retained_session_bytes, extra_bytes)?;
1181                j2c::decode_preparsed(&self.header, source_bytes, tiles, decoder_context)
1182                    .map(|()| session_bytes)
1183            });
1184        #[cfg(feature = "std")]
1185        let decode_result = if decode_result.as_ref().is_err_and(j2c::is_capacity_error) {
1186            tiles.set_packet_directory(None);
1187            if directory
1188                .as_ref()
1189                .is_some_and(|directory| self.packet_cache.evict_if_unshared(directory))
1190            {
1191                drop(directory.take());
1192                j2c::decode_preparsed(&self.header, retained_image_bytes, tiles, decoder_context)
1193                    .map(|()| retained_session_bytes)
1194            } else {
1195                decode_result
1196            }
1197        } else {
1198            decode_result
1199        };
1200        decoder_context.set_output_region(None);
1201        decoder_context.set_round_irreversible_output(false);
1202        decoder_context.permit_line_region_idwt(false);
1203        #[cfg(feature = "std")]
1204        {
1205            tiles.set_packet_directory(None);
1206            drop(directory);
1207        }
1208        let charged_session_bytes = decode_result?;
1209        let (_x, _y, width, height) = roi;
1210        let decoded_image = self.finish_decoded_image(
1211            &mut decoder_context.tile_decode_context.channel_data,
1212            charged_session_bytes,
1213        )?;
1214        let DecodedImage {
1215            decoded_components,
1216            boxes: _,
1217        } = decoded_image;
1218        self.try_borrow_component_planes_with_retained_baseline(
1219            decoded_components.as_slice(),
1220            decoded_components.capacity(),
1221            (width, height),
1222            retained_session_bytes,
1223        )
1224    }
1225}