Skip to main content

agg_rust/
pixfmt_rgb.rs

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