1#[allow(dead_code)]
7#[derive(Clone, PartialEq, Debug)]
8pub enum ScreenshotFormat {
9 Ppm,
10 Raw,
11 Tga,
12}
13
14#[allow(dead_code)]
15pub struct ScreenshotConfig {
16 pub width: u32,
17 pub height: u32,
18 pub format: ScreenshotFormat,
19 pub gamma_correct: bool,
20 pub include_alpha: bool,
21}
22
23#[allow(dead_code)]
24pub struct ScreenshotBuffer {
25 pub pixels: Vec<u8>, pub width: u32,
27 pub height: u32,
28 pub format: ScreenshotFormat,
29}
30
31#[allow(dead_code)]
32pub fn default_screenshot_config(width: u32, height: u32) -> ScreenshotConfig {
33 ScreenshotConfig {
34 width,
35 height,
36 format: ScreenshotFormat::Ppm,
37 gamma_correct: false,
38 include_alpha: false,
39 }
40}
41
42#[allow(dead_code)]
43pub fn new_screenshot_buffer(width: u32, height: u32) -> ScreenshotBuffer {
44 let size = (width as usize) * (height as usize) * 4;
45 ScreenshotBuffer {
46 pixels: vec![0u8; size],
47 width,
48 height,
49 format: ScreenshotFormat::Raw,
50 }
51}
52
53#[allow(dead_code)]
54pub fn set_pixel(buf: &mut ScreenshotBuffer, x: u32, y: u32, rgba: [u8; 4]) {
55 if x >= buf.width || y >= buf.height {
56 return;
57 }
58 let base = ((y as usize) * (buf.width as usize) + (x as usize)) * 4;
59 if base + 3 < buf.pixels.len() {
60 buf.pixels[base] = rgba[0];
61 buf.pixels[base + 1] = rgba[1];
62 buf.pixels[base + 2] = rgba[2];
63 buf.pixels[base + 3] = rgba[3];
64 }
65}
66
67#[allow(dead_code)]
68pub fn get_pixel(buf: &ScreenshotBuffer, x: u32, y: u32) -> [u8; 4] {
69 if x >= buf.width || y >= buf.height {
70 return [0u8; 4];
71 }
72 let base = ((y as usize) * (buf.width as usize) + (x as usize)) * 4;
73 if base + 3 < buf.pixels.len() {
74 [
75 buf.pixels[base],
76 buf.pixels[base + 1],
77 buf.pixels[base + 2],
78 buf.pixels[base + 3],
79 ]
80 } else {
81 [0u8; 4]
82 }
83}
84
85#[allow(dead_code)]
88pub fn encode_ppm(buf: &ScreenshotBuffer) -> Vec<u8> {
89 let header = format!("P6\n{} {}\n255\n", buf.width, buf.height);
90 let pixel_count = (buf.width as usize) * (buf.height as usize);
91 let mut out = Vec::with_capacity(header.len() + pixel_count * 3);
92 out.extend_from_slice(header.as_bytes());
93 for i in 0..pixel_count {
94 let base = i * 4;
95 if base + 2 < buf.pixels.len() {
96 out.push(buf.pixels[base]);
97 out.push(buf.pixels[base + 1]);
98 out.push(buf.pixels[base + 2]);
99 } else {
100 out.extend_from_slice(&[0u8, 0u8, 0u8]);
101 }
102 }
103 out
104}
105
106#[allow(dead_code)]
108pub fn encode_tga(buf: &ScreenshotBuffer) -> Vec<u8> {
109 let w = buf.width as u16;
110 let h = buf.height as u16;
111 let mut out = Vec::with_capacity(18 + (buf.width as usize) * (buf.height as usize) * 4);
113 out.push(0); out.push(0); out.push(2); out.extend_from_slice(&[0u8; 5]); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&w.to_le_bytes()); out.extend_from_slice(&h.to_le_bytes()); out.push(32); out.push(0x08); let pixel_count = (buf.width as usize) * (buf.height as usize);
125 for i in 0..pixel_count {
126 let base = i * 4;
127 if base + 3 < buf.pixels.len() {
128 out.push(buf.pixels[base + 2]); out.push(buf.pixels[base + 1]); out.push(buf.pixels[base]); out.push(buf.pixels[base + 3]); } else {
134 out.extend_from_slice(&[0u8; 4]);
135 }
136 }
137 out
138}
139
140#[allow(dead_code)]
142pub fn encode_raw(buf: &ScreenshotBuffer) -> Vec<u8> {
143 buf.pixels.clone()
144}
145
146#[allow(dead_code)]
147pub fn apply_gamma_correction(buf: &mut ScreenshotBuffer, gamma: f32) {
148 let inv_gamma = if gamma.abs() < 1e-6 { 1.0 } else { 1.0 / gamma };
149 for (i, pixel) in buf.pixels.iter_mut().enumerate() {
150 if i % 4 != 3 {
152 let linear = (*pixel as f32) / 255.0;
153 let corrected = linear.powf(inv_gamma);
154 *pixel = (corrected * 255.0).round().clamp(0.0, 255.0) as u8;
155 }
156 }
157}
158
159#[allow(dead_code)]
160pub fn flip_vertical(buf: &mut ScreenshotBuffer) {
161 let row_bytes = (buf.width as usize) * 4;
162 let height = buf.height as usize;
163 for row in 0..height / 2 {
164 let top = row * row_bytes;
165 let bot = (height - 1 - row) * row_bytes;
166 for col in 0..row_bytes {
167 buf.pixels.swap(top + col, bot + col);
168 }
169 }
170}
171
172#[allow(dead_code)]
173pub fn crop_screenshot(buf: &ScreenshotBuffer, x: u32, y: u32, w: u32, h: u32) -> ScreenshotBuffer {
174 let x = x.min(buf.width);
175 let y = y.min(buf.height);
176 let w = w.min(buf.width.saturating_sub(x));
177 let h = h.min(buf.height.saturating_sub(y));
178 let mut out = new_screenshot_buffer(w, h);
179 for row in 0..h {
180 for col in 0..w {
181 let src_base = (((y + row) as usize) * (buf.width as usize) + ((x + col) as usize)) * 4;
182 let dst_base = ((row as usize) * (w as usize) + (col as usize)) * 4;
183 if src_base + 3 < buf.pixels.len() && dst_base + 3 < out.pixels.len() {
184 out.pixels[dst_base] = buf.pixels[src_base];
185 out.pixels[dst_base + 1] = buf.pixels[src_base + 1];
186 out.pixels[dst_base + 2] = buf.pixels[src_base + 2];
187 out.pixels[dst_base + 3] = buf.pixels[src_base + 3];
188 }
189 }
190 }
191 out
192}
193
194#[allow(dead_code)]
195pub fn screenshot_size_bytes(cfg: &ScreenshotConfig) -> usize {
196 let pixels = (cfg.width as usize) * (cfg.height as usize);
197 match cfg.format {
198 ScreenshotFormat::Ppm => {
199 let header_est = format!("P6\n{} {}\n255\n", cfg.width, cfg.height).len();
201 header_est + pixels * 3
202 }
203 ScreenshotFormat::Raw => pixels * 4,
204 ScreenshotFormat::Tga => 18 + pixels * 4,
205 }
206}
207
208#[allow(dead_code)]
209pub fn blend_overlay(base: &mut ScreenshotBuffer, overlay: &ScreenshotBuffer, alpha: f32) {
210 let alpha = alpha.clamp(0.0, 1.0);
211 let w = base.width.min(overlay.width) as usize;
212 let h = base.height.min(overlay.height) as usize;
213 for row in 0..h {
214 for col in 0..w {
215 let bi = (row * (base.width as usize) + col) * 4;
216 let oi = (row * (overlay.width as usize) + col) * 4;
217 if bi + 3 < base.pixels.len() && oi + 3 < overlay.pixels.len() {
218 for c in 0..4 {
219 let bv = base.pixels[bi + c] as f32;
220 let ov = overlay.pixels[oi + c] as f32;
221 base.pixels[bi + c] =
222 (bv * (1.0 - alpha) + ov * alpha).round().clamp(0.0, 255.0) as u8;
223 }
224 }
225 }
226 }
227}
228
229#[allow(dead_code)]
230pub fn clear_screenshot(buf: &mut ScreenshotBuffer, color: [u8; 4]) {
231 let pixel_count = (buf.width as usize) * (buf.height as usize);
232 for i in 0..pixel_count {
233 let base = i * 4;
234 if base + 3 < buf.pixels.len() {
235 buf.pixels[base] = color[0];
236 buf.pixels[base + 1] = color[1];
237 buf.pixels[base + 2] = color[2];
238 buf.pixels[base + 3] = color[3];
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_new_screenshot_buffer() {
249 let buf = new_screenshot_buffer(4, 4);
250 assert_eq!(buf.width, 4);
251 assert_eq!(buf.height, 4);
252 assert_eq!(buf.pixels.len(), 4 * 4 * 4);
253 assert!(buf.pixels.iter().all(|&b| b == 0));
254 }
255
256 #[test]
257 fn test_set_and_get_pixel() {
258 let mut buf = new_screenshot_buffer(10, 10);
259 set_pixel(&mut buf, 3, 5, [255, 128, 64, 255]);
260 let px = get_pixel(&buf, 3, 5);
261 assert_eq!(px, [255, 128, 64, 255]);
262 }
263
264 #[test]
265 fn test_get_pixel_out_of_bounds() {
266 let buf = new_screenshot_buffer(4, 4);
267 let px = get_pixel(&buf, 100, 100);
268 assert_eq!(px, [0u8; 4]);
269 }
270
271 #[test]
272 fn test_encode_ppm_starts_with_p6() {
273 let buf = new_screenshot_buffer(2, 2);
274 let ppm = encode_ppm(&buf);
275 let header = std::str::from_utf8(&ppm[..2]).expect("should succeed");
276 assert_eq!(header, "P6");
277 }
278
279 #[test]
280 fn test_encode_ppm_correct_size() {
281 let buf = new_screenshot_buffer(3, 3);
282 let ppm = encode_ppm(&buf);
283 let header = "P6\n3 3\n255\n".to_string();
284 let expected_len = header.len() + 3 * 3 * 3; assert_eq!(ppm.len(), expected_len);
286 }
287
288 #[test]
289 fn test_encode_tga_minimum_size() {
290 let buf = new_screenshot_buffer(4, 4);
291 let tga = encode_tga(&buf);
292 assert!(
293 tga.len() >= 18,
294 "TGA should at least have the 18-byte header"
295 );
296 }
297
298 #[test]
299 fn test_encode_tga_header_image_type() {
300 let buf = new_screenshot_buffer(2, 2);
301 let tga = encode_tga(&buf);
302 assert_eq!(
303 tga[2], 2,
304 "TGA image type should be 2 (uncompressed true-color)"
305 );
306 }
307
308 #[test]
309 fn test_encode_raw_length() {
310 let buf = new_screenshot_buffer(5, 5);
311 let raw = encode_raw(&buf);
312 assert_eq!(
313 raw.len(),
314 5 * 5 * 4,
315 "raw should be exactly width*height*4 bytes"
316 );
317 }
318
319 #[test]
320 fn test_flip_vertical() {
321 let mut buf = new_screenshot_buffer(2, 2);
322 set_pixel(&mut buf, 0, 0, [255, 0, 0, 255]); set_pixel(&mut buf, 0, 1, [0, 0, 255, 255]); flip_vertical(&mut buf);
325 assert_eq!(get_pixel(&buf, 0, 0), [0, 0, 255, 255]);
327 assert_eq!(get_pixel(&buf, 0, 1), [255, 0, 0, 255]);
328 }
329
330 #[test]
331 fn test_crop_screenshot() {
332 let mut buf = new_screenshot_buffer(4, 4);
333 set_pixel(&mut buf, 2, 2, [100, 200, 50, 255]);
334 let cropped = crop_screenshot(&buf, 2, 2, 2, 2);
335 assert_eq!(cropped.width, 2);
336 assert_eq!(cropped.height, 2);
337 assert_eq!(get_pixel(&cropped, 0, 0), [100, 200, 50, 255]);
338 }
339
340 #[test]
341 fn test_clear_screenshot() {
342 let mut buf = new_screenshot_buffer(3, 3);
343 clear_screenshot(&mut buf, [128, 64, 32, 255]);
344 for y in 0..3 {
345 for x in 0..3 {
346 assert_eq!(get_pixel(&buf, x, y), [128, 64, 32, 255]);
347 }
348 }
349 }
350
351 #[test]
352 fn test_screenshot_size_bytes_raw() {
353 let cfg = ScreenshotConfig {
354 width: 10,
355 height: 10,
356 format: ScreenshotFormat::Raw,
357 gamma_correct: false,
358 include_alpha: false,
359 };
360 assert_eq!(screenshot_size_bytes(&cfg), 10 * 10 * 4);
361 }
362
363 #[test]
364 fn test_screenshot_size_bytes_tga() {
365 let cfg = ScreenshotConfig {
366 width: 8,
367 height: 8,
368 format: ScreenshotFormat::Tga,
369 gamma_correct: false,
370 include_alpha: true,
371 };
372 assert_eq!(screenshot_size_bytes(&cfg), 18 + 8 * 8 * 4);
373 }
374
375 #[test]
376 fn test_blend_overlay() {
377 let mut base = new_screenshot_buffer(2, 2);
378 clear_screenshot(&mut base, [0, 0, 0, 255]);
379 let mut overlay = new_screenshot_buffer(2, 2);
380 clear_screenshot(&mut overlay, [200, 200, 200, 255]);
381 blend_overlay(&mut base, &overlay, 0.5);
382 let px = get_pixel(&base, 0, 0);
383 assert_eq!(px[0], 100);
385 }
386
387 #[test]
388 fn test_apply_gamma_correction() {
389 let mut buf = new_screenshot_buffer(1, 1);
390 set_pixel(&mut buf, 0, 0, [128, 128, 128, 255]);
391 apply_gamma_correction(&mut buf, 2.2);
392 let px = get_pixel(&buf, 0, 0);
393 assert_eq!(px[3], 255, "alpha should be unchanged");
395 assert_ne!(px[0], 128, "gamma correction should change the value");
397 }
398
399 #[test]
400 fn test_default_screenshot_config() {
401 let cfg = default_screenshot_config(1920, 1080);
402 assert_eq!(cfg.width, 1920);
403 assert_eq!(cfg.height, 1080);
404 assert_eq!(cfg.format, ScreenshotFormat::Ppm);
405 assert!(!cfg.gamma_correct);
406 }
407}