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: Decoder = qoi::Decoder::from_stream(reader).map_err(op: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: Vec = self.decoder.decode_to_vec().map_err(op:decoding_error)?;
41 Ok(Cursor::new(inner:buffer))
42 }
43}
44
45fn decoding_error(error: qoi::Error) -> ImageError {
46 ImageError::Decoding(DecodingError::new(format:ImageFormat::Qoi.into(), err:error))
47}
48
49fn encoding_error(error: qoi::Error) -> ImageError {
50 ImageError::Encoding(EncodingError::new(format:ImageFormat::Qoi.into(), err: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}
115