Skip to main content

memf_core/
framebuffer.rs

1#[derive(Debug, Clone, serde::Serialize)]
2pub struct FramebufferResult {
3    pub width: u32,
4    pub height: u32,
5    pub stride: u32,
6    pub pixel_format: String,
7    pub phys_base: u64,
8    pub source: String,
9    #[serde(skip)]
10    pub png_bytes: Vec<u8>,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum PixelFormat {
15    Xrgb8888,
16    Xbgr8888,
17    Bgr24,
18    Rgb565,
19    Unknown(u8),
20}
21
22#[derive(Debug, thiserror::Error)]
23pub enum FramebufferError {
24    #[error("unsupported pixel format: {0:?}")]
25    UnsupportedPixelFormat(PixelFormat),
26    #[error("PNG encoding failed: {0}")]
27    PngEncode(String),
28    #[error("pixel buffer too small for {width}x{height} {format:?}")]
29    BufferTooSmall {
30        width: u32,
31        height: u32,
32        format: PixelFormat,
33    },
34    #[error("framebuffer not found")]
35    NotFound,
36    #[error("physical memory read failed")]
37    ReadFailed,
38}
39
40// r/g/b/v/p/n are conventional pixel- and colour-channel names here; keeping
41// them is clearer than synonyms invented to appease the lint.
42#[allow(clippy::many_single_char_names)]
43pub fn to_rgb24(
44    pixels: &[u8],
45    width: u32,
46    height: u32,
47    format: PixelFormat,
48) -> Result<Vec<u8>, FramebufferError> {
49    if width == 0 || height == 0 {
50        return Ok(Vec::new());
51    }
52    let n = (width * height) as usize;
53    match format {
54        PixelFormat::Xbgr8888 => {
55            if pixels.len() < n * 4 {
56                return Err(FramebufferError::BufferTooSmall {
57                    width,
58                    height,
59                    format,
60                });
61            }
62            Ok(pixels
63                .chunks_exact(4)
64                .take(n)
65                .flat_map(|p| [p[2], p[1], p[0]])
66                .collect())
67        }
68        PixelFormat::Xrgb8888 => {
69            if pixels.len() < n * 4 {
70                return Err(FramebufferError::BufferTooSmall {
71                    width,
72                    height,
73                    format,
74                });
75            }
76            Ok(pixels
77                .chunks_exact(4)
78                .take(n)
79                .flat_map(|p| [p[0], p[1], p[2]])
80                .collect())
81        }
82        PixelFormat::Bgr24 => {
83            if pixels.len() < n * 3 {
84                return Err(FramebufferError::BufferTooSmall {
85                    width,
86                    height,
87                    format,
88                });
89            }
90            Ok(pixels
91                .chunks_exact(3)
92                .take(n)
93                .flat_map(|p| [p[2], p[1], p[0]])
94                .collect())
95        }
96        PixelFormat::Rgb565 => {
97            if pixels.len() < n * 2 {
98                return Err(FramebufferError::BufferTooSmall {
99                    width,
100                    height,
101                    format,
102                });
103            }
104            Ok(pixels
105                .chunks_exact(2)
106                .take(n)
107                .flat_map(|p| {
108                    let v = u16::from_le_bytes([p[0], p[1]]);
109                    let r5 = ((v >> 11) & 0x1F) as u8;
110                    let g6 = ((v >> 5) & 0x3F) as u8;
111                    let b5 = (v & 0x1F) as u8;
112                    let r = (r5 << 3) | (r5 >> 2);
113                    let g = (g6 << 2) | (g6 >> 4);
114                    let b = (b5 << 3) | (b5 >> 2);
115                    [r, g, b]
116                })
117                .collect())
118        }
119        PixelFormat::Unknown(_) => Err(FramebufferError::UnsupportedPixelFormat(format)),
120    }
121}
122
123pub fn encode_png(
124    pixels: &[u8],
125    width: u32,
126    height: u32,
127    format: PixelFormat,
128) -> Result<Vec<u8>, FramebufferError> {
129    if width == 0 || height == 0 {
130        return Ok(Vec::new());
131    }
132    let rgb = to_rgb24(pixels, width, height, format)?;
133    let mut out = Vec::new();
134    {
135        let mut encoder = png::Encoder::new(&mut out, width, height);
136        encoder.set_color(png::ColorType::Rgb);
137        encoder.set_depth(png::BitDepth::Eight);
138        let mut writer = encoder
139            .write_header()
140            .map_err(|e| FramebufferError::PngEncode(e.to_string()))?;
141        writer
142            .write_image_data(&rgb)
143            .map_err(|e| FramebufferError::PngEncode(e.to_string()))?;
144    }
145    Ok(out)
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn encode_png_produces_valid_png_header() {
154        // 2x2 XBGR8888: B=i, G=0, R=0, X=0xFF for each pixel
155        let pixels: Vec<u8> = (0u8..4).flat_map(|i| [i, 0, 0, 0xFF]).collect();
156        let png = encode_png(&pixels, 2, 2, PixelFormat::Xbgr8888).expect("encode must succeed");
157        assert!(png.starts_with(b"\x89PNG\r\n\x1a\n"), "must have PNG magic");
158        assert!(png.len() > 20);
159    }
160
161    #[test]
162    fn encode_png_zero_dimensions_returns_empty() {
163        let result = encode_png(&[], 0, 0, PixelFormat::Xrgb8888).expect("zero dims ok");
164        assert!(result.is_empty());
165    }
166
167    #[test]
168    fn to_rgb24_xbgr8888_swaps_channels() {
169        // XBGR8888 byte order: [B, G, R, X]
170        // pixel: B=0x10, G=0x20, R=0x30, X=0xFF
171        let pixels = vec![0x10u8, 0x20, 0x30, 0xFF];
172        let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Xbgr8888).expect("ok");
173        // RGB24 output should be [R, G, B] = [0x30, 0x20, 0x10]
174        assert_eq!(rgb, vec![0x30, 0x20, 0x10]);
175    }
176
177    #[test]
178    fn to_rgb24_xrgb8888_preserves_rgb_order() {
179        // XRGB8888: [R, G, B, X] — output RGB24 is same first 3 bytes
180        let pixels = vec![0x10u8, 0x20, 0x30, 0xFF];
181        let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Xrgb8888).expect("ok");
182        assert_eq!(rgb, vec![0x10, 0x20, 0x30]);
183    }
184
185    #[test]
186    fn to_rgb24_bgr24_swaps_rb() {
187        // BGR24: [B, G, R] -> RGB24: [R, G, B]
188        let pixels = vec![0x10u8, 0x20, 0x30];
189        let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Bgr24).expect("ok");
190        assert_eq!(rgb, vec![0x30, 0x20, 0x10]);
191    }
192
193    #[test]
194    fn to_rgb24_rgb565_full_red_pixel() {
195        // RGB565: 5R 6G 5B packed in 2 bytes LE
196        // Full red: R=11111=0x1F, G=000000, B=00000 -> value = 0xF800
197        // LE bytes: [0x00, 0xF8]
198        let pixels = vec![0x00u8, 0xF8];
199        let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Rgb565).expect("ok");
200        // R should expand 0x1F -> 0xFF (shift left 3, fill low 2 bits)
201        assert_eq!(rgb[0], 0xFF, "R should be max for full red");
202        assert_eq!(rgb[1], 0x00, "G should be 0");
203        assert_eq!(rgb[2], 0x00, "B should be 0");
204    }
205
206    #[test]
207    fn to_rgb24_unknown_format_returns_error() {
208        let pixels = vec![0u8; 4];
209        let result = to_rgb24(&pixels, 1, 1, PixelFormat::Unknown(99));
210        assert!(result.is_err());
211    }
212
213    #[test]
214    fn to_rgb24_buffer_too_small_returns_error() {
215        // 2x2 XBGR8888 needs 16 bytes, give only 4
216        let pixels = vec![0u8; 4];
217        let result = to_rgb24(&pixels, 2, 2, PixelFormat::Xbgr8888);
218        assert!(result.is_err());
219    }
220}