Skip to main content

agg_rust/
pixfmt_rgba.rs

1//! RGBA pixel format with alpha blending.
2//!
3//! Port of `agg_pixfmt_rgba.h` — pixel format that reads and writes RGBA32
4//! pixels (4 bytes per pixel, non-premultiplied alpha) with alpha blending.
5//!
6//! Provides the `PixelFormat` trait and `PixfmtRgba32` concrete implementation.
7
8use crate::basics::CoverType;
9use crate::color::Rgba8;
10use crate::rendering_buffer::RowAccessor;
11
12// ============================================================================
13// PixelFormat trait
14// ============================================================================
15
16/// Trait for pixel format renderers that can blend colors into a rendering buffer.
17///
18/// This is the abstraction layer between the renderer and the raw pixel data.
19/// Different implementations handle different pixel layouts (RGBA, RGB, gray)
20/// and blending modes (premultiplied, non-premultiplied).
21pub trait PixelFormat {
22    type ColorType;
23
24    fn width(&self) -> u32;
25    fn height(&self) -> u32;
26
27    /// Blend a single pixel at (x, y) with color `c` and coverage `cover`.
28    fn blend_pixel(&mut self, x: i32, y: i32, c: &Self::ColorType, cover: CoverType);
29
30    /// Blend a horizontal line of `len` pixels at (x, y) with uniform color and coverage.
31    fn blend_hline(&mut self, x: i32, y: i32, len: u32, c: &Self::ColorType, cover: CoverType);
32
33    /// Blend a horizontal span of `len` pixels with per-pixel coverage values.
34    fn blend_solid_hspan(
35        &mut self,
36        x: i32,
37        y: i32,
38        len: u32,
39        c: &Self::ColorType,
40        covers: &[CoverType],
41    );
42
43    /// Copy (overwrite) a horizontal line of `len` pixels with color `c`.
44    fn copy_hline(&mut self, x: i32, y: i32, len: u32, c: &Self::ColorType);
45
46    /// Copy (overwrite) a single pixel at (x, y) with color `c`.
47    fn copy_pixel(&mut self, x: i32, y: i32, c: &Self::ColorType);
48
49    /// Blend a horizontal span with per-pixel colors and optional per-pixel coverage.
50    ///
51    /// If `covers` is non-empty, each pixel uses its corresponding coverage.
52    /// If `covers` is empty, all pixels use the uniform `cover` value.
53    fn blend_color_hspan(
54        &mut self,
55        x: i32,
56        y: i32,
57        len: u32,
58        colors: &[Self::ColorType],
59        covers: &[CoverType],
60        cover: CoverType,
61    );
62
63    /// Get the pixel color at (x, y).
64    fn pixel(&self, x: i32, y: i32) -> Self::ColorType;
65}
66
67// ============================================================================
68// PixfmtRgba32 — non-premultiplied RGBA, 8 bits per channel
69// ============================================================================
70
71/// Pixel format for non-premultiplied RGBA32 (4 bytes per pixel).
72///
73/// Port of C++ `pixfmt_alpha_blend_rgba<blender_rgba32, rendering_buf>`.
74/// Component order: R=0, G=1, B=2, A=3 (standard RGBA).
75///
76/// Blending uses the `Rgba8` utility methods (`lerp`, `mult_cover`, etc.)
77/// which match the C++ blender functions.
78pub struct PixfmtRgba32<'a> {
79    rbuf: &'a mut RowAccessor,
80}
81
82const BPP: usize = 4; // bytes per pixel
83
84impl<'a> PixfmtRgba32<'a> {
85    pub fn new(rbuf: &'a mut RowAccessor) -> Self {
86        Self { rbuf }
87    }
88
89    /// Clear the entire buffer to a solid color.
90    pub fn clear(&mut self, c: &Rgba8) {
91        let w = self.rbuf.width();
92        let h = self.rbuf.height();
93        for y in 0..h {
94            let row = unsafe {
95                let ptr = self.rbuf.row_ptr(y as i32);
96                std::slice::from_raw_parts_mut(ptr, (w as usize) * BPP)
97            };
98            for x in 0..w as usize {
99                let off = x * BPP;
100                row[off] = c.r;
101                row[off + 1] = c.g;
102                row[off + 2] = c.b;
103                row[off + 3] = c.a;
104            }
105        }
106    }
107
108    /// Blend a single pixel (internal helper, no bounds checking).
109    #[inline]
110    fn blend_pix(p: &mut [u8], cr: u8, cg: u8, cb: u8, alpha: u8) {
111        p[0] = Rgba8::lerp(p[0], cr, alpha);
112        p[1] = Rgba8::lerp(p[1], cg, alpha);
113        p[2] = Rgba8::lerp(p[2], cb, alpha);
114        p[3] = Rgba8::lerp(p[3], 255, alpha);
115    }
116
117    /// Apply inverse gamma correction to every pixel in the buffer.
118    ///
119    /// For each pixel, applies `gamma.inv()` to the R, G, B channels,
120    /// leaving the alpha channel unchanged. This matches the C++
121    /// `pixfmt_rgb24::apply_gamma_inv()` from `agg_pixfmt_rgb.h`.
122    pub fn apply_gamma_inv(&mut self, gamma: &crate::gamma::GammaLut) {
123        let w = self.rbuf.width();
124        let h = self.rbuf.height();
125        for y in 0..h {
126            let row = unsafe {
127                let ptr = self.rbuf.row_ptr(y as i32);
128                std::slice::from_raw_parts_mut(ptr, (w as usize) * BPP)
129            };
130            for x in 0..w as usize {
131                let off = x * BPP;
132                row[off] = gamma.inv(row[off]);
133                row[off + 1] = gamma.inv(row[off + 1]);
134                row[off + 2] = gamma.inv(row[off + 2]);
135                // row[off + 3] (alpha) is left unchanged
136            }
137        }
138    }
139
140    /// Apply forward gamma correction to every pixel in the buffer.
141    ///
142    /// For each pixel, applies `gamma.dir()` to the R, G, B channels,
143    /// leaving the alpha channel unchanged.
144    pub fn apply_gamma_dir(&mut self, gamma: &crate::gamma::GammaLut) {
145        let w = self.rbuf.width();
146        let h = self.rbuf.height();
147        for y in 0..h {
148            let row = unsafe {
149                let ptr = self.rbuf.row_ptr(y as i32);
150                std::slice::from_raw_parts_mut(ptr, (w as usize) * BPP)
151            };
152            for x in 0..w as usize {
153                let off = x * BPP;
154                row[off] = gamma.dir(row[off]);
155                row[off + 1] = gamma.dir(row[off + 1]);
156                row[off + 2] = gamma.dir(row[off + 2]);
157            }
158        }
159    }
160}
161
162impl<'a> PixelFormat for PixfmtRgba32<'a> {
163    type ColorType = Rgba8;
164
165    fn width(&self) -> u32 {
166        self.rbuf.width()
167    }
168
169    fn height(&self) -> u32 {
170        self.rbuf.height()
171    }
172
173    fn pixel(&self, x: i32, y: i32) -> Rgba8 {
174        let row = unsafe {
175            let ptr = self.rbuf.row_ptr(y);
176            std::slice::from_raw_parts(ptr, (self.rbuf.width() as usize) * BPP)
177        };
178        let off = x as usize * BPP;
179        Rgba8::new(
180            row[off] as u32,
181            row[off + 1] as u32,
182            row[off + 2] as u32,
183            row[off + 3] as u32,
184        )
185    }
186
187    fn copy_pixel(&mut self, x: i32, y: i32, c: &Rgba8) {
188        let row = unsafe {
189            let ptr = self.rbuf.row_ptr(y);
190            std::slice::from_raw_parts_mut(ptr, (self.rbuf.width() as usize) * BPP)
191        };
192        let off = x as usize * BPP;
193        row[off] = c.r;
194        row[off + 1] = c.g;
195        row[off + 2] = c.b;
196        row[off + 3] = c.a;
197    }
198
199    fn copy_hline(&mut self, x: i32, y: i32, len: u32, c: &Rgba8) {
200        let row = unsafe {
201            let ptr = self.rbuf.row_ptr(y);
202            std::slice::from_raw_parts_mut(ptr, (self.rbuf.width() as usize) * BPP)
203        };
204        for i in 0..len as usize {
205            let off = (x as usize + i) * BPP;
206            row[off] = c.r;
207            row[off + 1] = c.g;
208            row[off + 2] = c.b;
209            row[off + 3] = c.a;
210        }
211    }
212
213    fn blend_pixel(&mut self, x: i32, y: i32, c: &Rgba8, cover: CoverType) {
214        let row = unsafe {
215            let ptr = self.rbuf.row_ptr(y);
216            std::slice::from_raw_parts_mut(ptr, (self.rbuf.width() as usize) * BPP)
217        };
218        let off = x as usize * BPP;
219        let alpha = Rgba8::mult_cover(c.a, cover);
220        if alpha == 255 {
221            row[off] = c.r;
222            row[off + 1] = c.g;
223            row[off + 2] = c.b;
224            row[off + 3] = 255;
225        } else {
226            Self::blend_pix(&mut row[off..off + BPP], c.r, c.g, c.b, alpha);
227        }
228    }
229
230    fn blend_hline(&mut self, x: i32, y: i32, len: u32, c: &Rgba8, cover: CoverType) {
231        let row = unsafe {
232            let ptr = self.rbuf.row_ptr(y);
233            std::slice::from_raw_parts_mut(ptr, (self.rbuf.width() as usize) * BPP)
234        };
235        let alpha = Rgba8::mult_cover(c.a, cover);
236        if alpha == 255 {
237            for i in 0..len as usize {
238                let off = (x as usize + i) * BPP;
239                row[off] = c.r;
240                row[off + 1] = c.g;
241                row[off + 2] = c.b;
242                row[off + 3] = 255;
243            }
244        } else {
245            for i in 0..len as usize {
246                let off = (x as usize + i) * BPP;
247                Self::blend_pix(&mut row[off..off + BPP], c.r, c.g, c.b, alpha);
248            }
249        }
250    }
251
252    fn blend_solid_hspan(&mut self, x: i32, y: i32, len: u32, c: &Rgba8, covers: &[CoverType]) {
253        let row = unsafe {
254            let ptr = self.rbuf.row_ptr(y);
255            std::slice::from_raw_parts_mut(ptr, (self.rbuf.width() as usize) * BPP)
256        };
257        for (i, &cov) in covers.iter().enumerate().take(len as usize) {
258            let off = (x as usize + i) * BPP;
259            let alpha = Rgba8::mult_cover(c.a, cov);
260            if alpha == 255 {
261                row[off] = c.r;
262                row[off + 1] = c.g;
263                row[off + 2] = c.b;
264                row[off + 3] = 255;
265            } else if alpha > 0 {
266                Self::blend_pix(&mut row[off..off + BPP], c.r, c.g, c.b, alpha);
267            }
268        }
269    }
270
271    fn blend_color_hspan(
272        &mut self,
273        x: i32,
274        y: i32,
275        len: u32,
276        colors: &[Rgba8],
277        covers: &[CoverType],
278        cover: CoverType,
279    ) {
280        let row = unsafe {
281            let ptr = self.rbuf.row_ptr(y);
282            std::slice::from_raw_parts_mut(ptr, (self.rbuf.width() as usize) * BPP)
283        };
284        if !covers.is_empty() {
285            // Per-pixel coverage from covers array
286            for i in 0..len as usize {
287                let off = (x as usize + i) * BPP;
288                let c = &colors[i];
289                let alpha = Rgba8::mult_cover(c.a, covers[i]);
290                if alpha == 255 {
291                    row[off] = c.r;
292                    row[off + 1] = c.g;
293                    row[off + 2] = c.b;
294                    row[off + 3] = 255;
295                } else if alpha > 0 {
296                    Self::blend_pix(&mut row[off..off + BPP], c.r, c.g, c.b, alpha);
297                }
298            }
299        } else if cover == 255 {
300            // Full coverage, direct copy/blend
301            for (i, c) in colors.iter().enumerate().take(len as usize) {
302                let off = (x as usize + i) * BPP;
303                if c.a == 255 {
304                    row[off] = c.r;
305                    row[off + 1] = c.g;
306                    row[off + 2] = c.b;
307                    row[off + 3] = 255;
308                } else if c.a > 0 {
309                    Self::blend_pix(&mut row[off..off + BPP], c.r, c.g, c.b, c.a);
310                }
311            }
312        } else {
313            // Uniform coverage for all pixels
314            for (i, c) in colors.iter().enumerate().take(len as usize) {
315                let off = (x as usize + i) * BPP;
316                let alpha = Rgba8::mult_cover(c.a, cover);
317                if alpha == 255 {
318                    row[off] = c.r;
319                    row[off + 1] = c.g;
320                    row[off + 2] = c.b;
321                    row[off + 3] = 255;
322                } else if alpha > 0 {
323                    Self::blend_pix(&mut row[off..off + BPP], c.r, c.g, c.b, alpha);
324                }
325            }
326        }
327    }
328}
329
330// ============================================================================
331// Tests
332// ============================================================================
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
339        let stride = (w * BPP as u32) as i32;
340        let buf = vec![0u8; (h * w * BPP as u32) as usize];
341        let mut ra = RowAccessor::new();
342        unsafe {
343            ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
344        }
345        (buf, ra)
346    }
347
348    #[test]
349    fn test_new() {
350        let (_buf, mut ra) = make_buffer(100, 100);
351        let pf = PixfmtRgba32::new(&mut ra);
352        assert_eq!(pf.width(), 100);
353        assert_eq!(pf.height(), 100);
354    }
355
356    #[test]
357    fn test_copy_pixel() {
358        let (_buf, mut ra) = make_buffer(10, 10);
359        let mut pf = PixfmtRgba32::new(&mut ra);
360        let red = Rgba8::new(255, 0, 0, 255);
361        pf.copy_pixel(5, 5, &red);
362        let p = pf.pixel(5, 5);
363        assert_eq!(p.r, 255);
364        assert_eq!(p.g, 0);
365        assert_eq!(p.b, 0);
366        assert_eq!(p.a, 255);
367    }
368
369    #[test]
370    fn test_copy_hline() {
371        let (_buf, mut ra) = make_buffer(20, 10);
372        let mut pf = PixfmtRgba32::new(&mut ra);
373        let green = Rgba8::new(0, 255, 0, 255);
374        pf.copy_hline(5, 3, 10, &green);
375        for x in 5..15 {
376            let p = pf.pixel(x, 3);
377            assert_eq!(p.g, 255);
378        }
379        // Pixels outside should remain black
380        let p = pf.pixel(4, 3);
381        assert_eq!(p.g, 0);
382    }
383
384    #[test]
385    fn test_blend_pixel_opaque() {
386        let (_buf, mut ra) = make_buffer(10, 10);
387        let mut pf = PixfmtRgba32::new(&mut ra);
388        let blue = Rgba8::new(0, 0, 255, 255);
389        pf.blend_pixel(3, 3, &blue, 255);
390        let p = pf.pixel(3, 3);
391        assert_eq!(p.b, 255);
392        assert_eq!(p.a, 255);
393    }
394
395    #[test]
396    fn test_blend_pixel_semi_transparent() {
397        let (_buf, mut ra) = make_buffer(10, 10);
398        let mut pf = PixfmtRgba32::new(&mut ra);
399        // Start with white background
400        let white = Rgba8::new(255, 255, 255, 255);
401        pf.copy_pixel(3, 3, &white);
402        // Blend red at 50% coverage
403        let red = Rgba8::new(255, 0, 0, 255);
404        pf.blend_pixel(3, 3, &red, 128);
405        let p = pf.pixel(3, 3);
406        // Red should increase, green/blue should decrease
407        assert!(p.r > 128);
408        assert!(p.g < 200);
409        assert!(p.b < 200);
410    }
411
412    #[test]
413    fn test_blend_hline() {
414        let (_buf, mut ra) = make_buffer(20, 10);
415        let mut pf = PixfmtRgba32::new(&mut ra);
416        let red = Rgba8::new(255, 0, 0, 255);
417        pf.blend_hline(5, 3, 5, &red, 255);
418        for x in 5..10 {
419            let p = pf.pixel(x, 3);
420            assert_eq!(p.r, 255);
421        }
422    }
423
424    #[test]
425    fn test_blend_solid_hspan() {
426        let (_buf, mut ra) = make_buffer(20, 10);
427        let mut pf = PixfmtRgba32::new(&mut ra);
428        let green = Rgba8::new(0, 255, 0, 255);
429        let covers = [255u8, 128, 64, 0, 255];
430        pf.blend_solid_hspan(5, 3, 5, &green, &covers);
431        // Full coverage pixel
432        let p0 = pf.pixel(5, 3);
433        assert_eq!(p0.g, 255);
434        // Zero coverage pixel should be unchanged (black)
435        let p3 = pf.pixel(8, 3);
436        assert_eq!(p3.g, 0);
437    }
438
439    #[test]
440    fn test_clear() {
441        let (_buf, mut ra) = make_buffer(10, 10);
442        let mut pf = PixfmtRgba32::new(&mut ra);
443        let white = Rgba8::new(255, 255, 255, 255);
444        pf.clear(&white);
445        for y in 0..10 {
446            for x in 0..10 {
447                let p = pf.pixel(x, y);
448                assert_eq!(p.r, 255);
449                assert_eq!(p.g, 255);
450                assert_eq!(p.b, 255);
451                assert_eq!(p.a, 255);
452            }
453        }
454    }
455
456    #[test]
457    fn test_blend_on_black_background() {
458        let (_buf, mut ra) = make_buffer(10, 10);
459        let mut pf = PixfmtRgba32::new(&mut ra);
460        // Blend white at 50% on black
461        let white = Rgba8::new(255, 255, 255, 255);
462        pf.blend_pixel(0, 0, &white, 128);
463        let p = pf.pixel(0, 0);
464        // lerp(0, 255, 128) ≈ 128
465        assert!((p.r as i32 - 128).abs() <= 2);
466    }
467
468    #[test]
469    fn test_blend_preserves_adjacent_pixels() {
470        let (_buf, mut ra) = make_buffer(10, 10);
471        let mut pf = PixfmtRgba32::new(&mut ra);
472        let red = Rgba8::new(255, 0, 0, 255);
473        pf.blend_pixel(5, 5, &red, 255);
474        // Adjacent pixel should remain black
475        let p = pf.pixel(4, 5);
476        assert_eq!(p.r, 0);
477        assert_eq!(p.g, 0);
478        assert_eq!(p.b, 0);
479        assert_eq!(p.a, 0);
480    }
481
482    #[test]
483    fn test_pixel_read_write_roundtrip() {
484        let (_buf, mut ra) = make_buffer(10, 10);
485        let mut pf = PixfmtRgba32::new(&mut ra);
486        let c = Rgba8::new(42, 128, 200, 180);
487        pf.copy_pixel(7, 3, &c);
488        let p = pf.pixel(7, 3);
489        assert_eq!(p.r, 42);
490        assert_eq!(p.g, 128);
491        assert_eq!(p.b, 200);
492        assert_eq!(p.a, 180);
493    }
494
495    #[test]
496    fn test_blend_color_hspan_per_pixel_covers() {
497        let (_buf, mut ra) = make_buffer(20, 10);
498        let mut pf = PixfmtRgba32::new(&mut ra);
499        let colors = [
500            Rgba8::new(255, 0, 0, 255),
501            Rgba8::new(0, 255, 0, 255),
502            Rgba8::new(0, 0, 255, 255),
503        ];
504        let covers = [255u8, 128, 0];
505        pf.blend_color_hspan(5, 3, 3, &colors, &covers, 0);
506        // Full coverage red
507        let p0 = pf.pixel(5, 3);
508        assert_eq!(p0.r, 255);
509        assert_eq!(p0.g, 0);
510        // Half coverage green on black
511        let p1 = pf.pixel(6, 3);
512        assert!(p1.g > 64);
513        // Zero coverage blue: unchanged (black)
514        let p2 = pf.pixel(7, 3);
515        assert_eq!(p2.b, 0);
516    }
517
518    #[test]
519    fn test_blend_color_hspan_uniform_cover() {
520        let (_buf, mut ra) = make_buffer(20, 10);
521        let mut pf = PixfmtRgba32::new(&mut ra);
522        let colors = [Rgba8::new(255, 0, 0, 255), Rgba8::new(0, 255, 0, 255)];
523        // Empty covers slice → uniform cover of 255
524        pf.blend_color_hspan(3, 3, 2, &colors, &[], 255);
525        let p0 = pf.pixel(3, 3);
526        assert_eq!(p0.r, 255);
527        let p1 = pf.pixel(4, 3);
528        assert_eq!(p1.g, 255);
529    }
530
531    #[test]
532    fn test_blend_color_hspan_full_cover_opaque() {
533        let (_buf, mut ra) = make_buffer(10, 10);
534        let mut pf = PixfmtRgba32::new(&mut ra);
535        let white = Rgba8::new(255, 255, 255, 255);
536        pf.clear(&white);
537        let colors = [Rgba8::new(100, 150, 200, 255)];
538        pf.blend_color_hspan(0, 0, 1, &colors, &[], 255);
539        let p = pf.pixel(0, 0);
540        assert_eq!(p.r, 100);
541        assert_eq!(p.g, 150);
542        assert_eq!(p.b, 200);
543    }
544}