Skip to main content

slimg_core/codec/
avif.rs

1use imgref::Img;
2use rgb::RGBA8;
3
4use crate::error::{Error, Result};
5use crate::format::Format;
6
7use super::{Codec, EncodeOptions, ImageData};
8
9/// AVIF codec backed by ravif for encoding and the `image` crate for decoding.
10pub struct AvifCodec;
11
12impl Codec for AvifCodec {
13    fn format(&self) -> Format {
14        Format::Avif
15    }
16
17    fn decode(&self, data: &[u8]) -> Result<ImageData> {
18        let img = image::load_from_memory_with_format(data, image::ImageFormat::Avif)
19            .map_err(|e| Error::Decode(format!("avif decode: {e}")))?;
20
21        let rgba = img.to_rgba8();
22        let width = rgba.width();
23        let height = rgba.height();
24
25        Ok(ImageData::new(width, height, rgba.into_raw()))
26    }
27
28    fn encode(&self, image: &ImageData, options: &EncodeOptions) -> Result<Vec<u8>> {
29        let width = image.width as usize;
30        let height = image.height as usize;
31
32        // Convert raw RGBA bytes to Vec<RGBA8>
33        let pixels: Vec<RGBA8> = image
34            .data
35            .chunks_exact(4)
36            .map(|px| RGBA8::new(px[0], px[1], px[2], px[3]))
37            .collect();
38
39        let buffer = Img::new(pixels.as_slice(), width, height);
40
41        let encoded = ravif::Encoder::new()
42            .with_quality(options.quality as f32)
43            .with_speed(6)
44            .encode_rgba(buffer)
45            .map_err(|e| Error::Encode(format!("ravif encode: {e}")))?;
46
47        Ok(encoded.avif_file)
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; // R
62                data[i + 1] = (y * 255 / height) as u8; // G
63                data[i + 2] = 128; // B
64                data[i + 3] = 255; // A
65            }
66        }
67        ImageData::new(width, height, data)
68    }
69
70    #[test]
71    fn encode_produces_valid_avif() {
72        let codec = AvifCodec;
73        let image = create_test_image(64, 48);
74        let options = EncodeOptions { quality: 80 };
75
76        let encoded = codec.encode(&image, &options).expect("encode failed");
77
78        // Verify AVIF container: bytes 4-7 should be "ftyp"
79        assert!(
80            encoded.len() >= 8,
81            "encoded data too short: {} bytes",
82            encoded.len()
83        );
84        assert_eq!(&encoded[4..8], b"ftyp", "missing AVIF ftyp box");
85    }
86
87    #[test]
88    fn encode_and_decode_roundtrip() {
89        let codec = AvifCodec;
90        let original = create_test_image(64, 48);
91        let options = EncodeOptions { quality: 80 };
92
93        let encoded = codec.encode(&original, &options).expect("encode failed");
94        let decoded = codec.decode(&encoded).expect("decode failed");
95
96        assert_eq!(decoded.width, original.width);
97        assert_eq!(decoded.height, original.height);
98        assert_eq!(
99            decoded.data.len(),
100            (decoded.width * decoded.height * 4) as usize
101        );
102    }
103}