image_qoi/
decoder.rs

1use std::io::{BufReader, Read};
2
3use image::{ImageDecoder, ImageError, ImageResult, Progress};
4
5use crate::{QoiHeader, QoiReader};
6
7/// An [`ImageDecoder`] for the [Quite Ok Image Format](https://qoiformat.org).
8///
9/// ```
10/// # use std::fs::File;
11/// use image::DynamicImage;
12/// use image_qoi::QoiDecoder;
13///
14/// # fn main() -> image::ImageResult<()> {
15/// let file = File::open("qoi_test_images/dice.qoi")?;
16/// let decoder = QoiDecoder::new(file)?;
17/// let image = DynamicImage::from_decoder(decoder)?;
18/// # Ok(())
19/// # }
20/// ```
21pub struct QoiDecoder<R> {
22    header: QoiHeader,
23    buffer: BufReader<R>,
24}
25
26impl<R: Read> QoiDecoder<R> {
27    pub fn new(read: R) -> ImageResult<Self> {
28        let mut buffer = BufReader::new(read);
29        let mut header_bytes = [0; 14];
30        buffer.read_exact(&mut header_bytes)?;
31        let header = QoiHeader::try_from(&header_bytes[..]).map_err(ImageError::Decoding)?;
32        Ok(Self { header, buffer })
33    }
34}
35
36impl<'a, R: Read + 'a> ImageDecoder<'a> for QoiDecoder<R> {
37    type Reader = QoiReader<R>;
38
39    fn dimensions(&self) -> (u32, u32) {
40        (self.header.width, self.header.height)
41    }
42
43    fn color_type(&self) -> image::ColorType {
44        if self.header.is_rgba() {
45            image::ColorType::Rgba8
46        } else {
47            image::ColorType::Rgb8
48        }
49    }
50
51    fn into_reader(self) -> ImageResult<Self::Reader> {
52        Ok(QoiReader::new(self.header, self.buffer))
53    }
54
55    fn scanline_bytes(&self) -> u64 {
56        self.color_type().bytes_per_pixel() as u64
57    }
58
59    fn read_image_with_progress<F: Fn(Progress)>(
60        self,
61        mut buf: &mut [u8],
62        _progress_callback: F,
63    ) -> ImageResult<()> {
64        let total_bytes = self.total_bytes() as usize;
65        assert_eq!(buf.len(), total_bytes);
66
67        let mut reader = self.into_reader()?;
68
69        while !buf.is_empty() {
70            let pixel = reader.load_next_pixel()?;
71            for _ in 0..(pixel.count / pixel.chans) {
72                buf[..pixel.chans].copy_from_slice(&pixel.bytes[..pixel.chans]);
73                buf = &mut buf[pixel.chans..]
74            }
75        }
76
77        Ok(())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use std::{fs::File, path::PathBuf};
84
85    use image::{codecs::png::PngDecoder, DynamicImage};
86    use test_case::test_case;
87
88    use crate::QoiDecoder;
89
90    #[test_case("dice")]
91    #[test_case("kodim10")]
92    #[test_case("kodim23")]
93    #[test_case("qoi_logo")]
94    #[test_case("testcard_rgba")]
95    #[test_case("testcard")]
96    #[test_case("wikipedia_008")]
97    fn validate(file: &str) {
98        let base = PathBuf::from("qoi_test_images");
99
100        let png = base.join(file).with_extension("png");
101        let png = File::open(png).unwrap();
102        let png = PngDecoder::new(png).unwrap();
103        let png = DynamicImage::from_decoder(png).unwrap().into_rgba8();
104
105        let qoi = base.join(file).with_extension("qoi");
106        let qoi = File::open(qoi).unwrap();
107        let qoi = QoiDecoder::new(qoi).unwrap();
108        let qoi = DynamicImage::from_decoder(qoi).unwrap().into_rgba8();
109
110        assert_eq!(qoi, png);
111    }
112}