1use std::io::{BufReader, Read};
2
3use image::{ImageDecoder, ImageError, ImageResult, Progress};
4
5use crate::{QoiHeader, QoiReader};
6
7pub 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}