1 | //! Decoding and encoding of QOI images |
2 | |
3 | use crate::{ |
4 | error::{DecodingError, EncodingError}, |
5 | ColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, |
6 | }; |
7 | use std::io::{Cursor, Read, Write}; |
8 | |
9 | /// QOI decoder |
10 | pub struct QoiDecoder<R> { |
11 | decoder: qoi::Decoder<R>, |
12 | } |
13 | |
14 | impl<R> QoiDecoder<R> |
15 | where |
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 | |
25 | impl<'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 | |
45 | fn decoding_error(error: qoi::Error) -> ImageError { |
46 | ImageError::Decoding(DecodingError::new(format:ImageFormat::Qoi.into(), err:error)) |
47 | } |
48 | |
49 | fn encoding_error(error: qoi::Error) -> ImageError { |
50 | ImageError::Encoding(EncodingError::new(format:ImageFormat::Qoi.into(), err:error)) |
51 | } |
52 | |
53 | /// QOI encoder |
54 | pub struct QoiEncoder<W> { |
55 | writer: W, |
56 | } |
57 | |
58 | impl<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 | |
65 | impl<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)] |
102 | mod 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 | |