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