Skip to main content

zune_image/codecs/
jpeg.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#![cfg_attr(feature = "docs", doc(cfg(feature = "jpeg")))]
9#![cfg(feature = "jpeg")]
10//! Jpeg decoding and encoding support
11//!
12//! The decoder uses a delegate library [`zune-jpeg`](zune_jpeg)
13//! for decoding and [`jpeg-encoder`](jpeg_encoder) for encoding
14//!
15//! The decoder and encoder both support metadata extraction and saving.
16//!
17use jpeg_encoder::{ColorType, EncodingError, JfifWrite};
18use zune_core::bit_depth::BitDepth;
19use zune_core::bytestream::{ZByteIoError, ZByteReaderTrait, ZByteWriterTrait, ZWriter};
20use zune_core::colorspace::ColorSpace;
21use zune_core::log::warn;
22use zune_core::options::EncoderOptions;
23use zune_jpeg::errors::DecodeErrors;
24pub use zune_jpeg::{ImageInfo, JpegDecoder};
25
26use crate::codecs::{create_options_for_encoder, ImageFormat};
27use crate::errors::{ImageErrors, ImgEncodeErrors};
28use crate::image::Image;
29use crate::metadata::ImageMetadata;
30use crate::traits::{DecodeInto, DecoderTrait, EncoderTrait};
31
32struct TempVt<'a, T: ZByteWriterTrait> {
33    inner: &'a mut ZWriter<T>
34}
35impl<'a, T: ZByteWriterTrait> JfifWrite for TempVt<'a, T> {
36    fn write_all(&mut self, buf: &[u8]) -> Result<(), EncodingError> {
37        self.inner.write_all(buf).map_err(|r| match r {
38            ZByteIoError::StdIoError(e) => EncodingError::IoError(e),
39            r => EncodingError::Write(format!("{:?}", r))
40        })
41    }
42}
43impl<T: ZByteReaderTrait> DecoderTrait for zune_jpeg::JpegDecoder<T> {
44    fn decode(&mut self) -> Result<Image, crate::errors::ImageErrors> {
45        let metadata = self.read_headers()?.unwrap();
46
47        let pixels = self
48            .decode()
49            .map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
50
51        let colorspace = self.output_colorspace().unwrap();
52        let (width, height) = self.dimensions().unwrap();
53
54        let mut image = Image::from_u8(&pixels, width, height, colorspace);
55        image.metadata = metadata;
56        image.metadata.colorspace = self.output_colorspace().unwrap();
57        Ok(image)
58    }
59
60    fn dimensions(&self) -> Option<(usize, usize)> {
61        self.dimensions().map(|dims| (dims.0, dims.1))
62    }
63
64    fn out_colorspace(&self) -> ColorSpace {
65        self.output_colorspace().unwrap()
66    }
67
68    fn name(&self) -> &'static str {
69        "JPEG decoder"
70    }
71
72    fn read_headers(&mut self) -> Result<Option<ImageMetadata>, crate::errors::ImageErrors> {
73        self.decode_headers()
74            .map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
75
76        let (width, height) = self.dimensions().unwrap();
77
78        let mut metadata = ImageMetadata {
79            format: Some(ImageFormat::JPEG),
80            colorspace: self.input_colorspace().unwrap(),
81            depth: BitDepth::Eight,
82            width: width,
83            height: height,
84            ..Default::default()
85        };
86        #[cfg(feature = "metadata")]
87        {
88            // see if we have an exif chunk
89            if let Some(exif) = self.exif() {
90                metadata.parse_raw_exif(exif)
91            }
92        }
93        if let Some(icc) = self.icc_profile() {
94            metadata.set_icc_chunk(icc);
95        }
96
97        Ok(Some(metadata))
98    }
99}
100
101impl From<zune_jpeg::errors::DecodeErrors> for ImageErrors {
102    fn from(from: zune_jpeg::errors::DecodeErrors) -> Self {
103        let err = format!("jpg: {from:?}");
104
105        ImageErrors::ImageDecodeErrors(err)
106    }
107}
108// Okay I just need to really appreciate jpeg-encoder crate
109// it's well written and covers quite a lot of things really
110// well, so thank you Volker Ströbel
111
112/// A simple JPEG encoder
113#[derive(Copy, Clone, Default)]
114pub struct JpegEncoder {
115    options: Option<EncoderOptions>
116}
117
118impl JpegEncoder {
119    /// Create a new encoder with default options
120    pub fn new() -> JpegEncoder {
121        JpegEncoder::default()
122    }
123    /// Create a new encoder with custom options
124    pub fn new_with_options(options: EncoderOptions) -> JpegEncoder {
125        JpegEncoder {
126            options: Some(options)
127        }
128    }
129}
130
131impl EncoderTrait for JpegEncoder {
132    fn name(&self) -> &'static str {
133        "jpeg-encoder(vstroebel)"
134    }
135
136    fn encode_inner<T: ZByteWriterTrait>(
137        &mut self, image: &Image, sink: T
138    ) -> Result<usize, ImageErrors> {
139        assert_eq!(
140            image.depth(),
141            BitDepth::Eight,
142            "Unsupported bit depth{:?}",
143            image.depth()
144        );
145        let pixels = &image.flatten_frames::<u8>()[0];
146
147        if let Some(colorspace) = match_colorspace_to_colortype(image.colorspace()) {
148            let max_dims = usize::from(u16::MAX);
149
150            let (width, height) = image.dimensions();
151
152            // check dimensions
153            if (width > max_dims) || (height > max_dims) {
154                let msg = format!(
155                    "Too large image dimensions {} x {}, maximum is {} x {}",
156                    width, height, max_dims, max_dims
157                );
158                return Err(ImgEncodeErrors::ImageEncodeErrors(msg).into());
159            }
160            let mut writer = ZWriter::new(sink);
161            let temp_c = TempVt { inner: &mut writer };
162            // create space for our encoder
163
164            let options = create_options_for_encoder(self.options, image);
165
166            // create encoder finally
167            // vec<u8> supports write so we use that as our encoder
168            let mut encoder = jpeg_encoder::Encoder::new(temp_c, options.quality());
169
170            // add options
171            encoder.set_progressive(options.jpeg_encode_progressive());
172            encoder.set_optimized_huffman_tables(options.jpeg_optimized_huffman_tables());
173
174            #[cfg(feature = "metadata")]
175            {
176                use exif::experimental::Writer;
177
178                if options.strip_metadata() {
179                    // explicit :)
180                } else if let Some(metadata) = &image.metadata.exif {
181                    let mut writer = Writer::new();
182                    // write first tags for exif
183                    let mut buf = std::io::Cursor::new(b"Exif\x00\x00".to_vec());
184                    // set buffer position to be bytes written, to ensure we don't overwrite anything
185                    buf.set_position(6);
186
187                    for metadatum in metadata {
188                        writer.push_field(metadatum);
189                    }
190                    let result = writer.write(&mut buf, false);
191                    if result.is_ok() {
192                        // add the exif tag to APP1 segment
193                        encoder.add_app_segment(1, buf.into_inner())?;
194                    } else {
195                        warn!("Writing exif failed {:?}", result);
196                    }
197                }
198            }
199
200            encoder.encode(pixels, width as u16, height as u16, colorspace)?;
201
202            Ok(writer.bytes_written())
203        } else {
204            Err(ImgEncodeErrors::UnsupportedColorspace(
205                image.colorspace(),
206                self.supported_colorspaces()
207            )
208            .into())
209        }
210    }
211
212    fn supported_colorspaces(&self) -> &'static [ColorSpace] {
213        // should match with the
214        // jpeg-encoder crate
215        &[
216            ColorSpace::Luma,
217            ColorSpace::RGB,
218            ColorSpace::RGBA,
219            ColorSpace::YCbCr,
220            ColorSpace::YCCK,
221            ColorSpace::CMYK
222        ]
223    }
224
225    fn format(&self) -> ImageFormat {
226        ImageFormat::JPEG
227    }
228
229    fn supported_bit_depth(&self) -> &'static [BitDepth] {
230        &[BitDepth::Eight]
231    }
232
233    fn default_depth(&self, _: BitDepth) -> BitDepth {
234        BitDepth::Eight
235    }
236
237    fn set_options(&mut self, options: EncoderOptions) {
238        self.options = Some(options)
239    }
240}
241
242/// Match the library colorspace to jpeg color type
243const fn match_colorspace_to_colortype(colorspace: ColorSpace) -> Option<ColorType> {
244    match colorspace {
245        ColorSpace::RGBA => Some(ColorType::Rgba),
246        ColorSpace::RGB => Some(ColorType::Rgb),
247        ColorSpace::YCbCr => Some(ColorType::Ycbcr),
248        ColorSpace::Luma => Some(ColorType::Luma),
249        ColorSpace::YCCK => Some(ColorType::Ycck),
250        ColorSpace::CMYK => Some(ColorType::Cmyk),
251        _ => None
252    }
253}
254
255impl From<EncodingError> for ImageErrors {
256    fn from(value: EncodingError) -> Self {
257        ImageErrors::EncodeErrors(ImgEncodeErrors::Generic(value.to_string()))
258    }
259}
260
261impl<T> DecodeInto for JpegDecoder<T>
262where
263    T: ZByteReaderTrait
264{
265    type BufferType = u8;
266
267    fn decode_into(&mut self, buffer: &mut [u8]) -> Result<(), ImageErrors> {
268        self.decode_into(buffer)
269            .map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
270
271        Ok(())
272    }
273
274    fn decode_output_buffer_size(&mut self) -> Result<usize, ImageErrors> {
275        self.decode_headers()
276            .map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
277
278        // unwrap is okay because we successfully decoded image headers
279        Ok(self.output_buffer_size().unwrap())
280    }
281}