1use rapid_qoi::{Colors, Qoi};
2
3use crate::error::{Error, Result};
4use crate::format::Format;
5
6use super::{Codec, EncodeOptions, ImageData};
7
8pub struct QoiCodec;
10
11impl Codec for QoiCodec {
12 fn format(&self) -> Format {
13 Format::Qoi
14 }
15
16 fn decode(&self, data: &[u8]) -> Result<ImageData> {
17 let (header, pixels) =
18 Qoi::decode_alloc(data).map_err(|e| Error::Decode(format!("qoi decode: {e}")))?;
19
20 let width = header.width;
21 let height = header.height;
22
23 let rgba = if header.colors.has_alpha() {
25 pixels
26 } else {
27 pixels
28 .chunks_exact(3)
29 .flat_map(|px| [px[0], px[1], px[2], 255])
30 .collect()
31 };
32
33 Ok(ImageData::new(width, height, rgba))
34 }
35
36 fn encode(&self, image: &ImageData, _options: &EncodeOptions) -> Result<Vec<u8>> {
37 let qoi = Qoi {
38 width: image.width,
39 height: image.height,
40 colors: Colors::SrgbLinA,
41 };
42
43 let encoded = qoi
44 .encode_alloc(&image.data)
45 .map_err(|e| Error::Encode(format!("qoi encode: {e}")))?;
46
47 Ok(encoded)
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 fn create_test_image(width: u32, height: u32) -> ImageData {
56 let size = (width * height * 4) as usize;
57 let mut data = vec![0u8; size];
58 for y in 0..height {
59 for x in 0..width {
60 let i = ((y * width + x) * 4) as usize;
61 data[i] = (x * 255 / width) as u8; data[i + 1] = (y * 255 / height) as u8; data[i + 2] = 128; data[i + 3] = 255; }
66 }
67 ImageData::new(width, height, data)
68 }
69
70 #[test]
71 fn encode_and_decode_roundtrip() {
72 let codec = QoiCodec;
73 let original = create_test_image(64, 48);
74 let options = EncodeOptions { quality: 90 };
75
76 let encoded = codec.encode(&original, &options).expect("encode failed");
77
78 assert!(
80 encoded.len() >= 4,
81 "encoded data too short: {} bytes",
82 encoded.len()
83 );
84 assert_eq!(&encoded[..4], b"qoif", "missing QOI magic bytes");
85
86 let decoded = codec.decode(&encoded).expect("decode failed");
88 assert_eq!(decoded.width, original.width);
89 assert_eq!(decoded.height, original.height);
90 assert_eq!(decoded.data, original.data, "QOI should be lossless");
91 }
92}