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#[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 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 let pixels = vec![0x10u8, 0x20, 0x30, 0xFF];
136 let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Xbgr8888).expect("ok");
137 assert_eq!(rgb, vec![0x30, 0x20, 0x10]);
139 }
140
141 #[test]
142 fn to_rgb24_xrgb8888_preserves_rgb_order() {
143 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 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 let pixels = vec![0x00u8, 0xF8];
163 let rgb = to_rgb24(&pixels, 1, 1, PixelFormat::Rgb565).expect("ok");
164 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 let pixels = vec![0u8; 4];
181 let result = to_rgb24(&pixels, 2, 2, PixelFormat::Xbgr8888);
182 assert!(result.is_err());
183 }
184}