Skip to main content

agg_rust/
span_interpolator_linear.rs

1//! Linear span interpolator.
2//!
3//! Port of `agg_span_interpolator_linear.h` — transforms span coordinates
4//! through an affine (or other) transformation using DDA interpolation.
5
6use crate::basics::iround;
7use crate::dda_line::Dda2LineInterpolator;
8use crate::trans_affine::TransAffine;
9use crate::trans_bilinear::TransBilinear;
10use crate::trans_perspective::TransPerspective;
11
12/// Subpixel precision constants for span interpolation.
13pub const SUBPIXEL_SHIFT: u32 = 8;
14pub const SUBPIXEL_SCALE: i32 = 1 << SUBPIXEL_SHIFT;
15
16// ============================================================================
17// Transformer trait
18// ============================================================================
19
20/// Trait for coordinate transformers used by span interpolators.
21pub trait Transformer {
22    fn transform(&self, x: &mut f64, y: &mut f64);
23}
24
25/// Blanket impl: any reference to a Transformer is also a Transformer.
26/// This allows `ConvTransform<VS, &TransSinglePath>` etc. without cloning.
27impl<T: Transformer> Transformer for &T {
28    fn transform(&self, x: &mut f64, y: &mut f64) {
29        (*self).transform(x, y);
30    }
31}
32
33impl Transformer for TransAffine {
34    fn transform(&self, x: &mut f64, y: &mut f64) {
35        self.transform(x, y);
36    }
37}
38
39impl Transformer for TransBilinear {
40    fn transform(&self, x: &mut f64, y: &mut f64) {
41        self.transform(x, y);
42    }
43}
44
45impl Transformer for TransPerspective {
46    fn transform(&self, x: &mut f64, y: &mut f64) {
47        self.transform(x, y);
48    }
49}
50
51// ============================================================================
52// SpanInterpolator trait
53// ============================================================================
54
55/// Trait for span interpolators used by image filter span generators.
56///
57/// All span interpolators must provide `begin`, `next`, and `coordinates`
58/// methods. This mirrors the C++ template interface used by span generators.
59pub trait SpanInterpolator {
60    fn begin(&mut self, x: f64, y: f64, len: u32);
61    fn next(&mut self);
62    fn coordinates(&self, x: &mut i32, y: &mut i32);
63}
64
65// ============================================================================
66// SpanInterpolatorLinear
67// ============================================================================
68
69/// Linear span interpolator.
70///
71/// Transforms span coordinates through a transformation using two DDA
72/// interpolators (one for X, one for Y). Transforms only the endpoints
73/// and linearly interpolates between them.
74///
75/// Port of C++ `span_interpolator_linear<Transformer, SubpixelShift>`.
76pub struct SpanInterpolatorLinear<T = TransAffine> {
77    trans: T,
78    li_x: Dda2LineInterpolator,
79    li_y: Dda2LineInterpolator,
80}
81
82impl<T: Transformer> SpanInterpolatorLinear<T> {
83    pub fn new(trans: T) -> Self {
84        Self {
85            trans,
86            li_x: Dda2LineInterpolator::new_forward(0, 0, 1),
87            li_y: Dda2LineInterpolator::new_forward(0, 0, 1),
88        }
89    }
90
91    pub fn new_begin(trans: T, x: f64, y: f64, len: u32) -> Self {
92        let mut s = Self::new(trans);
93        s.begin(x, y, len);
94        s
95    }
96
97    pub fn transformer(&self) -> &T {
98        &self.trans
99    }
100
101    pub fn set_transformer(&mut self, trans: T) {
102        self.trans = trans;
103    }
104
105    /// Initialize interpolation for a span starting at (x, y) with `len` pixels.
106    pub fn begin(&mut self, x: f64, y: f64, len: u32) {
107        let mut tx = x;
108        let mut ty = y;
109        self.trans.transform(&mut tx, &mut ty);
110        let x1 = iround(tx * SUBPIXEL_SCALE as f64);
111        let y1 = iround(ty * SUBPIXEL_SCALE as f64);
112
113        let mut tx2 = x + len as f64;
114        let mut ty2 = y;
115        self.trans.transform(&mut tx2, &mut ty2);
116        let x2 = iround(tx2 * SUBPIXEL_SCALE as f64);
117        let y2 = iround(ty2 * SUBPIXEL_SCALE as f64);
118
119        self.li_x = Dda2LineInterpolator::new_forward(x1, x2, len as i32);
120        self.li_y = Dda2LineInterpolator::new_forward(y1, y2, len as i32);
121    }
122
123    /// Re-synchronize interpolation at a new endpoint.
124    pub fn resynchronize(&mut self, mut xe: f64, mut ye: f64, len: u32) {
125        self.trans.transform(&mut xe, &mut ye);
126        self.li_x = Dda2LineInterpolator::new_forward(
127            self.li_x.y(),
128            iround(xe * SUBPIXEL_SCALE as f64),
129            len as i32,
130        );
131        self.li_y = Dda2LineInterpolator::new_forward(
132            self.li_y.y(),
133            iround(ye * SUBPIXEL_SCALE as f64),
134            len as i32,
135        );
136    }
137
138    /// Advance to the next pixel.
139    #[inline]
140    pub fn next(&mut self) {
141        self.li_x.inc();
142        self.li_y.inc();
143    }
144
145    /// Get the current transformed coordinates (in subpixel units).
146    #[inline]
147    pub fn coordinates(&self, x: &mut i32, y: &mut i32) {
148        *x = self.li_x.y();
149        *y = self.li_y.y();
150    }
151}
152
153impl<T: Transformer> SpanInterpolator for SpanInterpolatorLinear<T> {
154    fn begin(&mut self, x: f64, y: f64, len: u32) {
155        self.begin(x, y, len);
156    }
157    fn next(&mut self) {
158        self.next();
159    }
160    fn coordinates(&self, x: &mut i32, y: &mut i32) {
161        self.coordinates(x, y);
162    }
163}
164
165// ============================================================================
166// SpanInterpolatorLinearSubdiv
167// ============================================================================
168
169/// Linear span interpolator with subdivision.
170///
171/// Periodically re-transforms coordinates to reduce error accumulation
172/// for non-linear transforms used as linear approximations.
173///
174/// Port of C++ `span_interpolator_linear_subdiv<Transformer, SubpixelShift>`.
175pub struct SpanInterpolatorLinearSubdiv<T = TransAffine> {
176    subdiv_shift: u32,
177    subdiv_size: u32,
178    trans: T,
179    li_x: Dda2LineInterpolator,
180    li_y: Dda2LineInterpolator,
181    src_x: i32,
182    src_y: f64,
183    pos: u32,
184    len: u32,
185}
186
187impl<T: Transformer> SpanInterpolatorLinearSubdiv<T> {
188    pub fn new(trans: T, subdiv_shift: u32) -> Self {
189        Self {
190            subdiv_shift,
191            subdiv_size: 1 << subdiv_shift,
192            trans,
193            li_x: Dda2LineInterpolator::new_forward(0, 0, 1),
194            li_y: Dda2LineInterpolator::new_forward(0, 0, 1),
195            src_x: 0,
196            src_y: 0.0,
197            pos: 1,
198            len: 0,
199        }
200    }
201
202    pub fn new_default(trans: T) -> Self {
203        Self::new(trans, 4)
204    }
205
206    pub fn transformer(&self) -> &T {
207        &self.trans
208    }
209
210    pub fn set_transformer(&mut self, trans: T) {
211        self.trans = trans;
212    }
213
214    pub fn subdiv_shift(&self) -> u32 {
215        self.subdiv_shift
216    }
217
218    pub fn set_subdiv_shift(&mut self, shift: u32) {
219        self.subdiv_shift = shift;
220        self.subdiv_size = 1 << shift;
221    }
222
223    /// Initialize interpolation for a span starting at (x, y) with `len` pixels.
224    pub fn begin(&mut self, x: f64, y: f64, len: u32) {
225        self.pos = 1;
226        self.src_x = iround(x * SUBPIXEL_SCALE as f64) + SUBPIXEL_SCALE;
227        self.src_y = y;
228        self.len = len;
229
230        let sub_len = if len > self.subdiv_size {
231            self.subdiv_size
232        } else {
233            len
234        };
235
236        let mut tx = x;
237        let mut ty = y;
238        self.trans.transform(&mut tx, &mut ty);
239        let x1 = iround(tx * SUBPIXEL_SCALE as f64);
240        let y1 = iround(ty * SUBPIXEL_SCALE as f64);
241
242        let mut tx2 = x + sub_len as f64;
243        let mut ty2 = y;
244        self.trans.transform(&mut tx2, &mut ty2);
245
246        self.li_x = Dda2LineInterpolator::new_forward(
247            x1,
248            iround(tx2 * SUBPIXEL_SCALE as f64),
249            sub_len as i32,
250        );
251        self.li_y = Dda2LineInterpolator::new_forward(
252            y1,
253            iround(ty2 * SUBPIXEL_SCALE as f64),
254            sub_len as i32,
255        );
256    }
257
258    /// Advance to the next pixel.
259    pub fn next(&mut self) {
260        self.li_x.inc();
261        self.li_y.inc();
262        if self.pos >= self.subdiv_size {
263            let mut sub_len = self.len;
264            if sub_len > self.subdiv_size {
265                sub_len = self.subdiv_size;
266            }
267            let mut tx = self.src_x as f64 / SUBPIXEL_SCALE as f64 + sub_len as f64;
268            let mut ty = self.src_y;
269            self.trans.transform(&mut tx, &mut ty);
270            self.li_x = Dda2LineInterpolator::new_forward(
271                self.li_x.y(),
272                iround(tx * SUBPIXEL_SCALE as f64),
273                sub_len as i32,
274            );
275            self.li_y = Dda2LineInterpolator::new_forward(
276                self.li_y.y(),
277                iround(ty * SUBPIXEL_SCALE as f64),
278                sub_len as i32,
279            );
280            self.pos = 0;
281        }
282        self.src_x += SUBPIXEL_SCALE;
283        self.pos += 1;
284        self.len = self.len.saturating_sub(1);
285    }
286
287    /// Get the current transformed coordinates (in subpixel units).
288    #[inline]
289    pub fn coordinates(&self, x: &mut i32, y: &mut i32) {
290        *x = self.li_x.y();
291        *y = self.li_y.y();
292    }
293}
294
295impl<T: Transformer> SpanInterpolator for SpanInterpolatorLinearSubdiv<T> {
296    fn begin(&mut self, x: f64, y: f64, len: u32) {
297        self.begin(x, y, len);
298    }
299    fn next(&mut self) {
300        self.next();
301    }
302    fn coordinates(&self, x: &mut i32, y: &mut i32) {
303        self.coordinates(x, y);
304    }
305}
306
307// ============================================================================
308// Tests
309// ============================================================================
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use std::f64::consts::PI;
315
316    #[test]
317    fn test_identity_transform() {
318        let trans = TransAffine::new();
319        let mut interp = SpanInterpolatorLinear::new(trans);
320        interp.begin(10.0, 20.0, 5);
321
322        let mut x = 0;
323        let mut y = 0;
324        interp.coordinates(&mut x, &mut y);
325        assert_eq!(x, 10 * SUBPIXEL_SCALE);
326        assert_eq!(y, 20 * SUBPIXEL_SCALE);
327    }
328
329    #[test]
330    fn test_translation() {
331        let trans = TransAffine::new_translation(100.0, 200.0);
332        let mut interp = SpanInterpolatorLinear::new(trans);
333        interp.begin(10.0, 20.0, 5);
334
335        let mut x = 0;
336        let mut y = 0;
337        interp.coordinates(&mut x, &mut y);
338        assert_eq!(x, 110 * SUBPIXEL_SCALE);
339        assert_eq!(y, 220 * SUBPIXEL_SCALE);
340    }
341
342    #[test]
343    fn test_scaling() {
344        let trans = TransAffine::new_scaling(2.0, 3.0);
345        let mut interp = SpanInterpolatorLinear::new(trans);
346        interp.begin(10.0, 20.0, 5);
347
348        let mut x = 0;
349        let mut y = 0;
350        interp.coordinates(&mut x, &mut y);
351        assert_eq!(x, 20 * SUBPIXEL_SCALE);
352        assert_eq!(y, 60 * SUBPIXEL_SCALE);
353    }
354
355    #[test]
356    fn test_interpolation_advances() {
357        let trans = TransAffine::new();
358        let mut interp = SpanInterpolatorLinear::new(trans);
359        interp.begin(0.0, 0.0, 10);
360
361        let mut x = 0;
362        let mut y = 0;
363        interp.coordinates(&mut x, &mut y);
364        assert_eq!(x, 0);
365
366        // Advance to end of span
367        for _ in 0..10 {
368            interp.next();
369        }
370        interp.coordinates(&mut x, &mut y);
371        // Should be at approximately 10 * SUBPIXEL_SCALE
372        assert!((x - 10 * SUBPIXEL_SCALE).abs() <= 1);
373    }
374
375    #[test]
376    fn test_rotation_90_degrees() {
377        let trans = TransAffine::new_rotation(PI / 2.0);
378        let mut interp = SpanInterpolatorLinear::new(trans);
379        interp.begin(10.0, 0.0, 1);
380
381        let mut x = 0;
382        let mut y = 0;
383        interp.coordinates(&mut x, &mut y);
384        // After 90° rotation, (10, 0) → (0, 10)
385        assert!(x.abs() <= 1, "x={} should be ~0", x);
386        assert!(
387            (y - 10 * SUBPIXEL_SCALE).abs() <= 1,
388            "y={} should be ~{}",
389            y,
390            10 * SUBPIXEL_SCALE
391        );
392    }
393
394    #[test]
395    fn test_resynchronize() {
396        let trans = TransAffine::new();
397        let mut interp = SpanInterpolatorLinear::new(trans);
398        interp.begin(0.0, 0.0, 10);
399        for _ in 0..5 {
400            interp.next();
401        }
402        // Resync at new endpoint
403        interp.resynchronize(15.0, 0.0, 5);
404
405        let mut x = 0;
406        let mut y = 0;
407        interp.coordinates(&mut x, &mut y);
408        // Should be at ~5 * SUBPIXEL_SCALE
409        assert!(
410            (x - 5 * SUBPIXEL_SCALE).abs() <= 1,
411            "x={} should be ~{}",
412            x,
413            5 * SUBPIXEL_SCALE
414        );
415    }
416
417    #[test]
418    fn test_new_begin() {
419        let trans = TransAffine::new();
420        let interp = SpanInterpolatorLinear::new_begin(trans, 5.0, 10.0, 3);
421        let mut x = 0;
422        let mut y = 0;
423        interp.coordinates(&mut x, &mut y);
424        assert_eq!(x, 5 * SUBPIXEL_SCALE);
425        assert_eq!(y, 10 * SUBPIXEL_SCALE);
426    }
427
428    #[test]
429    fn test_subdiv_identity() {
430        let trans = TransAffine::new();
431        let mut interp = SpanInterpolatorLinearSubdiv::new_default(trans);
432        interp.begin(10.0, 20.0, 100);
433
434        let mut x = 0;
435        let mut y = 0;
436        interp.coordinates(&mut x, &mut y);
437        assert_eq!(x, 10 * SUBPIXEL_SCALE);
438        assert_eq!(y, 20 * SUBPIXEL_SCALE);
439
440        // Advance many steps — should stay on track
441        for _ in 0..50 {
442            interp.next();
443        }
444        interp.coordinates(&mut x, &mut y);
445        assert!(
446            (x - 60 * SUBPIXEL_SCALE).abs() <= 2,
447            "x={} should be ~{}",
448            x,
449            60 * SUBPIXEL_SCALE
450        );
451    }
452
453    #[test]
454    fn test_subdiv_translation() {
455        let trans = TransAffine::new_translation(50.0, 100.0);
456        let mut interp = SpanInterpolatorLinearSubdiv::new_default(trans);
457        interp.begin(0.0, 0.0, 20);
458
459        let mut x = 0;
460        let mut y = 0;
461        interp.coordinates(&mut x, &mut y);
462        assert_eq!(x, 50 * SUBPIXEL_SCALE);
463        assert_eq!(y, 100 * SUBPIXEL_SCALE);
464    }
465
466    #[test]
467    fn test_subdiv_shift_setter() {
468        let trans = TransAffine::new();
469        let mut interp = SpanInterpolatorLinearSubdiv::new(trans, 4);
470        assert_eq!(interp.subdiv_shift(), 4);
471        interp.set_subdiv_shift(6);
472        assert_eq!(interp.subdiv_shift(), 6);
473    }
474}