Skip to main content

zenjxl_decoder/api/inner/
mod.rs

1// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6#[cfg(test)]
7use crate::api::FrameCallback;
8use crate::{
9    api::JxlFrameHeader,
10    error::{Error, Result},
11};
12
13use super::{JxlBasicInfo, JxlColorProfile, JxlDecoderOptions, JxlPixelFormat, VardctQuantizer};
14use crate::container::frame_index::FrameIndexBox;
15use crate::container::gain_map::GainMapBundle;
16use box_parser::BoxParser;
17use codestream_parser::CodestreamParser;
18
19mod box_parser;
20mod codestream_parser;
21mod process;
22
23/// Low-level, less-type-safe API.
24pub struct JxlDecoderInner {
25    options: JxlDecoderOptions,
26    box_parser: BoxParser,
27    codestream_parser: CodestreamParser,
28}
29
30impl JxlDecoderInner {
31    /// Creates a new decoder with the given options and, optionally, CMS.
32    pub fn new(options: JxlDecoderOptions) -> Self {
33        JxlDecoderInner {
34            options,
35            box_parser: BoxParser::new(),
36            codestream_parser: CodestreamParser::new(),
37        }
38    }
39
40    #[cfg(test)]
41    pub fn set_frame_callback(&mut self, callback: Box<FrameCallback>) {
42        self.codestream_parser.frame_callback = Some(callback);
43    }
44
45    #[cfg(test)]
46    pub fn decoded_frames(&self) -> usize {
47        self.codestream_parser.decoded_frames
48    }
49
50    /// Test-only accessor for the active [`crate::frame::DecoderState`].
51    ///
52    /// Used by regression tests that need to verify that per-run options
53    /// (limits, memory_tracker, parallel, high_precision, premultiply_output,
54    /// embedded_color_profile) survive the preview-frame recovery path in
55    /// `codestream_parser::sections::handle_frame_finalized`.
56    ///
57    /// Returns the parser-owned decoder state if it has not yet been moved
58    /// into a Frame, otherwise the in-progress frame's decoder state.
59    #[cfg(test)]
60    pub(crate) fn decoder_state_for_test(&self) -> Option<&crate::frame::DecoderState> {
61        if let Some(state) = self.codestream_parser.decoder_state.as_ref() {
62            Some(state)
63        } else {
64            self.codestream_parser
65                .frame
66                .as_ref()
67                .map(|f| &f.decoder_state)
68        }
69    }
70
71    /// Obtains the image's basic information, if available.
72    ///
73    /// Keep this aligned with typed `WithImageInfo` transitions: image info is
74    /// not observable until the embedded color profile has been parsed. This
75    /// mirrors the fix from upstream jxl-rs 28ddaeb (PR #745) so that callers
76    /// driving `set_pixel_format` off the partial info cannot race the profile
77    /// parse and observe an early-format-selection state that differs from
78    /// what the typed `WithImageInfo` transition would produce.
79    pub fn basic_info(&self) -> Option<&JxlBasicInfo> {
80        self.codestream_parser.embedded_color_profile.as_ref()?;
81        self.codestream_parser.basic_info.as_ref()
82    }
83
84    /// Retrieves the file's color profile, if available.
85    pub fn embedded_color_profile(&self) -> Option<&JxlColorProfile> {
86        self.codestream_parser.embedded_color_profile.as_ref()
87    }
88
89    /// Returns the first regular VarDCT frame's quantizer, if this is a lossy
90    /// VarDCT image whose first frame's `LfGlobal` section has been decoded.
91    ///
92    /// `None` for Modular (lossless) images, or before the first regular frame
93    /// has been parsed (e.g. right after image-info, which `read_header` stops
94    /// at). Advance one frame (e.g. `skip_frame`) to populate it from a probe.
95    pub fn vardct_quantizer(&self) -> Option<VardctQuantizer> {
96        let (global_scale, quant_lf) = self.codestream_parser.first_vardct_quantizer?;
97        Some(VardctQuantizer {
98            global_scale,
99            quant_lf,
100        })
101    }
102
103    /// Retrieves the current output color profile, if available.
104    pub fn output_color_profile(&self) -> Option<&JxlColorProfile> {
105        self.codestream_parser.output_color_profile.as_ref()
106    }
107
108    /// Specifies the preferred color profile to be used for outputting data.
109    /// Same semantics as JxlDecoderSetOutputColorProfile.
110    pub fn set_output_color_profile(&mut self, profile: JxlColorProfile) -> Result<()> {
111        if let (JxlColorProfile::Icc(_), None) = (&profile, &self.options.cms) {
112            return Err(Error::ICCOutputNoCMS);
113        }
114        self.codestream_parser.output_color_profile = Some(profile);
115        self.codestream_parser.output_color_profile_set_by_user = true;
116        Ok(())
117    }
118
119    pub fn current_pixel_format(&self) -> Option<&JxlPixelFormat> {
120        self.codestream_parser.pixel_format.as_ref()
121    }
122
123    pub fn set_pixel_format(&mut self, pixel_format: JxlPixelFormat) {
124        // TODO(veluca): return an error if we are asking for both planar and
125        // interleaved-in-color alpha.
126        self.codestream_parser.pixel_format = Some(pixel_format);
127        self.codestream_parser.update_default_output_color_profile();
128    }
129
130    pub fn frame_header(&self) -> Option<JxlFrameHeader> {
131        let frame_header = self.codestream_parser.frame.as_ref()?.header();
132        // The render pipeline always adds ExtendToImageDimensionsStage which extends
133        // frames to the full image size. So the output size is always the image size,
134        // not the frame's upsampled size.
135        let size = self.codestream_parser.basic_info.as_ref()?.size;
136        Some(JxlFrameHeader {
137            name: frame_header.name.clone(),
138            duration: self
139                .codestream_parser
140                .animation
141                .as_ref()
142                .map(|anim| frame_header.duration(anim)),
143            size,
144        })
145    }
146
147    /// Number of passes we have full data for.
148    /// Returns the minimum number of passes completed across all groups.
149    pub fn num_completed_passes(&self) -> Option<usize> {
150        Some(self.codestream_parser.num_completed_passes())
151    }
152
153    /// Fully resets the decoder to its initial state.
154    ///
155    /// This clears all state including pixel_format. For animation loop playback,
156    /// consider using [`rewind`](Self::rewind) instead which preserves pixel_format.
157    ///
158    /// After calling this, the caller should provide input from the beginning of the file.
159    pub fn reset(&mut self) {
160        // TODO(veluca): keep track of frame offsets for skipping.
161        self.box_parser = BoxParser::new();
162        self.codestream_parser = CodestreamParser::new();
163    }
164
165    /// Rewinds for animation loop replay, keeping pixel_format setting.
166    ///
167    /// This resets the decoder but preserves the pixel_format configuration,
168    /// so the caller doesn't need to re-set it after rewinding.
169    ///
170    /// After calling this, the caller should provide input from the beginning of the file.
171    /// Headers will be re-parsed, then frames can be decoded again.
172    ///
173    /// Returns `true` if pixel_format was preserved, `false` if none was set.
174    pub fn rewind(&mut self) -> bool {
175        self.box_parser = BoxParser::new();
176        self.codestream_parser.rewind().is_some()
177    }
178
179    pub fn has_more_frames(&self) -> bool {
180        self.codestream_parser.has_more_frames
181    }
182
183    /// Returns the reconstructed JPEG bytes if the file contained a JBRD box.
184    ///
185    /// The reconstruction `JpegData` is built when the frame decodes, but the
186    /// EXIF/XMP APPn payloads (lifted into container boxes that follow the
187    /// codestream) and the final byte serialization are produced here, once the
188    /// whole container has been parsed.
189    #[cfg(feature = "jpeg")]
190    pub fn take_jpeg_reconstruction(&mut self) -> Option<Vec<u8>> {
191        let mut jpeg = self.codestream_parser.jpeg_recon.take()?;
192        // The original ICC profile (if any) was lifted into the codestream color
193        // encoding; recover it to re-chunk the ICC_PROFILE APP2 markers.
194        let icc = match self.codestream_parser.embedded_color_profile.as_ref() {
195            Some(JxlColorProfile::Icc(bytes)) => Some(bytes.clone()),
196            _ => None,
197        };
198        crate::jpeg::fill_metadata(
199            &mut jpeg,
200            self.box_parser.exif.clone(),
201            self.box_parser.xmp.clone(),
202            icc.as_deref(),
203        );
204        crate::jpeg::write_jpeg(&jpeg).ok()
205    }
206
207    /// Returns the parsed frame index box, if the file contained one.
208    pub fn frame_index(&self) -> Option<&FrameIndexBox> {
209        self.box_parser.frame_index.as_ref()
210    }
211
212    /// Returns a reference to the parsed gain map bundle, if the file contained one.
213    pub fn gain_map(&self) -> Option<&GainMapBundle> {
214        self.box_parser.gain_map.as_ref()
215    }
216
217    /// Takes the parsed gain map bundle, if the file contained one.
218    /// After calling this, `gain_map()` will return `None`.
219    pub fn take_gain_map(&mut self) -> Option<GainMapBundle> {
220        self.box_parser.gain_map.take()
221    }
222
223    /// Returns the raw EXIF data from the `Exif` container box, if present.
224    ///
225    /// The 4-byte TIFF header offset prefix has been stripped; this returns
226    /// the raw EXIF/TIFF bytes starting with the byte-order marker (`II` or `MM`).
227    /// Returns `None` for bare codestreams or files without an `Exif` box.
228    pub fn exif(&self) -> Option<&[u8]> {
229        self.box_parser.exif.as_deref()
230    }
231
232    /// Takes the EXIF data, leaving `None` in its place.
233    pub fn take_exif(&mut self) -> Option<Vec<u8>> {
234        self.box_parser.exif.take()
235    }
236
237    /// Returns the raw XMP data from the `xml ` container box, if present.
238    ///
239    /// Returns `None` for bare codestreams or files without an `xml ` box.
240    pub fn xmp(&self) -> Option<&[u8]> {
241        self.box_parser.xmp.as_deref()
242    }
243
244    /// Takes the XMP data, leaving `None` in its place.
245    pub fn take_xmp(&mut self) -> Option<Vec<u8>> {
246        self.box_parser.xmp.take()
247    }
248
249    #[cfg(test)]
250    pub(crate) fn set_use_simple_pipeline(&mut self, u: bool) {
251        self.codestream_parser.set_use_simple_pipeline(u);
252    }
253}