1 | //! Decoding and encoding of QOI images |
2 | |
3 | use crate::error::{DecodingError, EncodingError}; |
4 | use crate::{ |
5 | ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult, |
6 | }; |
7 | use std::io::{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<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 | |
47 | fn decoding_error(error: qoi::Error) -> ImageError { |
48 | ImageError::Decoding(DecodingError::new(format:ImageFormat::Qoi.into(), err:error)) |
49 | } |
50 | |
51 | fn encoding_error(error: qoi::Error) -> ImageError { |
52 | ImageError::Encoding(EncodingError::new(format:ImageFormat::Qoi.into(), err:error)) |
53 | } |
54 | |
55 | /// QOI encoder |
56 | pub struct QoiEncoder<W> { |
57 | writer: W, |
58 | } |
59 | |
60 | impl<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 | |
67 | impl<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)] |
106 | mod 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 | |