Skip to main content

zune_image/codecs/
jpeg_xl.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8//! JPEG-XL decoding and encoding support
9//! This uses the delegate library [`zune-jpeg-xl`](zune_jpegxl)
10//! for encoding and  [`jxl-oxide`](jxl_oxide) for decoding images
11#![cfg_attr(feature = "docs", doc(cfg(feature = "jpeg-xl")))]
12#![cfg(feature = "jpeg-xl")]
13//! A simple jxl lossless encoder
14//!
15//! The encoder supports simple lossless image
16//! (modular, no var-dct) with support for 8 bit and
17//! 16 bit images with no palette support
18//!
19use 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/// A simple JXL encoder that ties the bridge between
42/// Image struct and the [zune_jpegxl::SimpleJxlEncoder](zune_jpegxl::JxlSimpleEncoder)
43#[derive(Default, Copy, Clone)]
44pub struct JxlEncoder {
45    options: Option<EncoderOptions>
46}
47
48impl JxlEncoder {
49    /// Create a new encoder with default options
50    ///
51    /// Default options include 4 threads for encoding,and an effort
52    /// od 4
53    pub fn new() -> JxlEncoder {
54        JxlEncoder::default()
55    }
56    /// Create new encoder with custom options
57    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        // by now headers have been decoded, so we can fetch these
142        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        // check dimensions if bigger than supported
157        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            // get the images
187            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                // copy the channel as plannar
198                let c = chan.reinterpret_as_mut()?;
199                c.copy_from_slice(channel.buf());
200                // then store it in frame_v
201                frame_v.push(chan);
202            }
203            let frame = Frame::new(frame_v);
204            total_frames.push(frame);
205        }
206        // then create a new image
207        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}