Skip to main content

agg_rust/
renderer_scanline.rs

1//! Scanline rendering functions.
2//!
3//! Port of `agg_renderer_scanline.h` — top-level functions that drive the
4//! rasterizer → scanline → renderer pipeline.
5//!
6//! The primary entry point is `render_scanlines_aa_solid()` which renders
7//! filled polygons with anti-aliased edges in a single solid color.
8
9use crate::pixfmt_rgba::PixelFormat;
10use crate::rasterizer_scanline_aa::{RasterizerScanlineAa, Scanline};
11use crate::renderer_base::RendererBase;
12use crate::scanline_u::ScanlineU8;
13use crate::span_allocator::SpanAllocator;
14
15// ============================================================================
16// render_scanlines_aa_solid — the main rendering driver
17// ============================================================================
18
19/// Render all scanlines from the rasterizer as a solid color.
20///
21/// This is the primary rendering function that ties together the full AGG
22/// pipeline: rasterizer → scanline → renderer.
23///
24/// Port of C++ `render_scanlines_aa_solid()`.
25///
26/// Works with `ScanlineU8` (unpacked per-pixel coverage). Each span has
27/// positive `len` and references into the covers array.
28pub fn render_scanlines_aa_solid<PF: PixelFormat>(
29    ras: &mut RasterizerScanlineAa,
30    sl: &mut ScanlineU8,
31    ren: &mut RendererBase<PF>,
32    color: &PF::ColorType,
33) {
34    if !ras.rewind_scanlines() {
35        return;
36    }
37
38    sl.reset(ras.min_x(), ras.max_x());
39    while ras.sweep_scanline(sl) {
40        render_scanline_aa_solid_u8(sl, ren, color);
41    }
42}
43
44/// Render a single scanline from `ScanlineU8` to the renderer.
45///
46/// Port of C++ `render_scanline_aa_solid()` specialized for ScanlineU8
47/// where all spans have positive len (per-pixel covers).
48fn render_scanline_aa_solid_u8<PF: PixelFormat>(
49    sl: &ScanlineU8,
50    ren: &mut RendererBase<PF>,
51    color: &PF::ColorType,
52) {
53    let y = sl.y();
54    let spans = sl.begin();
55    let covers = sl.covers();
56
57    for span in spans {
58        let x = span.x;
59        let len = span.len;
60        if len > 0 {
61            ren.blend_solid_hspan(
62                x,
63                y,
64                len,
65                color,
66                &covers[span.cover_offset..span.cover_offset + len as usize],
67            );
68        }
69        // ScanlineU8 always has positive len, but handle negative for safety
70        // (negative len would mean solid span like ScanlineP8 uses)
71    }
72}
73
74// ============================================================================
75// RendererScanlineAaSolid — stored-color renderer wrapper
76// ============================================================================
77
78/// A renderer that stores a color and renders solid AA scanlines.
79///
80/// Port of C++ `renderer_scanline_aa_solid`. Wraps a `RendererBase` and
81/// a stored color for convenience.
82pub struct RendererScanlineAaSolid<'a, PF: PixelFormat> {
83    ren: &'a mut RendererBase<PF>,
84    color: PF::ColorType,
85}
86
87impl<'a, PF: PixelFormat> RendererScanlineAaSolid<'a, PF>
88where
89    PF::ColorType: Default,
90{
91    pub fn new(ren: &'a mut RendererBase<PF>) -> Self {
92        Self {
93            ren,
94            color: PF::ColorType::default(),
95        }
96    }
97
98    pub fn color(&mut self, c: PF::ColorType) {
99        self.color = c;
100    }
101
102    /// Render all scanlines from the rasterizer.
103    pub fn render(&mut self, ras: &mut RasterizerScanlineAa, sl: &mut ScanlineU8) {
104        render_scanlines_aa_solid(ras, sl, self.ren, &self.color);
105    }
106}
107
108// ============================================================================
109// SpanGenerator trait
110// ============================================================================
111
112/// Trait for span generators that produce per-pixel colors.
113///
114/// Span generators are called during rendering to fill color arrays
115/// for each scanline span. The renderer then blends these colors into
116/// the output buffer.
117///
118/// Port of the C++ span generator concept used by `render_scanlines_aa`.
119pub trait SpanGenerator {
120    type Color;
121
122    /// Called once before rendering begins.
123    fn prepare(&mut self);
124
125    /// Generate colors for a horizontal span.
126    ///
127    /// Fills `span[0..len]` with colors for pixels starting at (x, y).
128    fn generate(&mut self, span: &mut [Self::Color], x: i32, y: i32, len: u32);
129}
130
131// ============================================================================
132// render_scanlines_aa — generic span generator rendering
133// ============================================================================
134
135/// Render all scanlines from the rasterizer using a span generator.
136///
137/// This is the generic rendering function that supports gradients, patterns,
138/// and other per-pixel color effects. For each scanline span, the span
139/// generator produces an array of colors which are blended into the output.
140///
141/// Port of C++ `render_scanlines_aa()` (span generator variant).
142pub fn render_scanlines_aa<PF, SG>(
143    ras: &mut RasterizerScanlineAa,
144    sl: &mut ScanlineU8,
145    ren: &mut RendererBase<PF>,
146    alloc: &mut SpanAllocator<SG::Color>,
147    span_gen: &mut SG,
148) where
149    PF: PixelFormat<ColorType = SG::Color>,
150    SG: SpanGenerator,
151    SG::Color: Default + Clone,
152{
153    if !ras.rewind_scanlines() {
154        return;
155    }
156
157    sl.reset(ras.min_x(), ras.max_x());
158    span_gen.prepare();
159    while ras.sweep_scanline(sl) {
160        render_scanline_aa(sl, ren, alloc, span_gen);
161    }
162}
163
164/// Render a single scanline using a span generator.
165///
166/// Port of C++ `render_scanline_aa()` (span generator variant).
167fn render_scanline_aa<PF, SG>(
168    sl: &ScanlineU8,
169    ren: &mut RendererBase<PF>,
170    alloc: &mut SpanAllocator<SG::Color>,
171    span_gen: &mut SG,
172) where
173    PF: PixelFormat<ColorType = SG::Color>,
174    SG: SpanGenerator,
175    SG::Color: Default + Clone,
176{
177    let y = sl.y();
178    let spans = sl.begin();
179    let covers = sl.covers();
180
181    for span in spans {
182        let x = span.x;
183        let len = span.len.unsigned_abs() as usize;
184
185        let colors = alloc.allocate(len);
186        span_gen.generate(colors, x, y, len as u32);
187
188        if span.len < 0 {
189            // Negative len: solid span (ScanlineP8 style) — use uniform cover
190            let cover = covers[span.cover_offset];
191            ren.blend_color_hspan(x, y, len as i32, colors, &[], cover);
192        } else {
193            // Positive len: per-pixel covers
194            let span_covers = &covers[span.cover_offset..span.cover_offset + len];
195            let first_cover = span_covers[0];
196            ren.blend_color_hspan(x, y, len as i32, colors, span_covers, first_cover);
197        }
198    }
199}
200
201// ============================================================================
202// Tests
203// ============================================================================
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::basics::POLY_SUBPIXEL_SCALE;
209    use crate::color::Rgba8;
210    use crate::ellipse::Ellipse;
211    use crate::path_storage::PathStorage;
212    use crate::pixfmt_rgba::{PixelFormat, PixfmtRgba32};
213    use crate::rendering_buffer::RowAccessor;
214
215    const BPP: usize = 4;
216
217    fn make_rgba_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
218        let stride = (w * BPP as u32) as i32;
219        let buf = vec![255u8; (h * w * BPP as u32) as usize]; // white background (all 0xFF)
220        let mut ra = RowAccessor::new();
221        unsafe {
222            ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
223        }
224        (buf, ra)
225    }
226
227    // ========================================================================
228    // Capstone test: render a solid red triangle on white background
229    // ========================================================================
230
231    #[test]
232    fn test_render_triangle_solid_red() {
233        let (_buf, mut ra) = make_rgba_buffer(100, 100);
234        let mut pf = PixfmtRgba32::new(&mut ra);
235        pf.clear(&Rgba8::new(255, 255, 255, 255));
236        let mut ren = RendererBase::new(pf);
237        let mut ras = RasterizerScanlineAa::new();
238        let mut sl = ScanlineU8::new();
239
240        // Triangle (20,20) → (80,20) → (50,80)
241        let s = POLY_SUBPIXEL_SCALE as i32;
242        ras.move_to(20 * s, 20 * s);
243        ras.line_to(80 * s, 20 * s);
244        ras.line_to(50 * s, 80 * s);
245
246        let red = Rgba8::new(255, 0, 0, 255);
247        render_scanlines_aa_solid(&mut ras, &mut sl, &mut ren, &red);
248
249        // Center of triangle should be red
250        let center = ren.ren().pixel(50, 40);
251        assert_eq!(center.r, 255, "Center R should be 255");
252        assert_eq!(center.g, 0, "Center G should be 0");
253        assert_eq!(center.b, 0, "Center B should be 0");
254        assert_eq!(center.a, 255, "Center A should be 255");
255
256        // Corner (0,0) should remain white
257        let corner = ren.ren().pixel(0, 0);
258        assert_eq!(corner.r, 255);
259        assert_eq!(corner.g, 255);
260        assert_eq!(corner.b, 255);
261
262        // Edge pixel should have AA blending (not fully red, not fully white)
263        // Check a pixel near the edge of the triangle
264        let edge = ren.ren().pixel(20, 20);
265        // At the exact vertex, it might be partially covered
266        assert!(edge.r > 0, "Edge pixel should have some red: r={}", edge.r);
267    }
268
269    // ========================================================================
270    // Rectangle test
271    // ========================================================================
272
273    #[test]
274    fn test_render_rectangle() {
275        let (_buf, mut ra) = make_rgba_buffer(100, 100);
276        let mut pf = PixfmtRgba32::new(&mut ra);
277        pf.clear(&Rgba8::new(255, 255, 255, 255));
278        let mut ren = RendererBase::new(pf);
279        let mut ras = RasterizerScanlineAa::new();
280        let mut sl = ScanlineU8::new();
281
282        // Rectangle: (10,10) → (90,10) → (90,90) → (10,90)
283        ras.move_to_d(10.0, 10.0);
284        ras.line_to_d(90.0, 10.0);
285        ras.line_to_d(90.0, 90.0);
286        ras.line_to_d(10.0, 90.0);
287
288        let blue = Rgba8::new(0, 0, 255, 255);
289        render_scanlines_aa_solid(&mut ras, &mut sl, &mut ren, &blue);
290
291        // Center should be blue
292        let center = ren.ren().pixel(50, 50);
293        assert_eq!(center.b, 255);
294        assert_eq!(center.r, 0);
295
296        // Outside should be white
297        let outside = ren.ren().pixel(5, 5);
298        assert_eq!(outside.r, 255);
299        assert_eq!(outside.g, 255);
300    }
301
302    // ========================================================================
303    // Ellipse test via add_path
304    // ========================================================================
305
306    #[test]
307    fn test_render_ellipse() {
308        let (_buf, mut ra) = make_rgba_buffer(100, 100);
309        let mut pf = PixfmtRgba32::new(&mut ra);
310        pf.clear(&Rgba8::new(0, 0, 0, 255)); // black background
311        let mut ren = RendererBase::new(pf);
312        let mut ras = RasterizerScanlineAa::new();
313        let mut sl = ScanlineU8::new();
314
315        let mut ellipse = Ellipse::new(50.0, 50.0, 30.0, 30.0, 64, false);
316        ras.add_path(&mut ellipse, 0);
317
318        let green = Rgba8::new(0, 255, 0, 255);
319        render_scanlines_aa_solid(&mut ras, &mut sl, &mut ren, &green);
320
321        // Center should be green
322        let center = ren.ren().pixel(50, 50);
323        assert_eq!(center.g, 255);
324
325        // Far corner should remain black
326        let corner = ren.ren().pixel(0, 0);
327        assert_eq!(corner.g, 0);
328    }
329
330    // ========================================================================
331    // PathStorage test
332    // ========================================================================
333
334    #[test]
335    fn test_render_path_storage_triangle() {
336        let (_buf, mut ra) = make_rgba_buffer(100, 100);
337        let mut pf = PixfmtRgba32::new(&mut ra);
338        pf.clear(&Rgba8::new(255, 255, 255, 255));
339        let mut ren = RendererBase::new(pf);
340        let mut ras = RasterizerScanlineAa::new();
341        let mut sl = ScanlineU8::new();
342
343        let mut path = PathStorage::new();
344        path.move_to(10.0, 10.0);
345        path.line_to(90.0, 50.0);
346        path.line_to(50.0, 90.0);
347        // auto_close will close the polygon
348
349        ras.add_path(&mut path, 0);
350
351        let magenta = Rgba8::new(255, 0, 255, 255);
352        render_scanlines_aa_solid(&mut ras, &mut sl, &mut ren, &magenta);
353
354        // Hit test: center of triangle
355        let p = ren.ren().pixel(40, 50);
356        assert!(p.r > 0 || p.b > 0, "Center should have color");
357    }
358
359    // ========================================================================
360    // Clip box test
361    // ========================================================================
362
363    #[test]
364    fn test_render_with_clip_box() {
365        let (_buf, mut ra) = make_rgba_buffer(100, 100);
366        let mut pf = PixfmtRgba32::new(&mut ra);
367        pf.clear(&Rgba8::new(255, 255, 255, 255));
368        let mut ren = RendererBase::new(pf);
369        let mut ras = RasterizerScanlineAa::new();
370        let mut sl = ScanlineU8::new();
371
372        // Set rasterizer clip box
373        ras.clip_box(0.0, 0.0, 50.0, 50.0);
374
375        // Draw a large rectangle that extends beyond clip
376        ras.move_to_d(10.0, 10.0);
377        ras.line_to_d(90.0, 10.0);
378        ras.line_to_d(90.0, 90.0);
379        ras.line_to_d(10.0, 90.0);
380
381        let red = Rgba8::new(255, 0, 0, 255);
382        render_scanlines_aa_solid(&mut ras, &mut sl, &mut ren, &red);
383
384        // Inside clip box and shape should be red
385        let inside = ren.ren().pixel(30, 30);
386        assert_eq!(inside.r, 255);
387
388        // Outside clip box should remain white
389        let outside = ren.ren().pixel(70, 70);
390        assert_eq!(outside.r, 255);
391        assert_eq!(outside.g, 255);
392    }
393
394    // ========================================================================
395    // RendererScanlineAaSolid wrapper test
396    // ========================================================================
397
398    #[test]
399    fn test_renderer_scanline_aa_solid_wrapper() {
400        let (_buf, mut ra) = make_rgba_buffer(100, 100);
401        let mut pf = PixfmtRgba32::new(&mut ra);
402        pf.clear(&Rgba8::new(0, 0, 0, 255));
403        let mut ren = RendererBase::new(pf);
404        let mut ras = RasterizerScanlineAa::new();
405        let mut sl = ScanlineU8::new();
406
407        ras.move_to_d(20.0, 20.0);
408        ras.line_to_d(80.0, 20.0);
409        ras.line_to_d(50.0, 80.0);
410
411        let mut renderer = RendererScanlineAaSolid::new(&mut ren);
412        renderer.color(Rgba8::new(0, 255, 0, 255));
413        renderer.render(&mut ras, &mut sl);
414
415        // Center should be green
416        let center = renderer.ren.ren().pixel(50, 40);
417        assert_eq!(center.g, 255);
418    }
419
420    // ========================================================================
421    // Empty rasterizer test
422    // ========================================================================
423
424    #[test]
425    fn test_render_empty() {
426        let (_buf, mut ra) = make_rgba_buffer(10, 10);
427        let mut pf = PixfmtRgba32::new(&mut ra);
428        pf.clear(&Rgba8::new(255, 255, 255, 255));
429        let mut ren = RendererBase::new(pf);
430        let mut ras = RasterizerScanlineAa::new();
431        let mut sl = ScanlineU8::new();
432
433        // Render with no polygons — should not crash
434        let red = Rgba8::new(255, 0, 0, 255);
435        render_scanlines_aa_solid(&mut ras, &mut sl, &mut ren, &red);
436
437        // Everything should remain white
438        let p = ren.ren().pixel(5, 5);
439        assert_eq!(p.r, 255);
440        assert_eq!(p.g, 255);
441    }
442
443    // ========================================================================
444    // Integration: gradient-filled rectangle via SpanGradient
445    // ========================================================================
446
447    #[test]
448    fn test_render_gradient_rectangle() {
449        use crate::gradient_lut::GradientLinearColor;
450        use crate::span_gradient::{GradientX, SpanGradient};
451        use crate::span_interpolator_linear::SpanInterpolatorLinear;
452        use crate::trans_affine::TransAffine;
453
454        let (_buf, mut ra) = make_rgba_buffer(100, 100);
455        let mut pf = PixfmtRgba32::new(&mut ra);
456        pf.clear(&Rgba8::new(255, 255, 255, 255));
457        let mut ren = RendererBase::new(pf);
458        let mut ras = RasterizerScanlineAa::new();
459        let mut sl = ScanlineU8::new();
460
461        // Rectangle (10,10)→(90,90)
462        ras.move_to_d(10.0, 10.0);
463        ras.line_to_d(90.0, 10.0);
464        ras.line_to_d(90.0, 90.0);
465        ras.line_to_d(10.0, 90.0);
466
467        // Linear gradient: black→white along X, range 10..90
468        let trans = TransAffine::new();
469        let interp = SpanInterpolatorLinear::new(trans);
470        let gc = GradientLinearColor::new(
471            Rgba8::new(0, 0, 0, 255),
472            Rgba8::new(255, 255, 255, 255),
473            256,
474        );
475        let mut sg = SpanGradient::new(interp, GradientX, &gc, 10.0, 90.0);
476        let mut alloc = SpanAllocator::<Rgba8>::new();
477
478        render_scanlines_aa(&mut ras, &mut sl, &mut ren, &mut alloc, &mut sg);
479
480        // Left side should be dark
481        let left = ren.ren().pixel(15, 50);
482        assert!(left.r < 50, "Left r={} should be dark", left.r);
483
484        // Right side should be light
485        let right = ren.ren().pixel(85, 50);
486        assert!(right.r > 200, "Right r={} should be light", right.r);
487
488        // Middle should be between
489        let mid = ren.ren().pixel(50, 50);
490        assert!(
491            mid.r > 50 && mid.r < 200,
492            "Mid r={} should be between",
493            mid.r
494        );
495
496        // Outside should remain white
497        let outside = ren.ren().pixel(5, 5);
498        assert_eq!(outside.r, 255);
499    }
500
501    // ========================================================================
502    // Integration: Gouraud-shaded triangle via SpanGouraudRgba
503    // ========================================================================
504
505    #[test]
506    fn test_render_gouraud_triangle() {
507        use crate::span_gouraud_rgba::SpanGouraudRgba;
508
509        let (_buf, mut ra) = make_rgba_buffer(100, 100);
510        let mut pf = PixfmtRgba32::new(&mut ra);
511        pf.clear(&Rgba8::new(0, 0, 0, 0)); // transparent black
512        let mut ren = RendererBase::new(pf);
513        let mut ras = RasterizerScanlineAa::new();
514        let mut sl = ScanlineU8::new();
515
516        // Triangle with red, green, blue vertices
517        let mut gouraud = SpanGouraudRgba::new_with_triangle(
518            Rgba8::new(255, 0, 0, 255), // top-left: red
519            Rgba8::new(0, 255, 0, 255), // top-right: green
520            Rgba8::new(0, 0, 255, 255), // bottom: blue
521            10.0,
522            10.0,
523            90.0,
524            10.0,
525            50.0,
526            90.0,
527            0.0,
528        );
529
530        // Feed triangle outline to rasterizer
531        ras.add_path(&mut gouraud, 0);
532
533        // Render using span generator
534        let mut alloc = SpanAllocator::<Rgba8>::new();
535        render_scanlines_aa(&mut ras, &mut sl, &mut ren, &mut alloc, &mut gouraud);
536
537        // Center of triangle should have visible color
538        let center = ren.ren().pixel(50, 40);
539        assert!(
540            center.a > 0,
541            "Center should be visible: rgba=({},{},{},{})",
542            center.r,
543            center.g,
544            center.b,
545            center.a
546        );
547
548        // Outside should remain transparent black
549        let outside = ren.ren().pixel(0, 0);
550        assert_eq!(outside.a, 0, "Outside should be transparent");
551    }
552
553    // ========================================================================
554    // Integration: gradient with LUT (multi-stop)
555    // ========================================================================
556
557    #[test]
558    fn test_render_gradient_with_lut() {
559        use crate::gradient_lut::GradientLut;
560        use crate::span_gradient::{GradientX, SpanGradient};
561        use crate::span_interpolator_linear::SpanInterpolatorLinear;
562        use crate::trans_affine::TransAffine;
563
564        let (_buf, mut ra) = make_rgba_buffer(100, 50);
565        let mut pf = PixfmtRgba32::new(&mut ra);
566        pf.clear(&Rgba8::new(0, 0, 0, 255));
567        let mut ren = RendererBase::new(pf);
568        let mut ras = RasterizerScanlineAa::new();
569        let mut sl = ScanlineU8::new();
570
571        // Full-width rectangle
572        ras.move_to_d(0.0, 0.0);
573        ras.line_to_d(100.0, 0.0);
574        ras.line_to_d(100.0, 50.0);
575        ras.line_to_d(0.0, 50.0);
576
577        // Red → Green → Blue gradient LUT
578        let mut lut = GradientLut::new_default();
579        lut.add_color(0.0, Rgba8::new(255, 0, 0, 255));
580        lut.add_color(0.5, Rgba8::new(0, 255, 0, 255));
581        lut.add_color(1.0, Rgba8::new(0, 0, 255, 255));
582        lut.build_lut();
583
584        let trans = TransAffine::new();
585        let interp = SpanInterpolatorLinear::new(trans);
586        let mut sg = SpanGradient::new(interp, GradientX, &lut, 0.0, 100.0);
587        let mut alloc = SpanAllocator::<Rgba8>::new();
588
589        render_scanlines_aa(&mut ras, &mut sl, &mut ren, &mut alloc, &mut sg);
590
591        // Left side should be red
592        let left = ren.ren().pixel(5, 25);
593        assert!(left.r > 200, "Left r={} should be red", left.r);
594
595        // Middle should be green
596        let mid = ren.ren().pixel(50, 25);
597        assert!(mid.g > 100, "Mid g={} should be green", mid.g);
598
599        // Right side should be blue
600        let right = ren.ren().pixel(95, 25);
601        assert!(right.b > 200, "Right b={} should be blue", right.b);
602    }
603}