zune_image/codecs/
jpeg_xl.rs1#![cfg_attr(feature = "docs", doc(cfg(feature = "jpeg-xl")))]
12#![cfg(feature = "jpeg-xl")]
13use std::io::Read;
20use std::mem::size_of;
21use std::thread::sleep;
22use std::time::Duration;
23
24pub use jxl_oxide;
25use jxl_oxide::{JxlImage, PixelFormat, RenderResult};
26use zune_core::bit_depth::{BitDepth, BitType};
27use zune_core::bytestream::ZByteWriterTrait;
28use zune_core::colorspace::ColorSpace;
29use zune_core::log::trace;
30use zune_core::options::{DecoderOptions, EncoderOptions};
31pub use zune_jpegxl::*;
32
33use crate::channel::Channel;
34use crate::codecs::{create_options_for_encoder, ImageFormat};
35use crate::errors::{ImageErrors, ImgEncodeErrors};
36use crate::frame::Frame;
37use crate::image::Image;
38use crate::metadata::ImageMetadata;
39use crate::traits::{DecoderTrait, EncoderTrait};
40
41#[derive(Default, Copy, Clone)]
44pub struct JxlEncoder {
45 options: Option<EncoderOptions>
46}
47
48impl JxlEncoder {
49 pub fn new() -> JxlEncoder {
54 JxlEncoder::default()
55 }
56 pub fn new_with_options(options: EncoderOptions) -> JxlEncoder {
58 JxlEncoder {
59 options: Some(options)
60 }
61 }
62}
63
64impl EncoderTrait for JxlEncoder {
65 fn name(&self) -> &'static str {
66 "jxl-encoder"
67 }
68
69 fn encode_inner<T: ZByteWriterTrait>(
70 &mut self, image: &Image, sink: T
71 ) -> Result<usize, ImageErrors> {
72 let options = create_options_for_encoder(self.options, image);
73
74 let data = &image.to_u8()[0];
75
76 let encoder = JxlSimpleEncoder::new(data, options);
77
78 let data = encoder
79 .encode(sink)
80 .map_err(<JxlEncodeErrors as Into<ImgEncodeErrors>>::into)?;
81
82 Ok(data)
83 }
84
85 fn supported_colorspaces(&self) -> &'static [ColorSpace] {
86 &[
87 ColorSpace::Luma,
88 ColorSpace::LumaA,
89 ColorSpace::RGBA,
90 ColorSpace::RGB
91 ]
92 }
93
94 fn format(&self) -> ImageFormat {
95 ImageFormat::JPEG_XL
96 }
97
98 fn supported_bit_depth(&self) -> &'static [BitDepth] {
99 &[BitDepth::Eight, BitDepth::Sixteen]
100 }
101
102 fn default_depth(&self, depth: BitDepth) -> BitDepth {
103 match depth {
104 BitDepth::Sixteen | BitDepth::Float32 => BitDepth::Sixteen,
105 _ => BitDepth::Eight
106 }
107 }
108
109 fn set_options(&mut self, options: EncoderOptions) {
110 self.options = Some(options)
111 }
112}
113
114impl From<JxlEncodeErrors> for ImgEncodeErrors {
115 fn from(value: JxlEncodeErrors) -> Self {
116 ImgEncodeErrors::ImageEncodeErrors(format!("{:?}", value))
117 }
118}
119
120pub struct JxlDecoder {
121 inner: jxl_oxide::JxlImage,
122 options: DecoderOptions
123}
124
125impl JxlDecoder {
126 pub fn try_new<R: Read>(source: R, options: DecoderOptions) -> Result<JxlDecoder, ImageErrors> {
127 let parser = jxl_oxide::JxlImage::builder()
128 .read(source)
129 .map_err(|x| ImageErrors::ImageDecodeErrors(format!("{:?}", x)))?;
130
131 let decoder = JxlDecoder {
132 inner: parser,
133 options
134 };
135 Ok(decoder)
136 }
137}
138
139impl DecoderTrait for JxlDecoder {
140 fn decode(&mut self) -> Result<Image, ImageErrors> {
141 let metadata = self.read_headers()?;
143 let (w, h) = <JxlDecoder as DecoderTrait>::dimensions(self).unwrap();
144 let color = <JxlDecoder as DecoderTrait>::out_colorspace(self);
145
146 let mut total_frames = vec![];
147
148 if color == ColorSpace::Unknown {
149 return Err(ImageErrors::ImageDecodeErrors(format!(
150 "Encountered unknown/unsupported colorspace {:?}",
151 self.inner.pixel_format()
152 )));
153 }
154 trace!("Image colorspace: {:?}", color);
155 trace!("Image dimensions: ({},{})", w, h);
156 if w > self.options.max_width() {
158 let msg = format!(
159 "Image width {}, greater than max set width {}",
160 w,
161 self.options.max_width()
162 );
163 return Err(ImageErrors::ImageDecodeErrors(msg));
164 }
165 if h > self.options.max_height() {
166 let msg = format!(
167 "Image height {}, greater than max set height {}",
168 h,
169 self.options.max_height()
170 );
171 return Err(ImageErrors::ImageDecodeErrors(msg));
172 }
173
174 let taken = if self.options.jxl_decode_animated() {
175 self.inner.num_loaded_frames()
176 } else {
177 1
178 };
179
180 for frame in 0..taken {
181 let render = self
182 .inner
183 .render_frame(frame)
184 .map_err(|x| ImageErrors::ImageDecodeErrors(format!("{}", x)))?;
185
186 let duration = render.duration();
188
189 let im_plannar = render.image_planar();
190 let mut frame_v = vec![];
191
192 for channel in im_plannar {
193 let mut chan = Channel::new_with_bit_type(
194 channel.width() * channel.height() * size_of::<f32>(),
195 BitType::F32
196 );
197 let c = chan.reinterpret_as_mut()?;
199 c.copy_from_slice(channel.buf());
200 frame_v.push(chan);
202 }
203 let frame = Frame::new(frame_v);
204 total_frames.push(frame);
205 }
206 let mut image = Image::new_frames(total_frames, BitDepth::Float32, w, h, color);
208 if let Some(im_metadata) = metadata {
209 image.metadata = im_metadata;
210 }
211 Ok(image)
212 }
213
214 fn dimensions(&self) -> Option<(usize, usize)> {
215 let (w, h) = (self.inner.width(), self.inner.height());
216 Some((w as usize, h as usize))
217 }
218
219 fn out_colorspace(&self) -> ColorSpace {
220 let format = self.inner.pixel_format();
221 match format {
222 PixelFormat::Gray => ColorSpace::Luma,
223 PixelFormat::Graya => ColorSpace::LumaA,
224 PixelFormat::Rgb => ColorSpace::RGB,
225 PixelFormat::Rgba => ColorSpace::RGBA,
226 PixelFormat::Cmyk => ColorSpace::CMYK,
227 PixelFormat::Cmyka => ColorSpace::Unknown
228 }
229 }
230
231 fn name(&self) -> &'static str {
232 "jxl-decoder (tirr-c)"
233 }
234
235 fn read_headers(&mut self) -> Result<Option<ImageMetadata>, ImageErrors> {
236 let (w, h) = <JxlDecoder as DecoderTrait>::dimensions(self).unwrap();
237 let color = <JxlDecoder as DecoderTrait>::out_colorspace(self);
238
239 let (width, height) = self.dimensions().unwrap();
240
241 let icc = self.inner.rendered_icc();
242 let metadata = ImageMetadata {
243 format: Some(ImageFormat::JPEG_XL),
244 colorspace: color,
245 depth: BitDepth::Float32,
246 width: width,
247 height: height,
248 icc_chunk: Some(icc),
249 ..Default::default()
250 };
251
252 Ok(Some(metadata))
253 }
254}