Skip to main content

agg_rust/
span_subdiv_adaptor.rs

1//! Span subdivision adaptor.
2//!
3//! Port of `agg_span_subdiv_adaptor.h` — wraps any span interpolator and
4//! periodically re-synchronizes it to correct accumulated error when using
5//! linear approximation for non-linear transforms.
6
7use crate::basics::iround;
8use crate::span_interpolator_linear::{SpanInterpolatorLinear, Transformer, SUBPIXEL_SCALE};
9
10// ============================================================================
11// SpanSubdivAdaptor
12// ============================================================================
13
14/// Span subdivision adaptor.
15///
16/// Breaks long spans into sub-spans of `subdiv_size` pixels, calling
17/// `resynchronize()` on the inner interpolator at each break point.
18/// This corrects accumulated error for non-linear transforms used with
19/// linear interpolation.
20///
21/// Port of C++ `span_subdiv_adaptor<Interpolator, SubpixelShift>`.
22pub struct SpanSubdivAdaptor<T: Transformer> {
23    subdiv_shift: u32,
24    subdiv_size: u32,
25    subdiv_mask: u32,
26    interpolator: SpanInterpolatorLinear<T>,
27    src_x: i32,
28    src_y: f64,
29    pos: u32,
30    len: u32,
31}
32
33impl<T: Transformer> SpanSubdivAdaptor<T> {
34    /// Create a new subdivision adaptor with default subdivision shift of 4 (16 pixels).
35    pub fn new(interpolator: SpanInterpolatorLinear<T>) -> Self {
36        Self {
37            subdiv_shift: 4,
38            subdiv_size: 1 << 4,
39            subdiv_mask: (1 << 4) - 1,
40            interpolator,
41            src_x: 0,
42            src_y: 0.0,
43            pos: 0,
44            len: 0,
45        }
46    }
47
48    /// Create with a custom subdivision shift.
49    pub fn new_with_shift(interpolator: SpanInterpolatorLinear<T>, subdiv_shift: u32) -> Self {
50        Self {
51            subdiv_shift,
52            subdiv_size: 1 << subdiv_shift,
53            subdiv_mask: (1 << subdiv_shift) - 1,
54            interpolator,
55            src_x: 0,
56            src_y: 0.0,
57            pos: 0,
58            len: 0,
59        }
60    }
61
62    pub fn interpolator(&self) -> &SpanInterpolatorLinear<T> {
63        &self.interpolator
64    }
65
66    pub fn interpolator_mut(&mut self) -> &mut SpanInterpolatorLinear<T> {
67        &mut self.interpolator
68    }
69
70    pub fn transformer(&self) -> &T {
71        self.interpolator.transformer()
72    }
73
74    pub fn subdiv_shift(&self) -> u32 {
75        self.subdiv_shift
76    }
77
78    pub fn set_subdiv_shift(&mut self, shift: u32) {
79        self.subdiv_shift = shift;
80        self.subdiv_size = 1 << shift;
81        self.subdiv_mask = self.subdiv_size - 1;
82    }
83
84    /// Initialize interpolation for a span starting at (x, y) with `len` pixels.
85    pub fn begin(&mut self, x: f64, y: f64, len: u32) {
86        self.pos = 1;
87        self.src_x = iround(x * SUBPIXEL_SCALE as f64) + SUBPIXEL_SCALE;
88        self.src_y = y;
89        self.len = len;
90        let sub_len = if len > self.subdiv_size {
91            self.subdiv_size
92        } else {
93            len
94        };
95        self.interpolator.begin(x, y, sub_len);
96    }
97
98    /// Advance to the next pixel.
99    #[inline]
100    pub fn next(&mut self) {
101        self.interpolator.next();
102        if self.pos >= self.subdiv_size {
103            let mut sub_len = self.len;
104            if sub_len > self.subdiv_size {
105                sub_len = self.subdiv_size;
106            }
107            self.interpolator.resynchronize(
108                self.src_x as f64 / SUBPIXEL_SCALE as f64 + sub_len as f64,
109                self.src_y,
110                sub_len,
111            );
112            self.pos = 0;
113        }
114        self.src_x += SUBPIXEL_SCALE;
115        self.pos += 1;
116        self.len -= 1;
117    }
118
119    /// Get the current transformed coordinates (in subpixel units).
120    #[inline]
121    pub fn coordinates(&self, x: &mut i32, y: &mut i32) {
122        self.interpolator.coordinates(x, y);
123    }
124}
125
126// ============================================================================
127// Tests
128// ============================================================================
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::trans_affine::TransAffine;
134
135    #[test]
136    fn test_identity_subdiv() {
137        let interp = SpanInterpolatorLinear::new(TransAffine::new());
138        let mut subdiv = SpanSubdivAdaptor::new(interp);
139        subdiv.begin(5.0, 10.0, 20);
140
141        let mut x = 0i32;
142        let mut y = 0i32;
143        subdiv.coordinates(&mut x, &mut y);
144        assert_eq!(x, 5 * 256);
145        assert_eq!(y, 10 * 256);
146    }
147
148    #[test]
149    fn test_next_advances() {
150        let interp = SpanInterpolatorLinear::new(TransAffine::new());
151        let mut subdiv = SpanSubdivAdaptor::new(interp);
152        subdiv.begin(0.0, 0.0, 10);
153
154        let mut x = 0i32;
155        let mut y = 0i32;
156        subdiv.coordinates(&mut x, &mut y);
157        assert_eq!(x, 0);
158
159        subdiv.next();
160        subdiv.coordinates(&mut x, &mut y);
161        assert_eq!(x, 256);
162    }
163
164    #[test]
165    fn test_subdiv_shift() {
166        let interp = SpanInterpolatorLinear::new(TransAffine::new());
167        let subdiv = SpanSubdivAdaptor::new_with_shift(interp, 3);
168        assert_eq!(subdiv.subdiv_shift(), 3);
169    }
170
171    #[test]
172    fn test_resynchronize_across_boundary() {
173        let interp = SpanInterpolatorLinear::new(TransAffine::new());
174        let mut subdiv = SpanSubdivAdaptor::new(interp);
175        // subdiv_size = 16, so after 16 steps it should resynchronize
176        subdiv.begin(0.0, 0.0, 32);
177
178        for _ in 0..16 {
179            subdiv.next();
180        }
181        let mut x = 0i32;
182        let mut y = 0i32;
183        subdiv.coordinates(&mut x, &mut y);
184        // Should be at pixel 16 with identity transform
185        assert_eq!(x, 16 * 256);
186    }
187
188    #[test]
189    fn test_with_translation() {
190        let trans = TransAffine::new_translation(100.0, 200.0);
191        let interp = SpanInterpolatorLinear::new(trans);
192        let mut subdiv = SpanSubdivAdaptor::new(interp);
193        subdiv.begin(0.0, 0.0, 5);
194
195        let mut x = 0i32;
196        let mut y = 0i32;
197        subdiv.coordinates(&mut x, &mut y);
198        assert_eq!(x, 100 * 256);
199        assert_eq!(y, 200 * 256);
200    }
201
202    #[test]
203    fn test_set_subdiv_shift() {
204        let interp = SpanInterpolatorLinear::new(TransAffine::new());
205        let mut subdiv = SpanSubdivAdaptor::new(interp);
206        subdiv.set_subdiv_shift(6);
207        assert_eq!(subdiv.subdiv_shift(), 6);
208    }
209}