Skip to main content

agg_rust/
pixfmt_gray.rs

1//! Grayscale pixel format with alpha blending.
2//!
3//! Port of `agg_pixfmt_gray.h` — pixel format that reads and writes 8-bit
4//! grayscale pixels (1 byte per pixel) with non-premultiplied alpha blending.
5//!
6//! The alpha value comes from the source color only; the buffer stores
7//! only a single gray value channel. Blending treats missing alpha as
8//! fully opaque.
9
10use crate::basics::CoverType;
11use crate::color::Gray8;
12use crate::pixfmt_rgba::PixelFormat;
13use crate::rendering_buffer::RowAccessor;
14
15/// Bytes per pixel for Gray8.
16const BPP: usize = 1;
17
18/// Pixel format for non-premultiplied Gray8 (1 byte per pixel).
19///
20/// Port of C++ `pixfmt_alpha_blend_gray<blender_gray<gray8>, rendering_buf, 1, 0>`.
21/// Each pixel is a single byte representing luminance.
22///
23/// Since there is no alpha channel stored in the buffer, `pixel()` always
24/// returns `a=255`. Blending uses the source color's alpha to interpolate
25/// the gray value.
26pub struct PixfmtGray8<'a> {
27    rbuf: &'a mut RowAccessor,
28}
29
30impl<'a> PixfmtGray8<'a> {
31    pub fn new(rbuf: &'a mut RowAccessor) -> Self {
32        Self { rbuf }
33    }
34
35    /// Clear the entire buffer to a solid gray value.
36    pub fn clear(&mut self, c: &Gray8) {
37        let w = self.rbuf.width();
38        let h = self.rbuf.height();
39        for y in 0..h {
40            let row = unsafe {
41                let ptr = self.rbuf.row_ptr(y as i32);
42                std::slice::from_raw_parts_mut(ptr, w as usize * BPP)
43            };
44            for x in 0..w as usize {
45                row[x] = c.v;
46            }
47        }
48    }
49
50    /// Blend a single pixel (internal helper, no bounds checking).
51    #[inline]
52    fn blend_pix(p: &mut u8, cv: u8, alpha: u8) {
53        *p = Gray8::lerp(*p, cv, alpha);
54    }
55}
56
57impl<'a> PixelFormat for PixfmtGray8<'a> {
58    type ColorType = Gray8;
59
60    fn width(&self) -> u32 {
61        self.rbuf.width()
62    }
63
64    fn height(&self) -> u32 {
65        self.rbuf.height()
66    }
67
68    fn pixel(&self, x: i32, y: i32) -> Gray8 {
69        let row = unsafe {
70            let ptr = self.rbuf.row_ptr(y);
71            std::slice::from_raw_parts(ptr, self.rbuf.width() as usize * BPP)
72        };
73        Gray8::new(row[x as usize] as u32, 255)
74    }
75
76    fn copy_pixel(&mut self, x: i32, y: i32, c: &Gray8) {
77        let row = unsafe {
78            let ptr = self.rbuf.row_ptr(y);
79            std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
80        };
81        row[x as usize] = c.v;
82    }
83
84    fn copy_hline(&mut self, x: i32, y: i32, len: u32, c: &Gray8) {
85        let row = unsafe {
86            let ptr = self.rbuf.row_ptr(y);
87            std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
88        };
89        for i in 0..len as usize {
90            row[x as usize + i] = c.v;
91        }
92    }
93
94    fn blend_pixel(&mut self, x: i32, y: i32, c: &Gray8, cover: CoverType) {
95        let row = unsafe {
96            let ptr = self.rbuf.row_ptr(y);
97            std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
98        };
99        let alpha = Gray8::mult_cover(c.a, cover);
100        if alpha == 255 {
101            row[x as usize] = c.v;
102        } else if alpha > 0 {
103            Self::blend_pix(&mut row[x as usize], c.v, alpha);
104        }
105    }
106
107    fn blend_hline(&mut self, x: i32, y: i32, len: u32, c: &Gray8, cover: CoverType) {
108        let row = unsafe {
109            let ptr = self.rbuf.row_ptr(y);
110            std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
111        };
112        let alpha = Gray8::mult_cover(c.a, cover);
113        if alpha == 255 {
114            for i in 0..len as usize {
115                row[x as usize + i] = c.v;
116            }
117        } else if alpha > 0 {
118            for i in 0..len as usize {
119                Self::blend_pix(&mut row[x as usize + i], c.v, alpha);
120            }
121        }
122    }
123
124    fn blend_solid_hspan(&mut self, x: i32, y: i32, len: u32, c: &Gray8, covers: &[CoverType]) {
125        let row = unsafe {
126            let ptr = self.rbuf.row_ptr(y);
127            std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
128        };
129        for (i, &cov) in covers.iter().enumerate().take(len as usize) {
130            let alpha = Gray8::mult_cover(c.a, cov);
131            if alpha == 255 {
132                row[x as usize + i] = c.v;
133            } else if alpha > 0 {
134                Self::blend_pix(&mut row[x as usize + i], c.v, alpha);
135            }
136        }
137    }
138
139    fn blend_color_hspan(
140        &mut self,
141        x: i32,
142        y: i32,
143        len: u32,
144        colors: &[Gray8],
145        covers: &[CoverType],
146        cover: CoverType,
147    ) {
148        let row = unsafe {
149            let ptr = self.rbuf.row_ptr(y);
150            std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
151        };
152        if !covers.is_empty() {
153            for i in 0..len as usize {
154                let c = &colors[i];
155                let alpha = Gray8::mult_cover(c.a, covers[i]);
156                if alpha == 255 {
157                    row[x as usize + i] = c.v;
158                } else if alpha > 0 {
159                    Self::blend_pix(&mut row[x as usize + i], c.v, alpha);
160                }
161            }
162        } else if cover == 255 {
163            for (i, c) in colors.iter().enumerate().take(len as usize) {
164                if c.a == 255 {
165                    row[x as usize + i] = c.v;
166                } else if c.a > 0 {
167                    Self::blend_pix(&mut row[x as usize + i], c.v, c.a);
168                }
169            }
170        } else {
171            for (i, c) in colors.iter().enumerate().take(len as usize) {
172                let alpha = Gray8::mult_cover(c.a, cover);
173                if alpha == 255 {
174                    row[x as usize + i] = c.v;
175                } else if alpha > 0 {
176                    Self::blend_pix(&mut row[x as usize + i], c.v, alpha);
177                }
178            }
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
188        let stride = w as i32;
189        let buf = vec![0u8; (h * w) as usize];
190        let mut ra = RowAccessor::new();
191        unsafe {
192            ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
193        }
194        (buf, ra)
195    }
196
197    #[test]
198    fn test_new() {
199        let (_buf, mut ra) = make_buffer(100, 100);
200        let pf = PixfmtGray8::new(&mut ra);
201        assert_eq!(pf.width(), 100);
202        assert_eq!(pf.height(), 100);
203    }
204
205    #[test]
206    fn test_copy_pixel() {
207        let (_buf, mut ra) = make_buffer(10, 10);
208        let mut pf = PixfmtGray8::new(&mut ra);
209        let white = Gray8::new(255, 255);
210        pf.copy_pixel(5, 5, &white);
211        let p = pf.pixel(5, 5);
212        assert_eq!(p.v, 255);
213        assert_eq!(p.a, 255);
214    }
215
216    #[test]
217    fn test_copy_hline() {
218        let (_buf, mut ra) = make_buffer(20, 10);
219        let mut pf = PixfmtGray8::new(&mut ra);
220        let mid = Gray8::new(128, 255);
221        pf.copy_hline(5, 3, 10, &mid);
222        for x in 5..15 {
223            let p = pf.pixel(x, 3);
224            assert_eq!(p.v, 128);
225        }
226        let p = pf.pixel(4, 3);
227        assert_eq!(p.v, 0);
228    }
229
230    #[test]
231    fn test_blend_pixel_opaque() {
232        let (_buf, mut ra) = make_buffer(10, 10);
233        let mut pf = PixfmtGray8::new(&mut ra);
234        let c = Gray8::new(200, 255);
235        pf.blend_pixel(3, 3, &c, 255);
236        let p = pf.pixel(3, 3);
237        assert_eq!(p.v, 200);
238    }
239
240    #[test]
241    fn test_blend_pixel_semitransparent() {
242        let (_buf, mut ra) = make_buffer(10, 10);
243        let mut pf = PixfmtGray8::new(&mut ra);
244        // Start with white background
245        let white = Gray8::new(255, 255);
246        pf.copy_hline(0, 0, 10, &white);
247
248        // Blend 50% black over white
249        let black_50 = Gray8::new(0, 128);
250        pf.blend_pixel(5, 0, &black_50, 255);
251        let p = pf.pixel(5, 0);
252        // Should be midway between 255 and 0 → ~128
253        assert!(p.v > 120 && p.v < 140, "v={}", p.v);
254    }
255
256    #[test]
257    fn test_blend_hline() {
258        let (_buf, mut ra) = make_buffer(20, 10);
259        let mut pf = PixfmtGray8::new(&mut ra);
260        let c = Gray8::new(100, 255);
261        pf.blend_hline(2, 2, 5, &c, 255);
262        for x in 2..7 {
263            let p = pf.pixel(x, 2);
264            assert_eq!(p.v, 100);
265        }
266    }
267
268    #[test]
269    fn test_blend_solid_hspan() {
270        let (_buf, mut ra) = make_buffer(20, 10);
271        let mut pf = PixfmtGray8::new(&mut ra);
272        let c = Gray8::new(200, 255);
273        let covers = [255u8, 128, 64, 0];
274        pf.blend_solid_hspan(0, 0, 4, &c, &covers);
275
276        let p0 = pf.pixel(0, 0);
277        assert_eq!(p0.v, 200); // full cover
278
279        let p3 = pf.pixel(3, 0);
280        assert_eq!(p3.v, 0); // zero cover, no change
281    }
282
283    #[test]
284    fn test_clear() {
285        let (_buf, mut ra) = make_buffer(10, 10);
286        let mut pf = PixfmtGray8::new(&mut ra);
287        pf.clear(&Gray8::new(128, 255));
288        let p = pf.pixel(5, 5);
289        assert_eq!(p.v, 128);
290    }
291
292    #[test]
293    fn test_pixel_always_opaque() {
294        let (_buf, mut ra) = make_buffer(10, 10);
295        let pf = PixfmtGray8::new(&mut ra);
296        let p = pf.pixel(0, 0);
297        assert_eq!(p.a, 255);
298    }
299
300    #[test]
301    fn test_blend_color_hspan_with_covers() {
302        let (_buf, mut ra) = make_buffer(20, 10);
303        let mut pf = PixfmtGray8::new(&mut ra);
304        let colors = [
305            Gray8::new(100, 255),
306            Gray8::new(200, 255),
307            Gray8::new(50, 128),
308        ];
309        let covers = [255u8, 255, 255];
310        pf.blend_color_hspan(0, 0, 3, &colors, &covers, 255);
311
312        assert_eq!(pf.pixel(0, 0).v, 100);
313        assert_eq!(pf.pixel(1, 0).v, 200);
314        // Third pixel: alpha=mult_cover(128, 255)=128, blended from 0→50
315        let p2 = pf.pixel(2, 0);
316        assert!(p2.v > 20 && p2.v < 35, "v={}", p2.v);
317    }
318}