Skip to main content

agg_rust/
span_converter.rs

1//! Composable span pipeline.
2//!
3//! Port of `agg_span_converter.h` — wraps a span generator and applies a
4//! post-processing conversion to the generated span colors.
5
6use crate::renderer_scanline::SpanGenerator;
7
8// ============================================================================
9// SpanConverterFunction trait
10// ============================================================================
11
12/// Trait for span conversion functions.
13///
14/// Converts a span of colors in-place. Used to post-process generated spans
15/// (e.g., applying alpha masks, color adjustments, etc.).
16pub trait SpanConverterFunction {
17    type Color;
18
19    fn prepare(&mut self) {}
20
21    fn convert(&mut self, span: &mut [Self::Color], x: i32, y: i32, len: u32);
22}
23
24// ============================================================================
25// SpanConverter
26// ============================================================================
27
28/// Composable span pipeline.
29///
30/// Combines a span generator with a span converter: first generates the span,
31/// then applies the conversion function to the result.
32///
33/// Port of C++ `span_converter<SpanGenerator, SpanConverter>`.
34pub struct SpanConverter<SG: SpanGenerator, SC: SpanConverterFunction<Color = SG::Color>> {
35    span_gen: SG,
36    span_cnv: SC,
37}
38
39impl<SG: SpanGenerator, SC: SpanConverterFunction<Color = SG::Color>> SpanConverter<SG, SC> {
40    pub fn new(span_gen: SG, span_cnv: SC) -> Self {
41        Self { span_gen, span_cnv }
42    }
43
44    pub fn generator(&self) -> &SG {
45        &self.span_gen
46    }
47
48    pub fn generator_mut(&mut self) -> &mut SG {
49        &mut self.span_gen
50    }
51
52    pub fn converter(&self) -> &SC {
53        &self.span_cnv
54    }
55
56    pub fn converter_mut(&mut self) -> &mut SC {
57        &mut self.span_cnv
58    }
59}
60
61impl<SG: SpanGenerator, SC: SpanConverterFunction<Color = SG::Color>> SpanGenerator
62    for SpanConverter<SG, SC>
63{
64    type Color = SG::Color;
65
66    fn prepare(&mut self) {
67        self.span_gen.prepare();
68        self.span_cnv.prepare();
69    }
70
71    fn generate(&mut self, span: &mut [Self::Color], x: i32, y: i32, len: u32) {
72        self.span_gen.generate(span, x, y, len);
73        self.span_cnv.convert(span, x, y, len);
74    }
75}
76
77// ============================================================================
78// Tests
79// ============================================================================
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::color::Rgba8;
85
86    struct FillRed;
87    impl SpanGenerator for FillRed {
88        type Color = Rgba8;
89        fn prepare(&mut self) {}
90        fn generate(&mut self, span: &mut [Rgba8], _x: i32, _y: i32, len: u32) {
91            for pixel in span.iter_mut().take(len as usize) {
92                *pixel = Rgba8::new(255, 0, 0, 255);
93            }
94        }
95    }
96
97    struct HalveAlpha;
98    impl SpanConverterFunction for HalveAlpha {
99        type Color = Rgba8;
100        fn convert(&mut self, span: &mut [Rgba8], _x: i32, _y: i32, len: u32) {
101            for pixel in span.iter_mut().take(len as usize) {
102                pixel.a /= 2;
103            }
104        }
105    }
106
107    #[test]
108    fn test_converter_pipeline() {
109        let gen = FillRed;
110        let cnv = HalveAlpha;
111        let mut pipeline = SpanConverter::new(gen, cnv);
112
113        pipeline.prepare();
114        let mut span = vec![Rgba8::new(0, 0, 0, 0); 3];
115        pipeline.generate(&mut span, 0, 0, 3);
116
117        assert_eq!(span[0].r, 255);
118        assert_eq!(span[0].a, 127); // 255 / 2
119        assert_eq!(span[2].r, 255);
120        assert_eq!(span[2].a, 127);
121    }
122
123    #[test]
124    fn test_access_inner() {
125        let gen = FillRed;
126        let cnv = HalveAlpha;
127        let pipeline = SpanConverter::new(gen, cnv);
128        let _gen_ref = pipeline.generator();
129        let _cnv_ref = pipeline.converter();
130    }
131
132    #[test]
133    fn test_identity_converter() {
134        struct IdentityConv;
135        impl SpanConverterFunction for IdentityConv {
136            type Color = Rgba8;
137            fn convert(&mut self, _span: &mut [Rgba8], _x: i32, _y: i32, _len: u32) {}
138        }
139
140        let gen = FillRed;
141        let cnv = IdentityConv;
142        let mut pipeline = SpanConverter::new(gen, cnv);
143
144        let mut span = vec![Rgba8::new(0, 0, 0, 0); 2];
145        pipeline.generate(&mut span, 0, 0, 2);
146        assert_eq!(span[0].r, 255);
147        assert_eq!(span[0].a, 255); // unchanged
148    }
149
150    #[test]
151    fn test_converter_receives_coordinates() {
152        struct TrackCoords {
153            last_x: i32,
154            last_y: i32,
155        }
156        impl SpanConverterFunction for TrackCoords {
157            type Color = Rgba8;
158            fn convert(&mut self, _span: &mut [Rgba8], x: i32, y: i32, _len: u32) {
159                self.last_x = x;
160                self.last_y = y;
161            }
162        }
163
164        let gen = FillRed;
165        let cnv = TrackCoords {
166            last_x: 0,
167            last_y: 0,
168        };
169        let mut pipeline = SpanConverter::new(gen, cnv);
170
171        let mut span = vec![Rgba8::new(0, 0, 0, 0); 1];
172        pipeline.generate(&mut span, 42, 99, 1);
173        assert_eq!(pipeline.converter().last_x, 42);
174        assert_eq!(pipeline.converter().last_y, 99);
175    }
176}