Skip to main content

agg_rust/
span_interpolator_persp.rs

1//! Perspective span interpolators.
2//!
3//! Port of `agg_span_interpolator_persp.h`.
4//! Two variants for perspective coordinate interpolation:
5//! - `SpanInterpolatorPerspExact` — uses exact perspective iterator
6//! - `SpanInterpolatorPerspLerp` — uses linear interpolation with DDA
7
8use crate::basics::{iround, uround};
9use crate::dda_line::Dda2LineInterpolator;
10use crate::span_interpolator_linear::SpanInterpolator;
11use crate::trans_perspective::{PerspectiveIteratorX, TransPerspective};
12
13/// Subpixel precision for perspective interpolation.
14const SUBPIXEL_SHIFT: u32 = 8;
15const SUBPIXEL_SCALE: i32 = 1 << SUBPIXEL_SHIFT;
16
17/// Helper: compute local scale from perspective transform at a point.
18/// Returns the scale factor as a subpixel-shifted integer, then right-shifted.
19fn calc_scale(
20    xt: f64,
21    yt: f64,
22    x_src: f64,
23    y_src: f64,
24    trans_inv: &TransPerspective,
25    dx_offset: f64,
26    dy_offset: f64,
27) -> i32 {
28    let mut dx = xt + dx_offset;
29    let mut dy = yt + dy_offset;
30    trans_inv.transform(&mut dx, &mut dy);
31    dx -= x_src;
32    dy -= y_src;
33    (uround(SUBPIXEL_SCALE as f64 / (dx * dx + dy * dy).sqrt()) >> SUBPIXEL_SHIFT) as i32
34}
35
36// ============================================================================
37// SpanInterpolatorPerspExact
38// ============================================================================
39
40/// Exact perspective span interpolator.
41///
42/// Port of C++ `span_interpolator_persp_exact<SubpixelShift>`.
43/// Uses `PerspectiveIteratorX` for exact perspective division at each pixel.
44/// Scale factors are linearly interpolated via DDA.
45pub struct SpanInterpolatorPerspExact {
46    trans_dir: TransPerspective,
47    trans_inv: TransPerspective,
48    iterator: PerspectiveIteratorX,
49    scale_x: Dda2LineInterpolator,
50    scale_y: Dda2LineInterpolator,
51}
52
53impl SpanInterpolatorPerspExact {
54    pub fn new() -> Self {
55        Self {
56            trans_dir: TransPerspective::new(),
57            trans_inv: TransPerspective::new(),
58            iterator: PerspectiveIteratorX::default_new(),
59            scale_x: Dda2LineInterpolator::new_forward(0, 0, 1),
60            scale_y: Dda2LineInterpolator::new_forward(0, 0, 1),
61        }
62    }
63
64    pub fn new_quad_to_quad(src: &[f64; 8], dst: &[f64; 8]) -> Self {
65        let mut s = Self::new();
66        s.quad_to_quad(src, dst);
67        s
68    }
69
70    pub fn new_rect_to_quad(x1: f64, y1: f64, x2: f64, y2: f64, quad: &[f64; 8]) -> Self {
71        let mut s = Self::new();
72        s.rect_to_quad(x1, y1, x2, y2, quad);
73        s
74    }
75
76    pub fn new_quad_to_rect(quad: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
77        let mut s = Self::new();
78        s.quad_to_rect(quad, x1, y1, x2, y2);
79        s
80    }
81
82    pub fn quad_to_quad(&mut self, src: &[f64; 8], dst: &[f64; 8]) {
83        self.trans_dir.quad_to_quad(src, dst);
84        self.trans_inv.quad_to_quad(dst, src);
85    }
86
87    pub fn rect_to_quad(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, quad: &[f64; 8]) {
88        let src = [x1, y1, x2, y1, x2, y2, x1, y2];
89        self.quad_to_quad(&src, quad);
90    }
91
92    pub fn quad_to_rect(&mut self, quad: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) {
93        let dst = [x1, y1, x2, y1, x2, y2, x1, y2];
94        self.quad_to_quad(quad, &dst);
95    }
96
97    pub fn is_valid(&self) -> bool {
98        self.trans_dir.is_valid()
99    }
100
101    pub fn local_scale(&self, x: &mut i32, y: &mut i32) {
102        *x = self.scale_x.y();
103        *y = self.scale_y.y();
104    }
105
106    pub fn transform(&self, x: &mut f64, y: &mut f64) {
107        self.trans_dir.transform(x, y);
108    }
109
110    pub fn trans_dir(&self) -> &TransPerspective {
111        &self.trans_dir
112    }
113
114    pub fn trans_inv(&self) -> &TransPerspective {
115        &self.trans_inv
116    }
117}
118
119impl SpanInterpolator for SpanInterpolatorPerspExact {
120    fn begin(&mut self, x: f64, y: f64, len: u32) {
121        self.iterator = self.trans_dir.begin(x, y, 1.0);
122        let xt = self.iterator.x;
123        let yt = self.iterator.y;
124
125        let delta = 1.0 / SUBPIXEL_SCALE as f64;
126
127        let sx1 = calc_scale(xt, yt, x, y, &self.trans_inv, delta, 0.0);
128        let sy1 = calc_scale(xt, yt, x, y, &self.trans_inv, 0.0, delta);
129
130        let x2 = x + len as f64;
131        let mut xt2 = x2;
132        let mut yt2 = y;
133        self.trans_dir.transform(&mut xt2, &mut yt2);
134
135        let sx2 = calc_scale(xt2, yt2, x2, y, &self.trans_inv, delta, 0.0);
136        let sy2 = calc_scale(xt2, yt2, x2, y, &self.trans_inv, 0.0, delta);
137
138        self.scale_x = Dda2LineInterpolator::new_forward(sx1, sx2, len as i32);
139        self.scale_y = Dda2LineInterpolator::new_forward(sy1, sy2, len as i32);
140    }
141
142    fn next(&mut self) {
143        self.iterator.next();
144        self.scale_x.inc();
145        self.scale_y.inc();
146    }
147
148    fn coordinates(&self, x: &mut i32, y: &mut i32) {
149        *x = iround(self.iterator.x * SUBPIXEL_SCALE as f64);
150        *y = iround(self.iterator.y * SUBPIXEL_SCALE as f64);
151    }
152}
153
154// ============================================================================
155// SpanInterpolatorPerspLerp
156// ============================================================================
157
158/// Linear-interpolation perspective span interpolator.
159///
160/// Port of C++ `span_interpolator_persp_lerp<SubpixelShift>`.
161/// Transforms only the endpoints and linearly interpolates coordinates
162/// between them using DDA. Faster but less accurate than the exact variant.
163pub struct SpanInterpolatorPerspLerp {
164    trans_dir: TransPerspective,
165    trans_inv: TransPerspective,
166    coord_x: Dda2LineInterpolator,
167    coord_y: Dda2LineInterpolator,
168    scale_x: Dda2LineInterpolator,
169    scale_y: Dda2LineInterpolator,
170}
171
172impl SpanInterpolatorPerspLerp {
173    pub fn new() -> Self {
174        Self {
175            trans_dir: TransPerspective::new(),
176            trans_inv: TransPerspective::new(),
177            coord_x: Dda2LineInterpolator::new_forward(0, 0, 1),
178            coord_y: Dda2LineInterpolator::new_forward(0, 0, 1),
179            scale_x: Dda2LineInterpolator::new_forward(0, 0, 1),
180            scale_y: Dda2LineInterpolator::new_forward(0, 0, 1),
181        }
182    }
183
184    pub fn new_quad_to_quad(src: &[f64; 8], dst: &[f64; 8]) -> Self {
185        let mut s = Self::new();
186        s.quad_to_quad(src, dst);
187        s
188    }
189
190    pub fn new_rect_to_quad(x1: f64, y1: f64, x2: f64, y2: f64, quad: &[f64; 8]) -> Self {
191        let mut s = Self::new();
192        s.rect_to_quad(x1, y1, x2, y2, quad);
193        s
194    }
195
196    pub fn new_quad_to_rect(quad: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
197        let mut s = Self::new();
198        s.quad_to_rect(quad, x1, y1, x2, y2);
199        s
200    }
201
202    pub fn quad_to_quad(&mut self, src: &[f64; 8], dst: &[f64; 8]) {
203        self.trans_dir.quad_to_quad(src, dst);
204        self.trans_inv.quad_to_quad(dst, src);
205    }
206
207    pub fn rect_to_quad(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, quad: &[f64; 8]) {
208        let src = [x1, y1, x2, y1, x2, y2, x1, y2];
209        self.quad_to_quad(&src, quad);
210    }
211
212    pub fn quad_to_rect(&mut self, quad: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) {
213        let dst = [x1, y1, x2, y1, x2, y2, x1, y2];
214        self.quad_to_quad(quad, &dst);
215    }
216
217    pub fn is_valid(&self) -> bool {
218        self.trans_dir.is_valid()
219    }
220
221    pub fn local_scale(&self, x: &mut i32, y: &mut i32) {
222        *x = self.scale_x.y();
223        *y = self.scale_y.y();
224    }
225
226    pub fn transform(&self, x: &mut f64, y: &mut f64) {
227        self.trans_dir.transform(x, y);
228    }
229
230    pub fn trans_dir(&self) -> &TransPerspective {
231        &self.trans_dir
232    }
233
234    pub fn trans_inv(&self) -> &TransPerspective {
235        &self.trans_inv
236    }
237}
238
239impl SpanInterpolator for SpanInterpolatorPerspLerp {
240    fn begin(&mut self, x: f64, y: f64, len: u32) {
241        // Transform start point
242        let mut xt = x;
243        let mut yt = y;
244        self.trans_dir.transform(&mut xt, &mut yt);
245        let x1 = iround(xt * SUBPIXEL_SCALE as f64);
246        let y1 = iround(yt * SUBPIXEL_SCALE as f64);
247
248        let delta = 1.0 / SUBPIXEL_SCALE as f64;
249
250        let sx1 = calc_scale(xt, yt, x, y, &self.trans_inv, delta, 0.0);
251        let sy1 = calc_scale(xt, yt, x, y, &self.trans_inv, 0.0, delta);
252
253        // Transform end point
254        let x_end = x + len as f64;
255        let mut xt2 = x_end;
256        let mut yt2 = y;
257        self.trans_dir.transform(&mut xt2, &mut yt2);
258        let x2 = iround(xt2 * SUBPIXEL_SCALE as f64);
259        let y2 = iround(yt2 * SUBPIXEL_SCALE as f64);
260
261        let sx2 = calc_scale(xt2, yt2, x_end, y, &self.trans_inv, delta, 0.0);
262        let sy2 = calc_scale(xt2, yt2, x_end, y, &self.trans_inv, 0.0, delta);
263
264        self.coord_x = Dda2LineInterpolator::new_forward(x1, x2, len as i32);
265        self.coord_y = Dda2LineInterpolator::new_forward(y1, y2, len as i32);
266        self.scale_x = Dda2LineInterpolator::new_forward(sx1, sx2, len as i32);
267        self.scale_y = Dda2LineInterpolator::new_forward(sy1, sy2, len as i32);
268    }
269
270    fn next(&mut self) {
271        self.coord_x.inc();
272        self.coord_y.inc();
273        self.scale_x.inc();
274        self.scale_y.inc();
275    }
276
277    fn coordinates(&self, x: &mut i32, y: &mut i32) {
278        *x = self.coord_x.y();
279        *y = self.coord_y.y();
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_exact_identity() {
289        let src = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
290        let dst = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
291        let mut interp = SpanInterpolatorPerspExact::new_quad_to_quad(&src, &dst);
292        assert!(interp.is_valid());
293
294        interp.begin(50.0, 50.0, 10);
295        let (mut x, mut y) = (0, 0);
296        interp.coordinates(&mut x, &mut y);
297        // Should be approximately (50*256, 50*256) = (12800, 12800)
298        assert!((x - 12800).abs() < 5, "x={x}");
299        assert!((y - 12800).abs() < 5, "y={y}");
300    }
301
302    #[test]
303    fn test_exact_next_advances() {
304        let src = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
305        let dst = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
306        let mut interp = SpanInterpolatorPerspExact::new_quad_to_quad(&src, &dst);
307
308        interp.begin(0.0, 50.0, 10);
309        let (mut x1, mut y1) = (0, 0);
310        interp.coordinates(&mut x1, &mut y1);
311
312        interp.next();
313        let (mut x2, mut y2) = (0, 0);
314        interp.coordinates(&mut x2, &mut y2);
315
316        // x should advance by roughly 256 (1 pixel in subpixel coords)
317        assert!(x2 > x1, "x2={x2} should be > x1={x1}");
318        assert!((x2 - x1 - 256).abs() < 5, "step={}", x2 - x1);
319    }
320
321    #[test]
322    fn test_lerp_identity() {
323        let src = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
324        let dst = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
325        let mut interp = SpanInterpolatorPerspLerp::new_quad_to_quad(&src, &dst);
326        assert!(interp.is_valid());
327
328        interp.begin(50.0, 50.0, 10);
329        let (mut x, mut y) = (0, 0);
330        interp.coordinates(&mut x, &mut y);
331        assert!((x - 12800).abs() < 5, "x={x}");
332        assert!((y - 12800).abs() < 5, "y={y}");
333    }
334
335    #[test]
336    fn test_lerp_next_advances() {
337        let src = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
338        let dst = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0, 0.0, 100.0];
339        let mut interp = SpanInterpolatorPerspLerp::new_quad_to_quad(&src, &dst);
340
341        interp.begin(0.0, 50.0, 10);
342        let (mut x1, mut y1) = (0, 0);
343        interp.coordinates(&mut x1, &mut y1);
344
345        interp.next();
346        let (mut x2, mut y2) = (0, 0);
347        interp.coordinates(&mut x2, &mut y2);
348
349        assert!(x2 > x1, "x2={x2} should be > x1={x1}");
350        assert!((x2 - x1 - 256).abs() < 5, "step={}", x2 - x1);
351    }
352
353    #[test]
354    fn test_exact_rect_to_quad() {
355        let quad = [10.0, 10.0, 110.0, 10.0, 110.0, 110.0, 10.0, 110.0];
356        let mut interp = SpanInterpolatorPerspExact::new_rect_to_quad(0.0, 0.0, 100.0, 100.0, &quad);
357        assert!(interp.is_valid());
358
359        // Point (0,0) in rect space → (10,10) in quad space
360        interp.begin(0.0, 0.0, 1);
361        let (mut x, mut y) = (0, 0);
362        interp.coordinates(&mut x, &mut y);
363        assert!((x - 10 * 256).abs() < 5, "x={x}");
364        assert!((y - 10 * 256).abs() < 5, "y={y}");
365    }
366
367    #[test]
368    fn test_lerp_rect_to_quad() {
369        let quad = [10.0, 10.0, 110.0, 10.0, 110.0, 110.0, 10.0, 110.0];
370        let mut interp = SpanInterpolatorPerspLerp::new_rect_to_quad(0.0, 0.0, 100.0, 100.0, &quad);
371        assert!(interp.is_valid());
372
373        interp.begin(0.0, 0.0, 1);
374        let (mut x, mut y) = (0, 0);
375        interp.coordinates(&mut x, &mut y);
376        assert!((x - 10 * 256).abs() < 5, "x={x}");
377        assert!((y - 10 * 256).abs() < 5, "y={y}");
378    }
379
380    #[test]
381    fn test_exact_and_lerp_agree_on_identity() {
382        let src = [0.0, 0.0, 200.0, 0.0, 200.0, 200.0, 0.0, 200.0];
383        let dst = src;
384
385        let mut exact = SpanInterpolatorPerspExact::new_quad_to_quad(&src, &dst);
386        let mut lerp = SpanInterpolatorPerspLerp::new_quad_to_quad(&src, &dst);
387
388        exact.begin(10.0, 10.0, 5);
389        lerp.begin(10.0, 10.0, 5);
390
391        for _ in 0..5 {
392            let (mut ex, mut ey) = (0, 0);
393            let (mut lx, mut ly) = (0, 0);
394            exact.coordinates(&mut ex, &mut ey);
395            lerp.coordinates(&mut lx, &mut ly);
396            assert!((ex - lx).abs() < 3, "ex={ex} lx={lx}");
397            assert!((ey - ly).abs() < 3, "ey={ey} ly={ly}");
398
399            exact.next();
400            lerp.next();
401        }
402    }
403}