Skip to main content

slimg_core/codec/
mod.rs

1pub mod avif;
2pub mod jpeg;
3pub mod jxl;
4pub mod png;
5pub mod qoi;
6pub mod webp;
7
8use crate::error::Result;
9use crate::format::Format;
10
11/// Decoded image data in RGBA format (4 bytes per pixel).
12#[derive(Debug, Clone)]
13pub struct ImageData {
14    pub width: u32,
15    pub height: u32,
16    pub data: Vec<u8>,
17}
18
19impl ImageData {
20    /// Create a new `ImageData`.
21    ///
22    /// # Panics (debug only)
23    /// Panics if `data.len()` does not equal `width * height * 4`.
24    pub fn new(width: u32, height: u32, data: Vec<u8>) -> Self {
25        debug_assert_eq!(
26            data.len(),
27            (width as usize) * (height as usize) * 4,
28            "ImageData: expected {} bytes ({}x{}x4), got {}",
29            (width as usize) * (height as usize) * 4,
30            width,
31            height,
32            data.len(),
33        );
34        Self {
35            width,
36            height,
37            data,
38        }
39    }
40
41    /// Convert RGBA pixel data to RGB by dropping the alpha channel.
42    pub fn to_rgb(&self) -> Vec<u8> {
43        self.data
44            .chunks_exact(4)
45            .flat_map(|px| &px[..3])
46            .copied()
47            .collect()
48    }
49}
50
51/// Options for encoding an image.
52#[derive(Debug, Clone, Copy)]
53pub struct EncodeOptions {
54    /// Quality value in the range 0..=100.
55    pub quality: u8,
56}
57
58impl Default for EncodeOptions {
59    fn default() -> Self {
60        Self { quality: 80 }
61    }
62}
63
64/// Trait implemented by each image codec (JPEG, PNG, WebP, etc.).
65pub trait Codec {
66    /// The image format handled by this codec.
67    fn format(&self) -> Format;
68
69    /// Decode raw file bytes into RGBA `ImageData`.
70    fn decode(&self, data: &[u8]) -> Result<ImageData>;
71
72    /// Encode `ImageData` into the codec's file format.
73    fn encode(&self, image: &ImageData, options: &EncodeOptions) -> Result<Vec<u8>>;
74}
75
76/// Return the appropriate codec for the given format.
77pub fn get_codec(format: Format) -> Box<dyn Codec> {
78    match format {
79        Format::Jpeg => Box::new(jpeg::JpegCodec),
80        Format::Png => Box::new(png::PngCodec),
81        Format::WebP => Box::new(webp::WebPCodec),
82        Format::Avif => Box::new(avif::AvifCodec),
83        Format::Jxl => Box::new(jxl::JxlCodec),
84        Format::Qoi => Box::new(qoi::QoiCodec),
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn image_data_to_rgb() {
94        // 2x1 image: red pixel (255,0,0,255) and green pixel (0,255,0,128)
95        let data = vec![255, 0, 0, 255, 0, 255, 0, 128];
96        let img = ImageData::new(2, 1, data);
97
98        let rgb = img.to_rgb();
99        assert_eq!(rgb, vec![255, 0, 0, 0, 255, 0]);
100    }
101
102    #[test]
103    fn encode_options_default() {
104        let opts = EncodeOptions::default();
105        assert_eq!(opts.quality, 80);
106    }
107
108    #[test]
109    fn image_data_dimensions() {
110        let data = vec![0u8; 4 * 3 * 2]; // 3x2 image
111        let img = ImageData::new(3, 2, data);
112        assert_eq!(img.width, 3);
113        assert_eq!(img.height, 2);
114        assert_eq!(img.data.len(), 24);
115    }
116
117    #[test]
118    #[cfg(debug_assertions)]
119    #[should_panic(expected = "ImageData: expected")]
120    fn image_data_wrong_size_panics_in_debug() {
121        let data = vec![0u8; 10]; // wrong size for any valid image
122        let _ = ImageData::new(2, 2, data);
123    }
124}