image/codecs/
qoi.rs

1//! Decoding and encoding of QOI images
2
3use crate::{
4    error::{DecodingError, EncodingError},
5    ColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult,
6};
7use std::io::{Cursor, Read, Write};
8
9/// QOI decoder
10pub struct QoiDecoder<R> {
11    decoder: qoi::Decoder<R>,
12}
13
14impl<R> QoiDecoder<R>
15where
16    R: Read,
17{
18    /// Creates a new decoder that decodes from the stream ```reader```
19    pub fn new(reader: R) -> ImageResult<Self> {
20        let decoder = qoi::Decoder::from_stream(reader).map_err(decoding_error)?;
21        Ok(Self { decoder })
22    }
23}
24
25impl<'a, R: Read + 'a> ImageDecoder<'a> for QoiDecoder<R> {
26    type Reader = Cursor<Vec<u8>>;
27
28    fn dimensions(&self) -> (u32, u32) {
29        (self.decoder.header().width, self.decoder.header().height)
30    }
31
32    fn color_type(&self) -> ColorType {
33        match self.decoder.header().channels {
34            qoi::Channels::Rgb => ColorType::Rgb8,
35            qoi::Channels::Rgba => ColorType::Rgba8,
36        }
37    }
38
39    fn into_reader(mut self) -> ImageResult<Self::Reader> {
40        let buffer = self.decoder.decode_to_vec().map_err(decoding_error)?;
41        Ok(Cursor::new(buffer))
42    }
43}
44
45fn decoding_error(error: qoi::Error) -> ImageError {
46    ImageError::Decoding(DecodingError::new(ImageFormat::Qoi.into(), error))
47}
48
49fn encoding_error(error: qoi::Error) -> ImageError {
50    ImageError::Encoding(EncodingError::new(ImageFormat::Qoi.into(), error))
51}
52
53/// QOI encoder
54pub struct QoiEncoder<W> {
55    writer: W,
56}
57
58impl<W: Write> QoiEncoder<W> {
59    /// Creates a new encoder that writes its output to ```writer```
60    pub fn new(writer: W) -> Self {
61        Self { writer }
62    }
63}
64
65impl<W: Write> ImageEncoder for QoiEncoder<W> {
66    #[track_caller]
67    fn write_image(
68        mut self,
69        buf: &[u8],
70        width: u32,
71        height: u32,
72        color_type: ColorType,
73    ) -> ImageResult<()> {
74        if !matches!(color_type, ColorType::Rgba8 | ColorType::Rgb8) {
75            return Err(ImageError::Encoding(EncodingError::new(
76                ImageFormat::Qoi.into(),
77                format!("unsupported color type {color_type:?}. Supported are Rgba8 and Rgb8."),
78            )));
79        }
80
81        let expected_buffer_len =
82            (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64);
83        assert_eq!(
84            expected_buffer_len,
85            buf.len() as u64,
86            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
87            buf.len(),
88        );
89
90        // Encode data in QOI
91        let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?;
92
93        // Write data to buffer
94        self.writer.write_all(&data[..])?;
95        self.writer.flush()?;
96
97        Ok(())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use std::fs::File;
105
106    #[test]
107    fn decode_test_image() {
108        let decoder = QoiDecoder::new(File::open("tests/images/qoi/basic-test.qoi").unwrap())
109            .expect("Unable to read QOI file");
110
111        assert_eq!((5, 5), decoder.dimensions());
112        assert_eq!(ColorType::Rgba8, decoder.color_type());
113    }
114}