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#[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 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 let pixels = vec![0x10u8, 0x20, 0x30, 0xFF];
172 let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Xbgr8888).expect("ok");
173 assert_eq!(rgb, vec![0x30, 0x20, 0x10]);
175 }
176
177 #[test]
178 fn to_rgb24_xrgb8888_preserves_rgb_order() {
179 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 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 let pixels = vec![0x00u8, 0xF8];
199 let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Rgb565).expect("ok");
200 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 let pixels = vec![0u8; 4];
217 let result = to_rgb24(&pixels, 2, 2, PixelFormat::Xbgr8888);
218 assert!(result.is_err());
219 }
220}