Skip to main content

agg_rust/
rasterizer_sl_clip.rs

1//! Rasterizer scanline clipping policies.
2//!
3//! Port of `agg_rasterizer_sl_clip.h` — coordinate conversion (double → 24.8
4//! fixed-point) and optional viewport clipping for the scanline rasterizer.
5//!
6//! Provides two concrete clipper types:
7//! - `RasterizerSlClipInt` — default, with viewport clipping
8//! - `RasterizerSlNoClip` — passthrough, no clipping
9
10use crate::basics::{iround, Rect, POLY_SUBPIXEL_SCALE};
11use crate::clip_liang_barsky::{clipping_flags, clipping_flags_y};
12use crate::rasterizer_cells_aa::RasterizerCellsAa;
13
14// ============================================================================
15// Coordinate conversion helpers (port of ras_conv_int)
16// ============================================================================
17
18/// Convert double to 24.8 fixed-point (upscale).
19#[inline]
20fn upscale(v: f64) -> i32 {
21    iround(v * POLY_SUBPIXEL_SCALE as f64)
22}
23
24/// Integer coordinate to subpixel X (identity for int conv).
25#[inline]
26fn xi(v: i32) -> i32 {
27    v
28}
29
30/// Integer coordinate to subpixel Y (identity for int conv).
31#[inline]
32fn yi(v: i32) -> i32 {
33    v
34}
35
36/// Mul-div for integer coordinates: round(a * b / c).
37#[inline]
38fn mul_div(a: i32, b: i32, c: i32) -> i32 {
39    iround(a as f64 * b as f64 / c as f64)
40}
41
42// ============================================================================
43// RasterizerSlClipInt — clipping policy with viewport clipping
44// ============================================================================
45
46/// Scanline rasterizer clipping policy that clips line segments against
47/// a viewport rectangle, then converts to 24.8 fixed-point coordinates.
48///
49/// Port of C++ `rasterizer_sl_clip<ras_conv_int>`.
50pub struct RasterizerSlClipInt {
51    clip_box: Rect<i32>,
52    x1: i32,
53    y1: i32,
54    f1: u32,
55    clipping: bool,
56}
57
58impl RasterizerSlClipInt {
59    pub fn new() -> Self {
60        Self {
61            clip_box: Rect::new(0, 0, 0, 0),
62            x1: 0,
63            y1: 0,
64            f1: 0,
65            clipping: false,
66        }
67    }
68
69    /// Disable clipping.
70    pub fn reset_clipping(&mut self) {
71        self.clipping = false;
72    }
73
74    /// Set the clipping rectangle in 24.8 fixed-point coordinates.
75    pub fn clip_box(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
76        self.clip_box = Rect::new(x1, y1, x2, y2);
77        self.clip_box.normalize();
78        self.clipping = true;
79    }
80
81    /// Record a move_to in 24.8 fixed-point coordinates.
82    pub fn move_to(&mut self, x1: i32, y1: i32) {
83        self.x1 = x1;
84        self.y1 = y1;
85        if self.clipping {
86            self.f1 = clipping_flags(x1, y1, &self.clip_box);
87        }
88    }
89
90    /// Record a move_to from double coordinates (upscales to 24.8).
91    pub fn move_to_d(&mut self, x: f64, y: f64) {
92        self.move_to(upscale(x), upscale(y));
93    }
94
95    /// Clip and emit a line segment to the cell rasterizer.
96    ///
97    /// Implements the 13-case clipping switch from C++ `line_to`.
98    pub fn line_to(&mut self, ras: &mut RasterizerCellsAa, x2: i32, y2: i32) {
99        if self.clipping {
100            let f2 = clipping_flags(x2, y2, &self.clip_box);
101
102            // Both endpoints invisible by Y on the same side → skip
103            if (self.f1 & 10) == (f2 & 10) && (self.f1 & 10) != 0 {
104                self.x1 = x2;
105                self.y1 = y2;
106                self.f1 = f2;
107                return;
108            }
109
110            let x1 = self.x1;
111            let y1 = self.y1;
112            let f1 = self.f1;
113
114            match ((f1 & 5) << 1) | (f2 & 5) {
115                0 => {
116                    // Visible by X
117                    self.line_clip_y(ras, x1, y1, x2, y2, f1, f2);
118                }
119                1 => {
120                    // x2 > clip.x2
121                    let y3 = y1 + mul_div(self.clip_box.x2 - x1, y2 - y1, x2 - x1);
122                    let f3 = clipping_flags_y(y3, &self.clip_box);
123                    self.line_clip_y(ras, x1, y1, self.clip_box.x2, y3, f1, f3);
124                    self.line_clip_y(ras, self.clip_box.x2, y3, self.clip_box.x2, y2, f3, f2);
125                }
126                2 => {
127                    // x1 > clip.x2
128                    let y3 = y1 + mul_div(self.clip_box.x2 - x1, y2 - y1, x2 - x1);
129                    let f3 = clipping_flags_y(y3, &self.clip_box);
130                    self.line_clip_y(ras, self.clip_box.x2, y1, self.clip_box.x2, y3, f1, f3);
131                    self.line_clip_y(ras, self.clip_box.x2, y3, x2, y2, f3, f2);
132                }
133                3 => {
134                    // x1 > clip.x2 && x2 > clip.x2
135                    self.line_clip_y(ras, self.clip_box.x2, y1, self.clip_box.x2, y2, f1, f2);
136                }
137                4 => {
138                    // x2 < clip.x1
139                    let y3 = y1 + mul_div(self.clip_box.x1 - x1, y2 - y1, x2 - x1);
140                    let f3 = clipping_flags_y(y3, &self.clip_box);
141                    self.line_clip_y(ras, x1, y1, self.clip_box.x1, y3, f1, f3);
142                    self.line_clip_y(ras, self.clip_box.x1, y3, self.clip_box.x1, y2, f3, f2);
143                }
144                6 => {
145                    // x1 > clip.x2 && x2 < clip.x1
146                    let y3 = y1 + mul_div(self.clip_box.x2 - x1, y2 - y1, x2 - x1);
147                    let y4 = y1 + mul_div(self.clip_box.x1 - x1, y2 - y1, x2 - x1);
148                    let f3 = clipping_flags_y(y3, &self.clip_box);
149                    let f4 = clipping_flags_y(y4, &self.clip_box);
150                    self.line_clip_y(ras, self.clip_box.x2, y1, self.clip_box.x2, y3, f1, f3);
151                    self.line_clip_y(ras, self.clip_box.x2, y3, self.clip_box.x1, y4, f3, f4);
152                    self.line_clip_y(ras, self.clip_box.x1, y4, self.clip_box.x1, y2, f4, f2);
153                }
154                8 => {
155                    // x1 < clip.x1
156                    let y3 = y1 + mul_div(self.clip_box.x1 - x1, y2 - y1, x2 - x1);
157                    let f3 = clipping_flags_y(y3, &self.clip_box);
158                    self.line_clip_y(ras, self.clip_box.x1, y1, self.clip_box.x1, y3, f1, f3);
159                    self.line_clip_y(ras, self.clip_box.x1, y3, x2, y2, f3, f2);
160                }
161                9 => {
162                    // x1 < clip.x1 && x2 > clip.x2
163                    let y3 = y1 + mul_div(self.clip_box.x1 - x1, y2 - y1, x2 - x1);
164                    let y4 = y1 + mul_div(self.clip_box.x2 - x1, y2 - y1, x2 - x1);
165                    let f3 = clipping_flags_y(y3, &self.clip_box);
166                    let f4 = clipping_flags_y(y4, &self.clip_box);
167                    self.line_clip_y(ras, self.clip_box.x1, y1, self.clip_box.x1, y3, f1, f3);
168                    self.line_clip_y(ras, self.clip_box.x1, y3, self.clip_box.x2, y4, f3, f4);
169                    self.line_clip_y(ras, self.clip_box.x2, y4, self.clip_box.x2, y2, f4, f2);
170                }
171                12 => {
172                    // x1 < clip.x1 && x2 < clip.x1
173                    self.line_clip_y(ras, self.clip_box.x1, y1, self.clip_box.x1, y2, f1, f2);
174                }
175                _ => {
176                    // cases 5, 7, 10, 11 — cannot happen with valid clipping flags
177                }
178            }
179            self.f1 = f2;
180        } else {
181            ras.line(xi(self.x1), yi(self.y1), xi(x2), yi(y2));
182        }
183        self.x1 = x2;
184        self.y1 = y2;
185    }
186
187    /// Emit a line_to from double coordinates (upscales to 24.8).
188    pub fn line_to_d(&mut self, ras: &mut RasterizerCellsAa, x: f64, y: f64) {
189        self.line_to(ras, upscale(x), upscale(y));
190    }
191
192    /// Clip a line segment in Y and emit to the rasterizer.
193    #[allow(clippy::too_many_arguments)]
194    fn line_clip_y(
195        &self,
196        ras: &mut RasterizerCellsAa,
197        x1: i32,
198        y1: i32,
199        x2: i32,
200        y2: i32,
201        f1: u32,
202        f2: u32,
203    ) {
204        let f1 = f1 & 10;
205        let f2 = f2 & 10;
206
207        if (f1 | f2) == 0 {
208            // Fully visible
209            ras.line(xi(x1), yi(y1), xi(x2), yi(y2));
210        } else if f1 != f2 {
211            // Partially visible — clip in Y
212            let mut tx1 = x1;
213            let mut ty1 = y1;
214            let mut tx2 = x2;
215            let mut ty2 = y2;
216
217            if f1 & 8 != 0 {
218                // y1 < clip.y1
219                tx1 = x1 + mul_div(self.clip_box.y1 - y1, x2 - x1, y2 - y1);
220                ty1 = self.clip_box.y1;
221            }
222
223            if f1 & 2 != 0 {
224                // y1 > clip.y2
225                tx1 = x1 + mul_div(self.clip_box.y2 - y1, x2 - x1, y2 - y1);
226                ty1 = self.clip_box.y2;
227            }
228
229            if f2 & 8 != 0 {
230                // y2 < clip.y1
231                tx2 = x1 + mul_div(self.clip_box.y1 - y1, x2 - x1, y2 - y1);
232                ty2 = self.clip_box.y1;
233            }
234
235            if f2 & 2 != 0 {
236                // y2 > clip.y2
237                tx2 = x1 + mul_div(self.clip_box.y2 - y1, x2 - x1, y2 - y1);
238                ty2 = self.clip_box.y2;
239            }
240
241            ras.line(xi(tx1), yi(ty1), xi(tx2), yi(ty2));
242        }
243        // else: f1 == f2, both invisible by Y on same side → skip
244    }
245}
246
247impl Default for RasterizerSlClipInt {
248    fn default() -> Self {
249        Self::new()
250    }
251}
252
253// ============================================================================
254// RasterizerSlNoClip — passthrough (no clipping)
255// ============================================================================
256
257/// Scanline rasterizer policy that performs no clipping, just coordinate
258/// conversion (double → 24.8 fixed-point) and direct passthrough to the
259/// cell rasterizer.
260///
261/// Port of C++ `rasterizer_sl_no_clip`.
262pub struct RasterizerSlNoClip {
263    x1: i32,
264    y1: i32,
265}
266
267impl RasterizerSlNoClip {
268    pub fn new() -> Self {
269        Self { x1: 0, y1: 0 }
270    }
271
272    pub fn reset_clipping(&mut self) {}
273
274    pub fn clip_box(&mut self, _x1: i32, _y1: i32, _x2: i32, _y2: i32) {}
275
276    pub fn move_to(&mut self, x1: i32, y1: i32) {
277        self.x1 = x1;
278        self.y1 = y1;
279    }
280
281    pub fn move_to_d(&mut self, x: f64, y: f64) {
282        self.move_to(upscale(x), upscale(y));
283    }
284
285    pub fn line_to(&mut self, ras: &mut RasterizerCellsAa, x2: i32, y2: i32) {
286        ras.line(self.x1, self.y1, x2, y2);
287        self.x1 = x2;
288        self.y1 = y2;
289    }
290
291    pub fn line_to_d(&mut self, ras: &mut RasterizerCellsAa, x: f64, y: f64) {
292        self.line_to(ras, upscale(x), upscale(y));
293    }
294}
295
296impl Default for RasterizerSlNoClip {
297    fn default() -> Self {
298        Self::new()
299    }
300}
301
302// ============================================================================
303// Public helpers
304// ============================================================================
305
306/// Upscale a double coordinate to 24.8 fixed-point.
307/// Public wrapper around the internal `upscale` function.
308pub fn poly_coord(v: f64) -> i32 {
309    upscale(v)
310}
311
312// ============================================================================
313// Tests
314// ============================================================================
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319    use crate::basics::POLY_SUBPIXEL_SCALE;
320
321    #[test]
322    fn test_upscale() {
323        assert_eq!(upscale(0.0), 0);
324        assert_eq!(upscale(1.0), POLY_SUBPIXEL_SCALE as i32);
325        assert_eq!(upscale(10.5), iround(10.5 * POLY_SUBPIXEL_SCALE as f64));
326        assert_eq!(upscale(-1.0), -(POLY_SUBPIXEL_SCALE as i32));
327    }
328
329    #[test]
330    fn test_mul_div() {
331        assert_eq!(mul_div(10, 20, 5), 40);
332        assert_eq!(mul_div(0, 100, 1), 0);
333        assert_eq!(mul_div(7, 3, 2), 11); // round(10.5) = 11
334    }
335
336    // ------------------------------------------------------------------
337    // RasterizerSlClipInt tests
338    // ------------------------------------------------------------------
339
340    #[test]
341    fn test_clip_int_new() {
342        let clip = RasterizerSlClipInt::new();
343        assert!(!clip.clipping);
344    }
345
346    #[test]
347    fn test_clip_int_no_clip_passthrough() {
348        let mut clip = RasterizerSlClipInt::new();
349        let mut ras = RasterizerCellsAa::new();
350        let s = POLY_SUBPIXEL_SCALE as i32;
351
352        clip.move_to(0, 0);
353        clip.line_to(&mut ras, 10 * s, 10 * s);
354        ras.sort_cells();
355
356        assert!(ras.total_cells() > 0);
357    }
358
359    #[test]
360    fn test_clip_int_visible_line() {
361        let mut clip = RasterizerSlClipInt::new();
362        let s = POLY_SUBPIXEL_SCALE as i32;
363        clip.clip_box(0, 0, 100 * s, 100 * s);
364
365        let mut ras = RasterizerCellsAa::new();
366        clip.move_to(10 * s, 10 * s);
367        clip.line_to(&mut ras, 50 * s, 50 * s);
368        ras.sort_cells();
369
370        assert!(ras.total_cells() > 0);
371        assert!(ras.min_x() >= 10);
372        assert!(ras.max_x() <= 50);
373    }
374
375    #[test]
376    fn test_clip_int_fully_clipped_by_y() {
377        let mut clip = RasterizerSlClipInt::new();
378        let s = POLY_SUBPIXEL_SCALE as i32;
379        clip.clip_box(0, 10 * s, 100 * s, 90 * s);
380
381        let mut ras = RasterizerCellsAa::new();
382        // Line entirely above the clip box
383        clip.move_to(10 * s, 0);
384        clip.line_to(&mut ras, 50 * s, 5 * s);
385        ras.sort_cells();
386
387        assert_eq!(ras.total_cells(), 0);
388    }
389
390    #[test]
391    fn test_clip_int_clipped_by_x_right() {
392        let mut clip = RasterizerSlClipInt::new();
393        let s = POLY_SUBPIXEL_SCALE as i32;
394        clip.clip_box(0, 0, 50 * s, 100 * s);
395
396        let mut ras = RasterizerCellsAa::new();
397        clip.move_to(10 * s, 10 * s);
398        clip.line_to(&mut ras, 80 * s, 80 * s);
399        ras.sort_cells();
400
401        assert!(ras.total_cells() > 0);
402        // All cells should be within clip bounds
403        for cell in ras.cells() {
404            assert!(cell.x <= 50, "Cell x={} exceeds clip x2=50", cell.x);
405        }
406    }
407
408    #[test]
409    fn test_clip_int_reset_clipping() {
410        let mut clip = RasterizerSlClipInt::new();
411        let s = POLY_SUBPIXEL_SCALE as i32;
412        clip.clip_box(0, 0, 10 * s, 10 * s);
413        assert!(clip.clipping);
414        clip.reset_clipping();
415        assert!(!clip.clipping);
416    }
417
418    #[test]
419    fn test_clip_int_move_to_d() {
420        let mut clip = RasterizerSlClipInt::new();
421        clip.move_to_d(10.5, 20.5);
422        assert_eq!(clip.x1, upscale(10.5));
423        assert_eq!(clip.y1, upscale(20.5));
424    }
425
426    #[test]
427    fn test_clip_int_line_to_d() {
428        let mut clip = RasterizerSlClipInt::new();
429        let mut ras = RasterizerCellsAa::new();
430        clip.move_to_d(0.0, 0.0);
431        clip.line_to_d(&mut ras, 10.0, 10.0);
432        ras.sort_cells();
433        assert!(ras.total_cells() > 0);
434    }
435
436    // ------------------------------------------------------------------
437    // RasterizerSlNoClip tests
438    // ------------------------------------------------------------------
439
440    #[test]
441    fn test_no_clip_passthrough() {
442        let mut clip = RasterizerSlNoClip::new();
443        let mut ras = RasterizerCellsAa::new();
444        let s = POLY_SUBPIXEL_SCALE as i32;
445
446        clip.move_to(0, 0);
447        clip.line_to(&mut ras, 10 * s, 10 * s);
448        ras.sort_cells();
449
450        assert!(ras.total_cells() > 0);
451    }
452
453    #[test]
454    fn test_no_clip_double_api() {
455        let mut clip = RasterizerSlNoClip::new();
456        let mut ras = RasterizerCellsAa::new();
457
458        clip.move_to_d(0.0, 0.0);
459        clip.line_to_d(&mut ras, 5.0, 5.0);
460        ras.sort_cells();
461
462        assert!(ras.total_cells() > 0);
463    }
464
465    #[test]
466    fn test_poly_coord() {
467        assert_eq!(poly_coord(1.0), POLY_SUBPIXEL_SCALE as i32);
468        assert_eq!(poly_coord(0.0), 0);
469    }
470}