1use crate::color::Rgba;
7use crate::error::{Error, Result};
8use trueno::{Backend, Vector};
9
10const SIMD_ALIGNMENT: usize = 64;
12
13#[derive(Debug, Clone)]
23pub struct Framebuffer {
24 width: u32,
26 height: u32,
28 pixels: Vec<u8>,
32 stride: usize,
34}
35
36impl Framebuffer {
37 pub fn new(width: u32, height: u32) -> Result<Self> {
55 if width == 0 || height == 0 {
56 return Err(Error::InvalidDimensions { width, height });
57 }
58
59 let row_bytes = (width as usize) * 4;
61 let stride = (row_bytes + SIMD_ALIGNMENT - 1) & !(SIMD_ALIGNMENT - 1);
62
63 let size = stride * (height as usize);
64
65 let mut pixels = Vec::with_capacity(size + SIMD_ALIGNMENT);
67 pixels.resize(size, 0);
68
69 Ok(Self { width, height, pixels, stride })
70 }
71
72 #[must_use]
74 pub const fn width(&self) -> u32 {
75 self.width
76 }
77
78 #[must_use]
80 pub const fn height(&self) -> u32 {
81 self.height
82 }
83
84 #[must_use]
86 pub const fn stride(&self) -> usize {
87 self.stride
88 }
89
90 #[must_use]
92 pub const fn pixel_count(&self) -> usize {
93 (self.width as usize) * (self.height as usize)
94 }
95
96 #[must_use]
98 pub fn pixels(&self) -> &[u8] {
99 &self.pixels
100 }
101
102 pub fn pixels_mut(&mut self) -> &mut [u8] {
104 &mut self.pixels
105 }
106
107 #[must_use]
109 pub fn row(&self, y: u32) -> Option<&[u8]> {
110 if y >= self.height {
111 return None;
112 }
113 let start = (y as usize) * self.stride;
114 let end = start + (self.width as usize) * 4;
115 Some(&self.pixels[start..end])
116 }
117
118 pub fn row_mut(&mut self, y: u32) -> Option<&mut [u8]> {
120 if y >= self.height {
121 return None;
122 }
123 let start = (y as usize) * self.stride;
124 let end = start + (self.width as usize) * 4;
125 Some(&mut self.pixels[start..end])
126 }
127
128 pub fn clear(&mut self, color: Rgba) {
133 let [r, g, b, a] = color.to_array();
134
135 let pattern: [u8; 64] = {
137 let mut p = [0u8; 64];
138 for i in 0..16 {
139 p[i * 4] = r;
140 p[i * 4 + 1] = g;
141 p[i * 4 + 2] = b;
142 p[i * 4 + 3] = a;
143 }
144 p
145 };
146
147 for y in 0..self.height {
149 let row_start = (y as usize) * self.stride;
150 let row_end = row_start + (self.width as usize) * 4;
151 let row = &mut self.pixels[row_start..row_end];
152
153 let mut offset = 0;
155 while offset + 64 <= row.len() {
156 row[offset..offset + 64].copy_from_slice(&pattern);
157 offset += 64;
158 }
159
160 for chunk in row[offset..].chunks_exact_mut(4) {
162 chunk[0] = r;
163 chunk[1] = g;
164 chunk[2] = b;
165 chunk[3] = a;
166 }
167 }
168 }
169
170 pub fn fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, color: Rgba) {
174 let x1 = x.min(self.width);
175 let y1 = y.min(self.height);
176 let x2 = (x + w).min(self.width);
177 let y2 = (y + h).min(self.height);
178
179 if x1 >= x2 || y1 >= y2 {
180 return;
181 }
182
183 let [r, g, b, a] = color.to_array();
184 let rect_width = (x2 - x1) as usize;
185
186 for row_y in y1..y2 {
187 let row_start = (row_y as usize) * self.stride + (x1 as usize) * 4;
188 let row = &mut self.pixels[row_start..row_start + rect_width * 4];
189
190 for chunk in row.chunks_exact_mut(4) {
191 chunk[0] = r;
192 chunk[1] = g;
193 chunk[2] = b;
194 chunk[3] = a;
195 }
196 }
197 }
198
199 #[must_use]
203 pub fn get_pixel(&self, x: u32, y: u32) -> Option<Rgba> {
204 if x >= self.width || y >= self.height {
205 return None;
206 }
207
208 let idx = self.pixel_index(x, y);
209 Some(Rgba::from_array([
210 self.pixels[idx],
211 self.pixels[idx + 1],
212 self.pixels[idx + 2],
213 self.pixels[idx + 3],
214 ]))
215 }
216
217 pub fn set_pixel(&mut self, x: u32, y: u32, color: Rgba) {
221 if x >= self.width || y >= self.height {
222 return;
223 }
224
225 let idx = self.pixel_index(x, y);
226 let [r, g, b, a] = color.to_array();
227 self.pixels[idx] = r;
228 self.pixels[idx + 1] = g;
229 self.pixels[idx + 2] = b;
230 self.pixels[idx + 3] = a;
231 }
232
233 pub fn blend_pixel(&mut self, x: u32, y: u32, color: Rgba) {
238 if x >= self.width || y >= self.height {
239 return;
240 }
241
242 let idx = self.pixel_index(x, y);
243 let src_a = f32::from(color.a) / 255.0;
244 let dst_a = f32::from(self.pixels[idx + 3]) / 255.0;
245 let out_a = src_a + dst_a * (1.0 - src_a);
246
247 if out_a > 0.0 {
248 let blend = |src: u8, dst: u8| -> u8 {
249 let src_f = f32::from(src) / 255.0;
250 let dst_f = f32::from(dst) / 255.0;
251 let out = (src_f * src_a + dst_f * dst_a * (1.0 - src_a)) / out_a;
252 (out * 255.0) as u8
253 };
254
255 self.pixels[idx] = blend(color.r, self.pixels[idx]);
256 self.pixels[idx + 1] = blend(color.g, self.pixels[idx + 1]);
257 self.pixels[idx + 2] = blend(color.b, self.pixels[idx + 2]);
258 self.pixels[idx + 3] = (out_a * 255.0) as u8;
259 }
260 }
261
262 pub fn blend_over(&mut self, other: &Framebuffer, alpha: f32) -> Result<()> {
270 if self.width != other.width || self.height != other.height {
271 return Err(Error::InvalidDimensions { width: other.width, height: other.height });
272 }
273
274 let alpha = alpha.clamp(0.0, 1.0);
275 let inv_alpha = 1.0 - alpha;
276
277 for y in 0..self.height {
279 let row_start = (y as usize) * self.stride;
280 let row_pixels = (self.width as usize) * 4;
281
282 let dst_slice = &self.pixels[row_start..row_start + row_pixels];
284 let src_slice = &other.pixels[row_start..row_start + row_pixels];
285
286 let dst_f32: Vec<f32> = dst_slice.iter().map(|&b| f32::from(b)).collect();
288 let src_f32: Vec<f32> = src_slice.iter().map(|&b| f32::from(b)).collect();
289
290 let dst_vec = Vector::from_vec(dst_f32);
292 let src_vec = Vector::from_vec(src_f32);
293
294 if let (Ok(src_scaled), Ok(dst_scaled)) = (
296 src_vec.mul(&Vector::from_vec(vec![alpha; row_pixels])),
297 dst_vec.mul(&Vector::from_vec(vec![inv_alpha; row_pixels])),
298 ) {
299 if let Ok(result) = src_scaled.add(&dst_scaled) {
300 let row = &mut self.pixels[row_start..row_start + row_pixels];
302 for (i, &v) in result.as_slice().iter().enumerate() {
303 row[i] = v.clamp(0.0, 255.0) as u8;
304 }
305 }
306 }
307 }
308
309 Ok(())
310 }
311
312 pub fn adjust_brightness(&mut self, factor: f32) {
316 let factor = factor.max(0.0);
317
318 for y in 0..self.height {
319 let row_start = (y as usize) * self.stride;
320 let row_pixels = (self.width as usize) * 4;
321 let row = &mut self.pixels[row_start..row_start + row_pixels];
322
323 for chunk in row.chunks_exact_mut(4) {
325 chunk[0] = (f32::from(chunk[0]) * factor).clamp(0.0, 255.0) as u8;
326 chunk[1] = (f32::from(chunk[1]) * factor).clamp(0.0, 255.0) as u8;
327 chunk[2] = (f32::from(chunk[2]) * factor).clamp(0.0, 255.0) as u8;
328 }
330 }
331 }
332
333 #[must_use]
337 pub fn luminance_stats(&self) -> (f32, f32, f32) {
338 let mut luminances = Vec::with_capacity(self.pixel_count());
339
340 for y in 0..self.height {
341 if let Some(row) = self.row(y) {
342 for chunk in row.chunks_exact(4) {
343 let lum = 0.2126 * f32::from(chunk[0])
345 + 0.7152 * f32::from(chunk[1])
346 + 0.0722 * f32::from(chunk[2]);
347 luminances.push(lum);
348 }
349 }
350 }
351
352 let vec = Vector::from_vec(luminances);
354
355 let min = vec.min().unwrap_or(0.0);
356 let max = vec.max().unwrap_or(255.0);
357 let mean = vec.mean().unwrap_or(127.5);
358
359 (min, max, mean)
360 }
361
362 #[inline]
364 fn pixel_index(&self, x: u32, y: u32) -> usize {
365 (y as usize) * self.stride + (x as usize) * 4
366 }
367
368 #[must_use]
370 pub fn is_aligned(&self) -> bool {
371 self.pixels.as_ptr() as usize % SIMD_ALIGNMENT == 0
372 }
373
374 #[must_use]
379 pub fn to_compact_pixels(&self) -> Vec<u8> {
380 let row_bytes = (self.width as usize) * 4;
381
382 if self.stride == row_bytes {
384 return self.pixels[..row_bytes * (self.height as usize)].to_vec();
385 }
386
387 let mut compact = Vec::with_capacity(row_bytes * (self.height as usize));
389 for y in 0..self.height {
390 let start = (y as usize) * self.stride;
391 compact.extend_from_slice(&self.pixels[start..start + row_bytes]);
392 }
393 compact
394 }
395
396 #[must_use]
398 pub fn backend() -> Backend {
399 Backend::select_best()
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_new_framebuffer() {
409 let fb = Framebuffer::new(100, 50).expect("framebuffer creation should succeed");
410 assert_eq!(fb.width(), 100);
411 assert_eq!(fb.height(), 50);
412 assert_eq!(fb.pixel_count(), 5000);
413 assert!(fb.stride() >= 400);
415 }
416
417 #[test]
418 fn test_invalid_dimensions() {
419 assert!(Framebuffer::new(0, 100).is_err());
420 assert!(Framebuffer::new(100, 0).is_err());
421 assert!(Framebuffer::new(0, 0).is_err());
422 }
423
424 #[test]
425 fn test_clear() {
426 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
427 fb.clear(Rgba::RED);
428
429 for y in 0..10 {
430 for x in 0..10 {
431 assert_eq!(fb.get_pixel(x, y), Some(Rgba::RED));
432 }
433 }
434 }
435
436 #[test]
437 fn test_clear_large() {
438 let mut fb = Framebuffer::new(1920, 1080).expect("framebuffer creation should succeed");
440 fb.clear(Rgba::BLUE);
441
442 assert_eq!(fb.get_pixel(0, 0), Some(Rgba::BLUE));
443 assert_eq!(fb.get_pixel(959, 539), Some(Rgba::BLUE));
444 assert_eq!(fb.get_pixel(1919, 1079), Some(Rgba::BLUE));
445 }
446
447 #[test]
448 fn test_fill_rect() {
449 let mut fb = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
450 fb.clear(Rgba::WHITE);
451 fb.fill_rect(10, 10, 20, 20, Rgba::RED);
452
453 assert_eq!(fb.get_pixel(15, 15), Some(Rgba::RED));
455 assert_eq!(fb.get_pixel(5, 5), Some(Rgba::WHITE));
457 }
458
459 #[test]
460 fn test_set_get_pixel() {
461 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
462
463 fb.set_pixel(5, 5, Rgba::BLUE);
464 assert_eq!(fb.get_pixel(5, 5), Some(Rgba::BLUE));
465
466 assert_eq!(fb.get_pixel(100, 100), None);
468 }
469
470 #[test]
471 fn test_blend_pixel() {
472 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
473 fb.clear(Rgba::WHITE);
474
475 let semi_red = Rgba::new(255, 0, 0, 128);
477 fb.blend_pixel(5, 5, semi_red);
478
479 let result = fb.get_pixel(5, 5).expect("operation should succeed");
480 assert!(result.r > 200);
482 assert!(result.g > 100);
483 assert!(result.b > 100);
484 }
485
486 #[test]
487 fn test_blend_over() {
488 let mut fb1 = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
489 let mut fb2 = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
490
491 fb1.clear(Rgba::BLACK);
492 fb2.clear(Rgba::WHITE);
493
494 fb1.blend_over(&fb2, 0.5).expect("operation should succeed");
495
496 let result = fb1.get_pixel(50, 50).expect("operation should succeed");
497 assert!(result.r > 100 && result.r < 150);
499 assert!(result.g > 100 && result.g < 150);
500 assert!(result.b > 100 && result.b < 150);
501 }
502
503 #[test]
504 fn test_adjust_brightness() {
505 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
506 fb.clear(Rgba::rgb(100, 100, 100));
507
508 fb.adjust_brightness(2.0);
509
510 let result = fb.get_pixel(5, 5).expect("operation should succeed");
511 assert_eq!(result.r, 200);
512 assert_eq!(result.g, 200);
513 assert_eq!(result.b, 200);
514 }
515
516 #[test]
517 fn test_luminance_stats() {
518 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
519 fb.clear(Rgba::rgb(128, 128, 128));
520
521 let (min, max, mean) = fb.luminance_stats();
522
523 assert!((min - max).abs() < 1.0);
525 assert!((mean - min).abs() < 1.0);
526 }
527
528 #[test]
529 fn test_row_access() {
530 let mut fb = Framebuffer::new(10, 5).expect("framebuffer creation should succeed");
531 fb.clear(Rgba::BLACK);
532
533 if let Some(row) = fb.row_mut(2) {
535 for chunk in row.chunks_exact_mut(4) {
536 chunk[0] = 255; }
538 }
539
540 assert_eq!(fb.get_pixel(5, 2).expect("value should be present").r, 255);
542 assert_eq!(fb.get_pixel(5, 1).expect("value should be present").r, 0);
543 }
544
545 #[test]
546 fn test_backend_selection() {
547 let backend = Framebuffer::backend();
548 println!("Selected backend: {backend:?}");
550 }
551
552 #[test]
553 fn test_pixels_access() {
554 let fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
555 let pixels = fb.pixels();
556 assert_eq!(pixels.len(), fb.stride() * 10);
558 assert!(pixels.len() >= 10 * 10 * 4);
560 }
561
562 #[test]
563 fn test_pixels_mut_access() {
564 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
565 let expected_size = fb.stride() * 10;
567 let pixels = fb.pixels_mut();
568 assert_eq!(pixels.len(), expected_size);
569 pixels[0] = 255;
571 pixels[1] = 0;
572 pixels[2] = 0;
573 pixels[3] = 255;
574 assert_eq!(fb.get_pixel(0, 0), Some(Rgba::RED));
575 }
576
577 #[test]
578 fn test_row_out_of_bounds() {
579 let fb = Framebuffer::new(10, 5).expect("framebuffer creation should succeed");
580 assert!(fb.row(5).is_none());
581 assert!(fb.row(100).is_none());
582 }
583
584 #[test]
585 fn test_row_mut_out_of_bounds() {
586 let mut fb = Framebuffer::new(10, 5).expect("framebuffer creation should succeed");
587 assert!(fb.row_mut(5).is_none());
588 assert!(fb.row_mut(100).is_none());
589 }
590
591 #[test]
592 fn test_fill_rect_empty() {
593 let mut fb = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
594 fb.clear(Rgba::WHITE);
595 fb.fill_rect(10, 10, 0, 20, Rgba::RED);
597 assert_eq!(fb.get_pixel(10, 10), Some(Rgba::WHITE));
598
599 fb.fill_rect(10, 10, 20, 0, Rgba::RED);
601 assert_eq!(fb.get_pixel(10, 10), Some(Rgba::WHITE));
602 }
603
604 #[test]
605 fn test_fill_rect_out_of_bounds() {
606 let mut fb = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
607 fb.clear(Rgba::WHITE);
608 fb.fill_rect(200, 200, 20, 20, Rgba::RED);
610 assert_eq!(fb.get_pixel(50, 50), Some(Rgba::WHITE));
611 }
612
613 #[test]
614 fn test_blend_pixel_out_of_bounds() {
615 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
616 fb.clear(Rgba::WHITE);
617 fb.blend_pixel(100, 100, Rgba::RED);
619 fb.blend_pixel(10, 5, Rgba::RED);
620 fb.blend_pixel(5, 10, Rgba::RED);
621 assert_eq!(fb.get_pixel(5, 5), Some(Rgba::WHITE));
623 }
624
625 #[test]
626 fn test_blend_over_dimension_mismatch() {
627 let mut fb1 = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
628 let fb2 = Framebuffer::new(50, 50).expect("framebuffer creation should succeed");
629
630 let result = fb1.blend_over(&fb2, 0.5);
631 assert!(result.is_err());
632 }
633
634 #[test]
635 fn test_is_aligned() {
636 let fb = Framebuffer::new(100, 100).expect("framebuffer creation should succeed");
637 let _aligned = fb.is_aligned();
639 }
640
641 #[test]
642 fn test_to_compact_pixels() {
643 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
644 fb.clear(Rgba::RED);
645 let compact = fb.to_compact_pixels();
646 assert_eq!(compact.len(), 10 * 10 * 4);
648 assert_eq!(&compact[0..4], &[255, 0, 0, 255]);
650 }
651
652 #[test]
653 fn test_to_compact_pixels_with_stride() {
654 let mut fb = Framebuffer::new(10, 5).expect("framebuffer creation should succeed");
656 fb.clear(Rgba::GREEN);
657
658 let compact = fb.to_compact_pixels();
659 assert_eq!(compact.len(), 10 * 5 * 4);
660
661 for chunk in compact.chunks_exact(4) {
663 assert_eq!(chunk, &[0, 255, 0, 255]);
664 }
665 }
666
667 #[test]
668 fn test_set_pixel_out_of_bounds() {
669 let mut fb = Framebuffer::new(10, 10).expect("framebuffer creation should succeed");
670 fb.set_pixel(100, 100, Rgba::RED);
672 fb.set_pixel(10, 5, Rgba::RED);
673 fb.set_pixel(5, 10, Rgba::RED);
674 }
675}