⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/media/image_codecs/
mod.rs

1//! Image format decoders: PNG, JPEG (baseline DCT), GIF (87a/89a)
2//!
3//! All decoders are `no_std`-compatible, use integer/fixed-point math only
4//! (no floating point), and produce pixel buffers suitable for the desktop
5//! compositing pipeline.
6//!
7//! # Supported Formats
8//!
9//! - **PNG**: Full critical chunk support (IHDR, PLTE, IDAT, IEND), ancillary
10//!   chunks (tRNS, gAMA), DEFLATE decompression, all 5 filter types, Adam7
11//!   interlacing, color types 0/2/3/4/6, bit depths 1/2/4/8/16
12//! - **JPEG**: Baseline DCT (SOF0), Huffman entropy coding, integer IDCT,
13//!   YCbCr-to-RGB via fixed-point, chroma subsampling 4:4:4/4:2:2/4:2:0,
14//!   restart intervals
15//! - **GIF**: 87a/89a, LZW decompression, global/local color tables, animation
16//!   frames, disposal methods, transparency, interlaced rendering
17
18#![allow(dead_code)]
19
20pub mod gif;
21pub mod jpeg;
22pub mod png;
23
24use alloc::vec::Vec;
25
26// Re-export all public items from submodules
27pub use gif::{decode_gif, DecodedGif, GifDisposal, GifFrame};
28pub use jpeg::decode_jpeg;
29pub use png::decode_png;
30
31// ============================================================================
32// Common types and helpers
33// ============================================================================
34
35/// Errors produced by the image decoders.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ImageCodecError {
38    /// Data is too short or truncated.
39    TruncatedData,
40    /// Magic bytes / signature mismatch.
41    InvalidSignature,
42    /// Unsupported feature or variant.
43    Unsupported,
44    /// Corrupted or invalid data encountered during decoding.
45    CorruptData,
46    /// Checksum (CRC, Adler-32, etc.) verification failed.
47    ChecksumMismatch,
48    /// Image dimensions are zero or exceed limits.
49    InvalidDimensions,
50    /// Decompression failed (DEFLATE, LZW).
51    DecompressionError,
52    /// Huffman table construction failed.
53    InvalidHuffmanTable,
54    /// Quantization table error.
55    InvalidQuantTable,
56}
57
58/// A decoded image frame with RGBA8888 pixels.
59#[derive(Debug, Clone, PartialEq)]
60pub struct DecodedImage {
61    /// Width in pixels.
62    pub width: u32,
63    /// Height in pixels.
64    pub height: u32,
65    /// RGBA8888 pixel data, row-major, 4 bytes per pixel.
66    pub pixels: Vec<u8>,
67}
68
69impl DecodedImage {
70    /// Create a new image filled with transparent black.
71    pub fn new(width: u32, height: u32) -> Self {
72        let size = (width as usize)
73            .checked_mul(height as usize)
74            .and_then(|n| n.checked_mul(4))
75            .unwrap_or(0);
76        Self {
77            width,
78            height,
79            pixels: alloc::vec![0u8; size],
80        }
81    }
82
83    /// Set a pixel at (x, y). Out-of-bounds writes are silently ignored.
84    #[inline]
85    pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) {
86        if x < self.width && y < self.height {
87            let off = ((y as usize) * (self.width as usize) + (x as usize)) * 4;
88            if off + 3 < self.pixels.len() {
89                self.pixels[off] = r;
90                self.pixels[off + 1] = g;
91                self.pixels[off + 2] = b;
92                self.pixels[off + 3] = a;
93            }
94        }
95    }
96
97    /// Get a pixel at (x, y) as (R, G, B, A).
98    #[inline]
99    pub fn get_pixel(&self, x: u32, y: u32) -> (u8, u8, u8, u8) {
100        if x < self.width && y < self.height {
101            let off = ((y as usize) * (self.width as usize) + (x as usize)) * 4;
102            if off + 3 < self.pixels.len() {
103                return (
104                    self.pixels[off],
105                    self.pixels[off + 1],
106                    self.pixels[off + 2],
107                    self.pixels[off + 3],
108                );
109            }
110        }
111        (0, 0, 0, 0)
112    }
113}
114
115// ---------------------------------------------------------------------------
116// Byte-reading helpers
117// ---------------------------------------------------------------------------
118
119#[inline]
120pub(crate) fn read_be_u16(data: &[u8], off: usize) -> u16 {
121    ((data[off] as u16) << 8) | (data[off + 1] as u16)
122}
123
124#[inline]
125pub(crate) fn read_be_u32(data: &[u8], off: usize) -> u32 {
126    ((data[off] as u32) << 24)
127        | ((data[off + 1] as u32) << 16)
128        | ((data[off + 2] as u32) << 8)
129        | (data[off + 3] as u32)
130}
131
132#[inline]
133pub(crate) fn read_le_u16(data: &[u8], off: usize) -> u16 {
134    (data[off] as u16) | ((data[off + 1] as u16) << 8)
135}
136
137#[inline]
138pub(crate) fn read_le_u32(data: &[u8], off: usize) -> u32 {
139    (data[off] as u32)
140        | ((data[off + 1] as u32) << 8)
141        | ((data[off + 2] as u32) << 16)
142        | ((data[off + 3] as u32) << 24)
143}
144
145/// Clamp an i32 to the u8 range [0, 255].
146#[inline]
147pub(crate) fn clamp_u8(v: i32) -> u8 {
148    if v < 0 {
149        0
150    } else if v > 255 {
151        255
152    } else {
153        v as u8
154    }
155}
156
157// ============================================================================
158// Format detection helper
159// ============================================================================
160
161/// Detected image codec format.
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum ImageCodecFormat {
164    Png,
165    Jpeg,
166    Gif,
167    Unknown,
168}
169
170/// Detect whether data is PNG, JPEG, or GIF.
171pub fn detect_codec_format(data: &[u8]) -> ImageCodecFormat {
172    if data.len() >= 8 && data[..8] == png::PNG_SIGNATURE {
173        ImageCodecFormat::Png
174    } else if data.len() >= 2 && data[0] == 0xFF && data[1] == 0xD8 {
175        ImageCodecFormat::Jpeg
176    } else if data.len() >= 6 && (&data[..6] == gif::GIF87A || &data[..6] == gif::GIF89A) {
177        ImageCodecFormat::Gif
178    } else {
179        ImageCodecFormat::Unknown
180    }
181}
182
183/// Auto-detect format and decode.
184pub fn decode_image(data: &[u8]) -> Result<DecodedImage, ImageCodecError> {
185    match detect_codec_format(data) {
186        ImageCodecFormat::Png => decode_png(data),
187        ImageCodecFormat::Jpeg => decode_jpeg(data),
188        ImageCodecFormat::Gif => {
189            let gif = decode_gif(data)?;
190            if let Some(frame) = gif.frames.into_iter().next() {
191                Ok(frame.image)
192            } else {
193                Err(ImageCodecError::CorruptData)
194            }
195        }
196        ImageCodecFormat::Unknown => Err(ImageCodecError::Unsupported),
197    }
198}
199
200// ============================================================================
201// TESTS
202// ============================================================================
203
204#[cfg(test)]
205mod tests {
206    #[allow(unused_imports)]
207    use alloc::vec;
208
209    use super::*;
210
211    // -----------------------------------------------------------------------
212    // Common / format detection tests
213    // -----------------------------------------------------------------------
214
215    #[test]
216    fn test_detect_png() {
217        let data = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 0];
218        assert_eq!(detect_codec_format(&data), ImageCodecFormat::Png);
219    }
220
221    #[test]
222    fn test_detect_jpeg() {
223        let data = [0xFF, 0xD8, 0xFF, 0xE0];
224        assert_eq!(detect_codec_format(&data), ImageCodecFormat::Jpeg);
225    }
226
227    #[test]
228    fn test_detect_gif87a() {
229        let mut data = vec![0u8; 20];
230        data[..6].copy_from_slice(b"GIF87a");
231        assert_eq!(detect_codec_format(&data), ImageCodecFormat::Gif);
232    }
233
234    #[test]
235    fn test_detect_gif89a() {
236        let mut data = vec![0u8; 20];
237        data[..6].copy_from_slice(b"GIF89a");
238        assert_eq!(detect_codec_format(&data), ImageCodecFormat::Gif);
239    }
240
241    #[test]
242    fn test_detect_unknown() {
243        let data = [0x00, 0x01, 0x02, 0x03];
244        assert_eq!(detect_codec_format(&data), ImageCodecFormat::Unknown);
245    }
246
247    #[test]
248    fn test_decoded_image_set_get_pixel() {
249        let mut img = DecodedImage::new(4, 4);
250        img.set_pixel(1, 2, 0xAA, 0xBB, 0xCC, 0xDD);
251        assert_eq!(img.get_pixel(1, 2), (0xAA, 0xBB, 0xCC, 0xDD));
252    }
253
254    #[test]
255    fn test_decoded_image_out_of_bounds() {
256        let mut img = DecodedImage::new(2, 2);
257        img.set_pixel(10, 10, 255, 0, 0, 255); // should not panic
258        assert_eq!(img.get_pixel(10, 10), (0, 0, 0, 0));
259    }
260
261    #[test]
262    fn test_clamp_u8_values() {
263        assert_eq!(clamp_u8(-10), 0);
264        assert_eq!(clamp_u8(0), 0);
265        assert_eq!(clamp_u8(128), 128);
266        assert_eq!(clamp_u8(255), 255);
267        assert_eq!(clamp_u8(300), 255);
268    }
269
270    #[test]
271    fn test_image_codec_error_equality() {
272        assert_eq!(
273            ImageCodecError::TruncatedData,
274            ImageCodecError::TruncatedData
275        );
276        assert_ne!(ImageCodecError::TruncatedData, ImageCodecError::CorruptData);
277    }
278
279    #[test]
280    fn test_decoded_image_new_size() {
281        let img = DecodedImage::new(10, 20);
282        assert_eq!(img.width, 10);
283        assert_eq!(img.height, 20);
284        assert_eq!(img.pixels.len(), 10 * 20 * 4);
285    }
286
287    #[test]
288    fn test_read_helpers() {
289        let data = [0x01, 0x02, 0x03, 0x04];
290        assert_eq!(read_be_u16(&data, 0), 0x0102);
291        assert_eq!(read_be_u32(&data, 0), 0x01020304);
292        assert_eq!(read_le_u16(&data, 0), 0x0201);
293        assert_eq!(read_le_u32(&data, 0), 0x04030201);
294    }
295}