Skip to main content

agg_rust/
renderer_mclip.rs

1//! Multi-clip renderer.
2//!
3//! Port of `agg_renderer_mclip.h`.
4//! Wraps a `RendererBase` with multiple clipping rectangles.
5//! Each rendering operation is repeated for each clip box.
6
7use crate::basics::{CoverType, RectI};
8use crate::pixfmt_rgba::PixelFormat;
9use crate::renderer_base::RendererBase;
10
11/// Renderer that supports multiple independent clipping rectangles.
12///
13/// Port of C++ `renderer_mclip<PixelFormat>`.
14/// Each rendering call is automatically repeated for every clip box.
15pub struct RendererMclip<PF: PixelFormat> {
16    ren: RendererBase<PF>,
17    clip_boxes: Vec<RectI>,
18    curr_cb: usize,
19    bounds: RectI,
20}
21
22impl<PF: PixelFormat> RendererMclip<PF> {
23    pub fn new(ren: RendererBase<PF>) -> Self {
24        let bounds = *ren.clip_box();
25        Self {
26            ren,
27            clip_boxes: Vec::new(),
28            curr_cb: 0,
29            bounds,
30        }
31    }
32
33    pub fn ren(&self) -> &RendererBase<PF> {
34        &self.ren
35    }
36
37    pub fn ren_mut(&mut self) -> &mut RendererBase<PF> {
38        &mut self.ren
39    }
40
41    /// Add a clip box. The box is normalized and clipped to the buffer bounds.
42    pub fn add_clip_box(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
43        let mut cb = RectI::new(x1, y1, x2, y2);
44        cb.normalize();
45        let buf_rect = RectI::new(
46            0,
47            0,
48            self.ren.width() as i32 - 1,
49            self.ren.height() as i32 - 1,
50        );
51        if cb.clip(&buf_rect) {
52            if self.clip_boxes.is_empty() {
53                self.bounds = cb;
54            } else {
55                if cb.x1 < self.bounds.x1 {
56                    self.bounds.x1 = cb.x1;
57                }
58                if cb.y1 < self.bounds.y1 {
59                    self.bounds.y1 = cb.y1;
60                }
61                if cb.x2 > self.bounds.x2 {
62                    self.bounds.x2 = cb.x2;
63                }
64                if cb.y2 > self.bounds.y2 {
65                    self.bounds.y2 = cb.y2;
66                }
67            }
68            self.clip_boxes.push(cb);
69        }
70    }
71
72    /// Reset clipping. If `visibility` is true, adds the full buffer as a clip box.
73    pub fn reset_clipping(&mut self, visibility: bool) {
74        self.clip_boxes.clear();
75        if visibility {
76            let w = self.ren.width() as i32;
77            let h = self.ren.height() as i32;
78            self.add_clip_box(0, 0, w - 1, h - 1);
79        }
80    }
81
82    /// Set the current clip box to the first one. Returns true if there are any.
83    fn first_clip_box(&mut self) -> bool {
84        self.curr_cb = 0;
85        if !self.clip_boxes.is_empty() {
86            let cb = &self.clip_boxes[0];
87            self.ren.clip_box_i(cb.x1, cb.y1, cb.x2, cb.y2);
88            true
89        } else {
90            false
91        }
92    }
93
94    /// Advance to the next clip box. Returns true if there's another.
95    fn next_clip_box(&mut self) -> bool {
96        self.curr_cb += 1;
97        if self.curr_cb < self.clip_boxes.len() {
98            let cb = &self.clip_boxes[self.curr_cb];
99            self.ren.clip_box_i(cb.x1, cb.y1, cb.x2, cb.y2);
100            true
101        } else {
102            false
103        }
104    }
105
106    pub fn bounding_clip_box(&self) -> &RectI {
107        &self.bounds
108    }
109
110    pub fn clip_box_count(&self) -> usize {
111        self.clip_boxes.len()
112    }
113
114    // ========================================================================
115    // Rendering operations (iterate over all clip boxes)
116    // ========================================================================
117
118    pub fn copy_pixel(&mut self, x: i32, y: i32, c: &PF::ColorType) {
119        if self.first_clip_box() {
120            loop {
121                self.ren.copy_pixel(x, y, c);
122                if !self.next_clip_box() {
123                    break;
124                }
125            }
126        }
127    }
128
129    pub fn blend_pixel(&mut self, x: i32, y: i32, c: &PF::ColorType, cover: CoverType) {
130        if self.first_clip_box() {
131            loop {
132                self.ren.blend_pixel(x, y, c, cover);
133                if !self.next_clip_box() {
134                    break;
135                }
136            }
137        }
138    }
139
140    pub fn pixel(&self, x: i32, y: i32) -> PF::ColorType
141    where
142        PF::ColorType: Default,
143    {
144        // Check each clip box — read directly from pixfmt if point is inside any box.
145        for cb in &self.clip_boxes {
146            if x >= cb.x1 && y >= cb.y1 && x <= cb.x2 && y <= cb.y2 {
147                return self.ren.ren().pixel(x, y);
148            }
149        }
150        PF::ColorType::default()
151    }
152
153    pub fn copy_hline(&mut self, x1: i32, y: i32, x2: i32, c: &PF::ColorType) {
154        if self.first_clip_box() {
155            loop {
156                self.ren.copy_hline(x1, y, x2, c);
157                if !self.next_clip_box() {
158                    break;
159                }
160            }
161        }
162    }
163
164    pub fn blend_hline(
165        &mut self,
166        x1: i32,
167        y: i32,
168        x2: i32,
169        c: &PF::ColorType,
170        cover: CoverType,
171    ) {
172        if self.first_clip_box() {
173            loop {
174                self.ren.blend_hline(x1, y, x2, c, cover);
175                if !self.next_clip_box() {
176                    break;
177                }
178            }
179        }
180    }
181
182    pub fn blend_vline(
183        &mut self,
184        x: i32,
185        y1: i32,
186        y2: i32,
187        c: &PF::ColorType,
188        cover: CoverType,
189    ) {
190        if self.first_clip_box() {
191            loop {
192                self.ren.blend_vline(x, y1, y2, c, cover);
193                if !self.next_clip_box() {
194                    break;
195                }
196            }
197        }
198    }
199
200    pub fn blend_solid_hspan(
201        &mut self,
202        x: i32,
203        y: i32,
204        len: i32,
205        c: &PF::ColorType,
206        covers: &[CoverType],
207    ) {
208        if self.first_clip_box() {
209            loop {
210                self.ren.blend_solid_hspan(x, y, len, c, covers);
211                if !self.next_clip_box() {
212                    break;
213                }
214            }
215        }
216    }
217
218    pub fn blend_color_hspan(
219        &mut self,
220        x: i32,
221        y: i32,
222        len: i32,
223        colors: &[PF::ColorType],
224        covers: &[CoverType],
225        cover: CoverType,
226    ) {
227        if self.first_clip_box() {
228            loop {
229                self.ren.blend_color_hspan(x, y, len, colors, covers, cover);
230                if !self.next_clip_box() {
231                    break;
232                }
233            }
234        }
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use crate::color::Rgba8;
242    use crate::pixfmt_rgba::PixfmtRgba32;
243    use crate::rendering_buffer::RowAccessor;
244
245    fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
246        let stride = (w * 4) as i32;
247        let buf = vec![0u8; (h * w * 4) as usize];
248        let mut ra = RowAccessor::new();
249        unsafe {
250            ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
251        }
252        (buf, ra)
253    }
254
255    #[test]
256    fn test_add_clip_boxes() {
257        let (_buf, mut ra) = make_buffer(200, 200);
258        let pixf = PixfmtRgba32::new(&mut ra);
259        let ren = RendererBase::new(pixf);
260        let mut mclip = RendererMclip::new(ren);
261
262        mclip.add_clip_box(10, 10, 50, 50);
263        mclip.add_clip_box(100, 100, 150, 150);
264        assert_eq!(mclip.clip_box_count(), 2);
265
266        let b = mclip.bounding_clip_box();
267        assert_eq!(b.x1, 10);
268        assert_eq!(b.y1, 10);
269        assert_eq!(b.x2, 150);
270        assert_eq!(b.y2, 150);
271    }
272
273    #[test]
274    fn test_render_to_multiple_clips() {
275        let (_buf, mut ra) = make_buffer(100, 100);
276        let pixf = PixfmtRgba32::new(&mut ra);
277        let ren = RendererBase::new(pixf);
278        let mut mclip = RendererMclip::new(ren);
279
280        mclip.add_clip_box(0, 0, 49, 49);
281        mclip.add_clip_box(50, 50, 99, 99);
282
283        let red = Rgba8::new(255, 0, 0, 255);
284        mclip.copy_pixel(25, 25, &red); // inside clip box 0
285        mclip.copy_pixel(75, 75, &red); // inside clip box 1
286
287        let p1 = mclip.pixel(25, 25);
288        assert_eq!(p1.r, 255);
289        let p2 = mclip.pixel(75, 75);
290        assert_eq!(p2.r, 255);
291    }
292
293    #[test]
294    fn test_reset_clipping() {
295        let (_buf, mut ra) = make_buffer(100, 100);
296        let pixf = PixfmtRgba32::new(&mut ra);
297        let ren = RendererBase::new(pixf);
298        let mut mclip = RendererMclip::new(ren);
299
300        mclip.add_clip_box(10, 10, 50, 50);
301        assert_eq!(mclip.clip_box_count(), 1);
302
303        mclip.reset_clipping(false);
304        assert_eq!(mclip.clip_box_count(), 0);
305
306        mclip.reset_clipping(true);
307        assert_eq!(mclip.clip_box_count(), 1);
308    }
309}