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};
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 /// Retrieves the current output color profile, if available.
90 pub fn output_color_profile(&self) -> Option<&JxlColorProfile> {
91 self.codestream_parser.output_color_profile.as_ref()
92 }
93
94 /// Specifies the preferred color profile to be used for outputting data.
95 /// Same semantics as JxlDecoderSetOutputColorProfile.
96 pub fn set_output_color_profile(&mut self, profile: JxlColorProfile) -> Result<()> {
97 if let (JxlColorProfile::Icc(_), None) = (&profile, &self.options.cms) {
98 return Err(Error::ICCOutputNoCMS);
99 }
100 self.codestream_parser.output_color_profile = Some(profile);
101 self.codestream_parser.output_color_profile_set_by_user = true;
102 Ok(())
103 }
104
105 pub fn current_pixel_format(&self) -> Option<&JxlPixelFormat> {
106 self.codestream_parser.pixel_format.as_ref()
107 }
108
109 pub fn set_pixel_format(&mut self, pixel_format: JxlPixelFormat) {
110 // TODO(veluca): return an error if we are asking for both planar and
111 // interleaved-in-color alpha.
112 self.codestream_parser.pixel_format = Some(pixel_format);
113 self.codestream_parser.update_default_output_color_profile();
114 }
115
116 pub fn frame_header(&self) -> Option<JxlFrameHeader> {
117 let frame_header = self.codestream_parser.frame.as_ref()?.header();
118 // The render pipeline always adds ExtendToImageDimensionsStage which extends
119 // frames to the full image size. So the output size is always the image size,
120 // not the frame's upsampled size.
121 let size = self.codestream_parser.basic_info.as_ref()?.size;
122 Some(JxlFrameHeader {
123 name: frame_header.name.clone(),
124 duration: self
125 .codestream_parser
126 .animation
127 .as_ref()
128 .map(|anim| frame_header.duration(anim)),
129 size,
130 })
131 }
132
133 /// Number of passes we have full data for.
134 /// Returns the minimum number of passes completed across all groups.
135 pub fn num_completed_passes(&self) -> Option<usize> {
136 Some(self.codestream_parser.num_completed_passes())
137 }
138
139 /// Fully resets the decoder to its initial state.
140 ///
141 /// This clears all state including pixel_format. For animation loop playback,
142 /// consider using [`rewind`](Self::rewind) instead which preserves pixel_format.
143 ///
144 /// After calling this, the caller should provide input from the beginning of the file.
145 pub fn reset(&mut self) {
146 // TODO(veluca): keep track of frame offsets for skipping.
147 self.box_parser = BoxParser::new();
148 self.codestream_parser = CodestreamParser::new();
149 }
150
151 /// Rewinds for animation loop replay, keeping pixel_format setting.
152 ///
153 /// This resets the decoder but preserves the pixel_format configuration,
154 /// so the caller doesn't need to re-set it after rewinding.
155 ///
156 /// After calling this, the caller should provide input from the beginning of the file.
157 /// Headers will be re-parsed, then frames can be decoded again.
158 ///
159 /// Returns `true` if pixel_format was preserved, `false` if none was set.
160 pub fn rewind(&mut self) -> bool {
161 self.box_parser = BoxParser::new();
162 self.codestream_parser.rewind().is_some()
163 }
164
165 pub fn has_more_frames(&self) -> bool {
166 self.codestream_parser.has_more_frames
167 }
168
169 /// Returns the reconstructed JPEG bytes if the file contained a JBRD box.
170 #[cfg(feature = "jpeg")]
171 pub fn take_jpeg_reconstruction(&mut self) -> Option<Vec<u8>> {
172 self.codestream_parser.jpeg_bytes.take()
173 }
174
175 /// Returns the parsed frame index box, if the file contained one.
176 pub fn frame_index(&self) -> Option<&FrameIndexBox> {
177 self.box_parser.frame_index.as_ref()
178 }
179
180 /// Returns a reference to the parsed gain map bundle, if the file contained one.
181 pub fn gain_map(&self) -> Option<&GainMapBundle> {
182 self.box_parser.gain_map.as_ref()
183 }
184
185 /// Takes the parsed gain map bundle, if the file contained one.
186 /// After calling this, `gain_map()` will return `None`.
187 pub fn take_gain_map(&mut self) -> Option<GainMapBundle> {
188 self.box_parser.gain_map.take()
189 }
190
191 /// Returns the raw EXIF data from the `Exif` container box, if present.
192 ///
193 /// The 4-byte TIFF header offset prefix has been stripped; this returns
194 /// the raw EXIF/TIFF bytes starting with the byte-order marker (`II` or `MM`).
195 /// Returns `None` for bare codestreams or files without an `Exif` box.
196 pub fn exif(&self) -> Option<&[u8]> {
197 self.box_parser.exif.as_deref()
198 }
199
200 /// Takes the EXIF data, leaving `None` in its place.
201 pub fn take_exif(&mut self) -> Option<Vec<u8>> {
202 self.box_parser.exif.take()
203 }
204
205 /// Returns the raw XMP data from the `xml ` container box, if present.
206 ///
207 /// Returns `None` for bare codestreams or files without an `xml ` box.
208 pub fn xmp(&self) -> Option<&[u8]> {
209 self.box_parser.xmp.as_deref()
210 }
211
212 /// Takes the XMP data, leaving `None` in its place.
213 pub fn take_xmp(&mut self) -> Option<Vec<u8>> {
214 self.box_parser.xmp.take()
215 }
216
217 #[cfg(test)]
218 pub(crate) fn set_use_simple_pipeline(&mut self, u: bool) {
219 self.codestream_parser.set_use_simple_pipeline(u);
220 }
221}