stipple_render/
surface.rs1use stipple_geometry::{PhysicalSize, Rect};
2
3#[derive(Clone, Debug)]
9pub struct Pixmap {
10 size: PhysicalSize,
11 data: Vec<u8>,
12}
13
14impl Pixmap {
15 pub fn new(size: PhysicalSize) -> Self {
17 Self {
18 size,
19 data: vec![0u8; size.pixel_count() as usize * 4],
20 }
21 }
22
23 pub fn from_rgba8(size: PhysicalSize, data: Vec<u8>) -> Self {
25 assert_eq!(
26 data.len(),
27 size.pixel_count() as usize * 4,
28 "pixmap byte length must equal width*height*4"
29 );
30 Self { size, data }
31 }
32
33 #[inline]
34 pub fn size(&self) -> PhysicalSize {
35 self.size
36 }
37
38 #[inline]
40 pub fn stride(&self) -> usize {
41 self.size.width as usize * 4
42 }
43
44 #[inline]
45 pub fn as_bytes(&self) -> &[u8] {
46 &self.data
47 }
48
49 #[inline]
50 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
51 &mut self.data
52 }
53
54 pub fn blit(&mut self, src: &Pixmap, dst_x: u32, dst_y: u32) {
61 let dst_w = self.size.width;
62 let dst_h = self.size.height;
63 let copy_w = src.size.width.min(dst_w.saturating_sub(dst_x));
65 let copy_h = src.size.height.min(dst_h.saturating_sub(dst_y));
66 if copy_w == 0 || copy_h == 0 {
67 return;
68 }
69 let (src_stride, dst_stride) = (src.stride(), self.stride());
70 let row_bytes = copy_w as usize * 4;
71 for row in 0..copy_h as usize {
72 let s = row * src_stride;
73 let d = (dst_y as usize + row) * dst_stride + dst_x as usize * 4;
74 self.data[d..d + row_bytes].copy_from_slice(&src.data[s..s + row_bytes]);
75 }
76 }
77
78 pub fn pixel(&self, x: u32, y: u32) -> Option<[u8; 4]> {
80 if x >= self.size.width || y >= self.size.height {
81 return None;
82 }
83 let i = y as usize * self.stride() + x as usize * 4;
84 Some([
85 self.data[i],
86 self.data[i + 1],
87 self.data[i + 2],
88 self.data[i + 3],
89 ])
90 }
91}
92
93pub trait Surface {
102 fn resize(&mut self, size: PhysicalSize);
104
105 fn size(&self) -> PhysicalSize;
107
108 fn present(&mut self, pixmap: &Pixmap, damage: &[Rect]);
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn pixel_access_bounds() {
118 let mut pm = Pixmap::new(PhysicalSize::new(2, 2));
119 pm.as_bytes_mut()[4..8].copy_from_slice(&[10, 20, 30, 40]);
120 assert_eq!(pm.pixel(1, 0), Some([10, 20, 30, 40]));
121 assert_eq!(pm.pixel(2, 0), None);
122 assert_eq!(pm.stride(), 8);
123 }
124
125 #[test]
126 fn blit_composites_at_offset() {
127 let mut dst = Pixmap::new(PhysicalSize::new(4, 4));
128 let mut src = Pixmap::new(PhysicalSize::new(2, 2));
129 for px in src.as_bytes_mut().chunks_exact_mut(4) {
130 px.copy_from_slice(&[1, 2, 3, 4]);
131 }
132 dst.blit(&src, 1, 1);
133 assert_eq!(dst.pixel(0, 0), Some([0, 0, 0, 0]));
135 assert_eq!(dst.pixel(1, 1), Some([1, 2, 3, 4]));
136 assert_eq!(dst.pixel(2, 2), Some([1, 2, 3, 4]));
137 assert_eq!(dst.pixel(3, 3), Some([0, 0, 0, 0]));
138 }
139
140 #[test]
141 fn blit_clips_to_destination_bounds() {
142 let mut dst = Pixmap::new(PhysicalSize::new(2, 2));
143 let mut src = Pixmap::new(PhysicalSize::new(2, 2));
144 for px in src.as_bytes_mut().chunks_exact_mut(4) {
145 px.copy_from_slice(&[9, 9, 9, 9]);
146 }
147 dst.blit(&src, 1, 1);
149 assert_eq!(dst.pixel(1, 1), Some([9, 9, 9, 9]));
150 assert_eq!(dst.pixel(0, 0), Some([0, 0, 0, 0]));
151 dst.blit(&src, 5, 5);
153 }
154}