image/codecs/
qoi.rs

1//! Decoding and encoding of QOI images
2
3use crate::error::{DecodingError, EncodingError, UnsupportedError, UnsupportedErrorKind};
4use crate::{
5    ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult,
6};
7use std::io::{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<R: Read> ImageDecoder for QoiDecoder<R> {
26    fn dimensions(&self) -> (u32, u32) {
27        (self.decoder.header().width, self.decoder.header().height)
28    }
29
30    fn color_type(&self) -> ColorType {
31        match self.decoder.header().channels {
32            qoi::Channels::Rgb => ColorType::Rgb8,
33            qoi::Channels::Rgba => ColorType::Rgba8,
34        }
35    }
36
37    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
38        self.decoder.decode_to_buf(buf).map_err(decoding_error)?;
39        Ok(())
40    }
41
42    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
43        (*self).read_image(buf)
44    }
45}
46
47fn decoding_error(error: qoi::Error) -> ImageError {
48    ImageError::Decoding(DecodingError::new(ImageFormat::Qoi.into(), error))
49}
50
51fn encoding_error(error: qoi::Error) -> ImageError {
52    ImageError::Encoding(EncodingError::new(ImageFormat::Qoi.into(), error))
53}
54
55/// QOI encoder
56pub struct QoiEncoder<W> {
57    writer: W,
58}
59
60impl<W: Write> QoiEncoder<W> {
61    /// Creates a new encoder that writes its output to ```writer```
62    pub fn new(writer: W) -> Self {
63        Self { writer }
64    }
65}
66
67impl<W: Write> ImageEncoder for QoiEncoder<W> {
68    #[track_caller]
69    fn write_image(
70        mut self,
71        buf: &[u8],
72        width: u32,
73        height: u32,
74        color_type: ExtendedColorType,
75    ) -> ImageResult<()> {
76        if !matches!(
77            color_type,
78            ExtendedColorType::Rgba8 | ExtendedColorType::Rgb8
79        ) {
80            return Err(ImageError::Unsupported(
81                UnsupportedError::from_format_and_kind(
82                    ImageFormat::Qoi.into(),
83                    UnsupportedErrorKind::Color(color_type),
84                ),
85            ));
86        }
87
88        let expected_buffer_len = color_type.buffer_size(width, height);
89        assert_eq!(
90            expected_buffer_len,
91            buf.len() as u64,
92            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
93            buf.len(),
94        );
95
96        // Encode data in QOI
97        let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?;
98
99        // Write data to buffer
100        self.writer.write_all(&data[..])?;
101        self.writer.flush()?;
102
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use std::fs::File;
111
112    #[test]
113    fn decode_test_image() {
114        let decoder = QoiDecoder::new(File::open("tests/images/qoi/basic-test.qoi").unwrap())
115            .expect("Unable to read QOI file");
116
117        assert_eq!((5, 5), decoder.dimensions());
118        assert_eq!(ColorType::Rgba8, decoder.color_type());
119    }
120}