1//! Decoding and encoding of QOI images
2
3use crate::error::{DecodingError, EncodingError};
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: Decoder = qoi::Decoder::from_stream(reader).map_err(op: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(op: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(format:ImageFormat::Qoi.into(), err:error))
49}
50
51fn encoding_error(error: qoi::Error) -> ImageError {
52 ImageError::Encoding(EncodingError::new(format:ImageFormat::Qoi.into(), err: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::Encoding(EncodingError::new(
81 ImageFormat::Qoi.into(),
82 format!("unsupported color type {color_type:?}. Supported are Rgba8 and Rgb8."),
83 )));
84 }
85
86 let expected_buffer_len = color_type.buffer_size(width, height);
87 assert_eq!(
88 expected_buffer_len,
89 buf.len() as u64,
90 "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
91 buf.len(),
92 );
93
94 // Encode data in QOI
95 let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?;
96
97 // Write data to buffer
98 self.writer.write_all(&data[..])?;
99 self.writer.flush()?;
100
101 Ok(())
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use std::fs::File;
109
110 #[test]
111 fn decode_test_image() {
112 let decoder = QoiDecoder::new(File::open("tests/images/qoi/basic-test.qoi").unwrap())
113 .expect("Unable to read QOI file");
114
115 assert_eq!((5, 5), decoder.dimensions());
116 assert_eq!(ColorType::Rgba8, decoder.color_type());
117 }
118}
119