1use crate::headless::RgbaBuffer;
4use oxiui_core::Color;
5
6pub fn pack(c: &Color) -> u32 {
8 let Color(r, g, b, a) = *c;
9 ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
10}
11
12pub fn unpack(px: u32) -> (u8, u8, u8, u8) {
14 let a = ((px >> 24) & 0xFF) as u8;
15 let r = ((px >> 16) & 0xFF) as u8;
16 let g = ((px >> 8) & 0xFF) as u8;
17 let b = (px & 0xFF) as u8;
18 (r, g, b, a)
19}
20
21pub fn pack_rgba(r: u8, g: u8, b: u8, a: u8) -> u32 {
23 ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
24}
25
26#[derive(Clone, Debug)]
28pub struct Framebuffer {
29 width: u32,
30 height: u32,
31 pixels: Vec<u32>,
32}
33
34impl Framebuffer {
35 pub fn new(width: u32, height: u32) -> Self {
37 Self {
38 width,
39 height,
40 pixels: vec![0u32; (width * height) as usize],
41 }
42 }
43
44 pub fn with_fill(width: u32, height: u32, color: Color) -> Self {
46 Self {
47 width,
48 height,
49 pixels: vec![pack(&color); (width * height) as usize],
50 }
51 }
52
53 pub fn width(&self) -> u32 {
55 self.width
56 }
57
58 pub fn height(&self) -> u32 {
60 self.height
61 }
62
63 pub fn pixels(&self) -> &[u32] {
65 &self.pixels
66 }
67
68 pub fn pixels_mut(&mut self) -> &mut [u32] {
70 &mut self.pixels
71 }
72
73 pub fn clear(&mut self, color: Color) {
75 let v = pack(&color);
76 for px in &mut self.pixels {
77 *px = v;
78 }
79 }
80
81 #[inline]
82 fn index(&self, x: u32, y: u32) -> Option<usize> {
83 if x < self.width && y < self.height {
84 Some((y * self.width + x) as usize)
85 } else {
86 None
87 }
88 }
89
90 pub fn get(&self, x: u32, y: u32) -> Option<u32> {
92 self.index(x, y).map(|i| self.pixels[i])
93 }
94
95 pub fn get_rgba(&self, x: u32, y: u32) -> Option<(u8, u8, u8, u8)> {
97 self.get(x, y).map(unpack)
98 }
99
100 pub fn set(&mut self, x: u32, y: u32, px: u32) {
103 if let Some(i) = self.index(x, y) {
104 self.pixels[i] = px;
105 }
106 }
107
108 pub fn row(&self, y: u32) -> Option<&[u32]> {
110 if y < self.height {
111 let start = (y * self.width) as usize;
112 Some(&self.pixels[start..start + self.width as usize])
113 } else {
114 None
115 }
116 }
117
118 pub fn blend(&mut self, x: u32, y: u32, src: u32) {
121 let i = match self.index(x, y) {
122 Some(i) => i,
123 None => return,
124 };
125 let (sr, sg, sb, sa) = unpack(src);
126 if sa == 0 {
127 return;
128 }
129 if sa == 255 {
130 self.pixels[i] = src;
131 return;
132 }
133 let (dr, dg, db, da) = unpack(self.pixels[i]);
134 let sa_f = sa as f32 / 255.0;
135 let da_f = da as f32 / 255.0;
136 let out_a = sa_f + da_f * (1.0 - sa_f);
137 if out_a <= 0.0 {
138 self.pixels[i] = 0;
139 return;
140 }
141 let blend_ch = |s: u8, d: u8| -> u8 {
142 let s = s as f32 / 255.0;
143 let d = d as f32 / 255.0;
144 let v = (s * sa_f + d * da_f * (1.0 - sa_f)) / out_a;
145 (v.clamp(0.0, 1.0) * 255.0).round() as u8
146 };
147 let r = blend_ch(sr, dr);
148 let g = blend_ch(sg, dg);
149 let b = blend_ch(sb, db);
150 let a = (out_a.clamp(0.0, 1.0) * 255.0).round() as u8;
151 self.pixels[i] = pack_rgba(r, g, b, a);
152 }
153
154 pub fn blend_coverage(&mut self, x: u32, y: u32, c: &Color, coverage: f32) {
158 let cov = coverage.clamp(0.0, 1.0);
159 if cov <= 0.0 {
160 return;
161 }
162 let Color(r, g, b, a) = *c;
163 let a = (a as f32 * cov).round() as u8;
164 self.blend(x, y, pack_rgba(r, g, b, a));
165 }
166
167 pub fn to_rgba_buffer(&self) -> RgbaBuffer {
169 let mut data = Vec::with_capacity(self.pixels.len() * 4);
170 for &px in &self.pixels {
171 let (r, g, b, a) = unpack(px);
172 data.extend_from_slice(&[r, g, b, a]);
173 }
174 RgbaBuffer {
175 width: self.width,
176 height: self.height,
177 data,
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn pack_unpack_roundtrip() {
188 let c = Color(10, 20, 30, 200);
189 let packed = pack(&c);
190 assert_eq!(unpack(packed), (10, 20, 30, 200));
191 }
192
193 #[test]
194 fn fill_and_get() {
195 let fb = Framebuffer::with_fill(4, 4, Color(255, 0, 0, 255));
196 assert_eq!(fb.get_rgba(0, 0), Some((255, 0, 0, 255)));
197 assert_eq!(fb.get_rgba(3, 3), Some((255, 0, 0, 255)));
198 assert_eq!(fb.get(4, 4), None);
199 }
200
201 #[test]
202 fn opaque_blend_overwrites() {
203 let mut fb = Framebuffer::with_fill(2, 2, Color(0, 0, 0, 255));
204 fb.blend(0, 0, pack(&Color(255, 255, 255, 255)));
205 assert_eq!(fb.get_rgba(0, 0), Some((255, 255, 255, 255)));
206 }
207
208 #[test]
209 fn half_alpha_blend() {
210 let mut fb = Framebuffer::with_fill(1, 1, Color(0, 0, 0, 255));
212 fb.blend(0, 0, pack(&Color(255, 255, 255, 128)));
213 let (r, g, b, a) = fb.get_rgba(0, 0).expect("pixel");
214 assert!((120..=135).contains(&r), "r={r}");
215 assert_eq!(g, r);
216 assert_eq!(b, r);
217 assert_eq!(a, 255);
218 }
219
220 #[test]
221 fn transparent_blend_is_noop() {
222 let mut fb = Framebuffer::with_fill(1, 1, Color(10, 20, 30, 255));
223 fb.blend(0, 0, pack(&Color(255, 255, 255, 0)));
224 assert_eq!(fb.get_rgba(0, 0), Some((10, 20, 30, 255)));
225 }
226
227 #[test]
228 fn coverage_scales_alpha() {
229 let mut fb = Framebuffer::with_fill(1, 1, Color(0, 0, 0, 255));
230 fb.blend_coverage(0, 0, &Color(255, 255, 255, 255), 0.0);
232 assert_eq!(fb.get_rgba(0, 0), Some((0, 0, 0, 255)));
233 fb.blend_coverage(0, 0, &Color(255, 255, 255, 255), 1.0);
235 assert_eq!(fb.get_rgba(0, 0), Some((255, 255, 255, 255)));
236 }
237
238 #[test]
239 fn to_rgba_buffer_layout() {
240 let fb = Framebuffer::with_fill(2, 1, Color(1, 2, 3, 4));
241 let rgba = fb.to_rgba_buffer();
242 assert_eq!(rgba.width, 2);
243 assert_eq!(rgba.data, vec![1, 2, 3, 4, 1, 2, 3, 4]);
244 }
245}