rlvgl_core/plugins/
jpeg.rs

1//! JPEG decoder for Color pixel arrays.
2use crate::widget::Color;
3use alloc::vec::Vec;
4pub use jpeg_decoder::Error;
5use jpeg_decoder::{Decoder, PixelFormat};
6use std::io::Cursor;
7
8/// Decode a JPEG image into a vector of RGB [`Color`]s.
9pub fn decode(data: &[u8]) -> Result<(Vec<Color>, u16, u16), Error> {
10    let mut decoder = Decoder::new(Cursor::new(data));
11    let pixels_raw = decoder.decode()?;
12    let info = decoder
13        .info()
14        .ok_or_else(|| Error::Format("missing image info".into()))?;
15    let mut pixels = Vec::with_capacity(info.width as usize * info.height as usize);
16    match info.pixel_format {
17        PixelFormat::L8 => {
18            for &v in &pixels_raw {
19                pixels.push(Color(v, v, v, 255));
20            }
21        }
22        PixelFormat::L16 => {
23            for chunk in pixels_raw.chunks_exact(2) {
24                let val = u16::from_be_bytes([chunk[0], chunk[1]]);
25                let v = (val / 257) as u8;
26                pixels.push(Color(v, v, v, 255));
27            }
28        }
29        PixelFormat::RGB24 => {
30            for chunk in pixels_raw.chunks_exact(3) {
31                pixels.push(Color(chunk[0], chunk[1], chunk[2], 255));
32            }
33        }
34        PixelFormat::CMYK32 => {
35            for chunk in pixels_raw.chunks_exact(4) {
36                let c = chunk[0] as f32 / 255.0;
37                let m = chunk[1] as f32 / 255.0;
38                let y = chunk[2] as f32 / 255.0;
39                let k = chunk[3] as f32 / 255.0;
40                let r = (1.0 - (c * (1.0 - k) + k)) * 255.0;
41                let g = (1.0 - (m * (1.0 - k) + k)) * 255.0;
42                let b = (1.0 - (y * (1.0 - k) + k)) * 255.0;
43                pixels.push(Color(r as u8, g as u8, b as u8, 255));
44            }
45        }
46    }
47    Ok((pixels, info.width, info.height))
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use base64::Engine;
54
55    const RED_DOT_JPEG: &str = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDi6KKK+ZP3E//Z";
56
57    #[test]
58    fn decode_red_dot() {
59        let data = base64::engine::general_purpose::STANDARD
60            .decode(RED_DOT_JPEG)
61            .unwrap();
62        let (pixels, w, h) = decode(&data).unwrap();
63        assert_eq!((w, h), (1, 1));
64        assert!(pixels[0].0 >= 250 && pixels[0].1 == 0 && pixels[0].2 == 0);
65    }
66}