nexcore_softrender/pipeline/
framebuffer.rs1use crate::math::Color;
6
7#[derive(Debug, Clone)]
8#[non_exhaustive]
9pub struct Framebuffer {
10 pub width: u32,
11 pub height: u32,
12 pub pixels: Vec<u32>,
14}
15
16impl Framebuffer {
17 pub fn new(width: u32, height: u32) -> Self {
18 Self {
19 width,
20 height,
21 pixels: vec![0xFF000000; (width * height) as usize], }
23 }
24
25 pub fn len(&self) -> usize {
27 self.pixels.len()
28 }
29
30 pub fn is_empty(&self) -> bool {
31 self.pixels.is_empty()
32 }
33
34 pub fn clear(&mut self, color: Color) {
36 let packed = color.to_argb_u32();
37 self.pixels.fill(packed);
38 }
39
40 pub fn set_pixel(&mut self, x: u32, y: u32, color: Color) {
42 if x < self.width && y < self.height {
43 let idx = (y * self.width + x) as usize;
44 self.pixels[idx] = color.to_argb_u32();
45 }
46 }
47
48 pub fn blend_pixel(&mut self, x: u32, y: u32, src: Color) {
50 if x < self.width && y < self.height {
51 let idx = (y * self.width + x) as usize;
52 let dst = Color::from_argb_u32(self.pixels[idx]);
53 self.pixels[idx] = src.alpha_over(dst).to_argb_u32();
54 }
55 }
56
57 pub fn get_pixel(&self, x: u32, y: u32) -> Color {
59 if x < self.width && y < self.height {
60 let idx = (y * self.width + x) as usize;
61 Color::from_argb_u32(self.pixels[idx])
62 } else {
63 Color::TRANSPARENT
64 }
65 }
66
67 pub fn to_rgba_bytes(&self) -> Vec<u8> {
69 let mut bytes = Vec::with_capacity(self.pixels.len() * 4);
70 for &pixel in &self.pixels {
71 bytes.push(((pixel >> 16) & 0xFF) as u8); bytes.push(((pixel >> 8) & 0xFF) as u8); bytes.push((pixel & 0xFF) as u8); bytes.push(((pixel >> 24) & 0xFF) as u8); }
76 bytes
77 }
78
79 pub fn to_bgra_bytes(&self) -> Vec<u8> {
81 let mut bytes = Vec::with_capacity(self.pixels.len() * 4);
82 for &pixel in &self.pixels {
83 bytes.push((pixel & 0xFF) as u8); bytes.push(((pixel >> 8) & 0xFF) as u8); bytes.push(((pixel >> 16) & 0xFF) as u8); bytes.push(((pixel >> 24) & 0xFF) as u8); }
88 bytes
89 }
90
91 pub fn blit(&mut self, src: &Framebuffer, dx: i32, dy: i32) {
93 for sy in 0..src.height {
94 let ty = dy + sy as i32;
95 if ty < 0 || ty >= self.height as i32 {
96 continue;
97 }
98 for sx in 0..src.width {
99 let tx = dx + sx as i32;
100 if tx < 0 || tx >= self.width as i32 {
101 continue;
102 }
103 let src_color = src.get_pixel(sx, sy);
104 self.blend_pixel(tx as u32, ty as u32, src_color);
105 }
106 }
107 }
108}
109
110#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn new_framebuffer_is_black() {
120 let fb = Framebuffer::new(100, 100);
121 assert_eq!(fb.len(), 10000);
122 let c = fb.get_pixel(50, 50);
123 assert!((c.r - 0.0).abs() < 0.01);
124 assert!((c.a - 1.0).abs() < 0.01);
125 }
126
127 #[test]
128 fn set_and_get_pixel() {
129 let mut fb = Framebuffer::new(10, 10);
130 fb.set_pixel(5, 5, Color::RED);
131 let c = fb.get_pixel(5, 5);
132 assert!((c.r - 1.0).abs() < 0.01);
133 assert!((c.g - 0.0).abs() < 0.01);
134 }
135
136 #[test]
137 fn out_of_bounds_noop() {
138 let mut fb = Framebuffer::new(10, 10);
139 fb.set_pixel(100, 100, Color::RED); let c = fb.get_pixel(100, 100);
141 assert!((c.a - 0.0).abs() < 0.01); }
143
144 #[test]
145 fn clear_to_color() {
146 let mut fb = Framebuffer::new(10, 10);
147 fb.clear(Color::BLUE);
148 let c = fb.get_pixel(0, 0);
149 assert!((c.b - 1.0).abs() < 0.01);
150 }
151
152 #[test]
153 fn rgba_bytes_length() {
154 let fb = Framebuffer::new(8, 8);
155 assert_eq!(fb.to_rgba_bytes().len(), 8 * 8 * 4);
156 }
157
158 #[test]
159 fn blit_small_onto_large() {
160 let mut dst = Framebuffer::new(20, 20);
161 dst.clear(Color::BLACK);
162
163 let mut src = Framebuffer::new(5, 5);
164 src.clear(Color::RED);
165
166 dst.blit(&src, 10, 10);
167
168 let c = dst.get_pixel(12, 12);
170 assert!((c.r - 1.0).abs() < 0.01);
171
172 let c2 = dst.get_pixel(0, 0);
174 assert!((c2.r - 0.0).abs() < 0.01);
175 }
176}