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