image_renderer/
clip.rs

1//! 裁剪系统
2//!
3//! 提供矩形和路径裁剪功能
4
5use crate::path::Path;
6use crate::point::PointF;
7use crate::rect::{IRect, Rect};
8
9/// 裁剪操作类型
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ClipOp {
12    /// 交集(保留重叠部分)
13    Intersect,
14    /// 差集(从当前裁剪区域中减去新区域)
15    Difference,
16}
17
18/// 裁剪区域类型
19#[derive(Debug, Clone)]
20pub enum ClipRegion {
21    /// 矩形裁剪
22    Rect(IRect),
23    /// 路径裁剪
24    Path(Path),
25    /// 空裁剪(不绘制任何内容)
26    Empty,
27}
28
29impl ClipRegion {
30    /// 创建矩形裁剪区域
31    pub fn from_rect(rect: IRect) -> Self {
32        ClipRegion::Rect(rect)
33    }
34
35    /// 创建路径裁剪区域
36    pub fn from_path(path: Path) -> Self {
37        ClipRegion::Path(path)
38    }
39
40    /// 创建空裁剪区域
41    pub fn empty() -> Self {
42        ClipRegion::Empty
43    }
44
45    /// 判断点是否在裁剪区域内
46    pub fn contains_point(&self, x: i32, y: i32) -> bool {
47        match self {
48            ClipRegion::Rect(rect) => {
49                x >= rect.x
50                    && x < rect.x + rect.width as i32
51                    && y >= rect.y
52                    && y < rect.y + rect.height as i32
53            }
54            ClipRegion::Path(path) => {
55                // 使用射线法判断点是否在路径内
56                self.point_in_path(PointF::new(x as f32, y as f32), path)
57            }
58            ClipRegion::Empty => false,
59        }
60    }
61
62    /// 判断点是否在路径内(射线法)
63    fn point_in_path(&self, point: PointF, path: &Path) -> bool {
64        let commands = path.commands();
65        if commands.is_empty() {
66            return false;
67        }
68
69        let mut crossings = 0;
70        let mut current = PointF::new(0.0, 0.0);
71        let mut start = PointF::new(0.0, 0.0);
72
73        for cmd in commands {
74            match cmd {
75                crate::path::PathCommand::MoveTo(p) => {
76                    current = *p;
77                    start = *p;
78                }
79                crate::path::PathCommand::LineTo(p) => {
80                    if self.ray_intersects_segment(point, current, *p) {
81                        crossings += 1;
82                    }
83                    current = *p;
84                }
85                crate::path::PathCommand::Close => {
86                    if self.ray_intersects_segment(point, current, start) {
87                        crossings += 1;
88                    }
89                    current = start;
90                }
91                crate::path::PathCommand::QuadTo { end, .. } => {
92                    // 简化处理:将二次贝塞尔曲线当作直线
93                    if self.ray_intersects_segment(point, current, *end) {
94                        crossings += 1;
95                    }
96                    current = *end;
97                }
98                crate::path::PathCommand::CubicTo { end, .. } => {
99                    // 简化处理:将三次贝塞尔曲线当作直线
100                    if self.ray_intersects_segment(point, current, *end) {
101                        crossings += 1;
102                    }
103                    current = *end;
104                }
105                crate::path::PathCommand::ArcTo { .. } => {
106                    // 圆弧暂不处理
107                }
108            }
109        }
110
111        // 奇数次交叉表示在路径内
112        crossings % 2 == 1
113    }
114
115    /// 判断从点发出的水平射线是否与线段相交
116    fn ray_intersects_segment(&self, point: PointF, p1: PointF, p2: PointF) -> bool {
117        // 射线向右延伸
118        let py = point.y;
119        let px = point.x;
120
121        // 线段的 y 坐标范围
122        let (y_min, y_max) = if p1.y < p2.y {
123            (p1.y, p2.y)
124        } else {
125            (p2.y, p1.y)
126        };
127
128        // 射线的 y 坐标不在线段 y 范围内
129        if py < y_min || py >= y_max {
130            return false;
131        }
132
133        // 计算交点的 x 坐标
134        let t = (py - p1.y) / (p2.y - p1.y);
135        let x_intersect = p1.x + t * (p2.x - p1.x);
136
137        // 交点在射线上(点的右侧)
138        x_intersect >= px
139    }
140
141    /// 获取裁剪区域的边界矩形
142    pub fn bounds(&self) -> Option<IRect> {
143        match self {
144            ClipRegion::Rect(rect) => Some(*rect),
145            ClipRegion::Path(path) => path
146                .bounds()
147                .map(|r| IRect::new(r.x, r.y, r.width, r.height)),
148            ClipRegion::Empty => None,
149        }
150    }
151
152    /// 判断裁剪区域是否为空
153    pub fn is_empty(&self) -> bool {
154        matches!(self, ClipRegion::Empty)
155    }
156
157    /// 与另一个裁剪区域求交集
158    pub fn intersect(&self, other: &ClipRegion) -> ClipRegion {
159        match (self, other) {
160            (ClipRegion::Empty, _) | (_, ClipRegion::Empty) => ClipRegion::Empty,
161            (ClipRegion::Rect(r1), ClipRegion::Rect(r2)) => {
162                // 计算两个矩形的交集
163                let x1 = r1.x.max(r2.x);
164                let y1 = r1.y.max(r2.y);
165                let x2 = (r1.x + r1.width as i32).min(r2.x + r2.width as i32);
166                let y2 = (r1.y + r1.height as i32).min(r2.y + r2.height as i32);
167
168                if x1 < x2 && y1 < y2 {
169                    ClipRegion::Rect(IRect::new(x1, y1, (x2 - x1) as u32, (y2 - y1) as u32))
170                } else {
171                    ClipRegion::Empty
172                }
173            }
174            (ClipRegion::Rect(rect), ClipRegion::Path(path))
175            | (ClipRegion::Path(path), ClipRegion::Rect(rect)) => {
176                // 简化处理:使用路径的边界矩形与矩形求交集
177                if let Some(path_bounds) = path.bounds() {
178                    let path_irect = IRect::new(
179                        path_bounds.x,
180                        path_bounds.y,
181                        path_bounds.width,
182                        path_bounds.height,
183                    );
184                    ClipRegion::Rect(path_irect).intersect(&ClipRegion::Rect(*rect))
185                } else {
186                    ClipRegion::Empty
187                }
188            }
189            (ClipRegion::Path(_), ClipRegion::Path(_)) => {
190                // 路径与路径的交集较复杂,这里简化处理
191                // 实际应用中可以使用更复杂的算法
192                self.clone()
193            }
194        }
195    }
196
197    /// 从当前裁剪区域中减去另一个区域
198    pub fn difference(&self, other: &ClipRegion) -> ClipRegion {
199        match (self, other) {
200            (ClipRegion::Empty, _) => ClipRegion::Empty,
201            (_, ClipRegion::Empty) => self.clone(),
202            // 差集操作较复杂,这里简化处理
203            // 实际应用中需要更复杂的算法
204            _ => self.clone(),
205        }
206    }
207}
208
209/// 裁剪栈
210///
211/// 用于管理多层裁剪区域
212#[derive(Debug, Clone)]
213pub struct ClipStack {
214    /// 裁剪区域栈
215    stack: Vec<ClipRegion>,
216}
217
218impl ClipStack {
219    /// 创建新的裁剪栈
220    pub fn new() -> Self {
221        Self { stack: Vec::new() }
222    }
223
224    /// 创建带初始裁剪区域的栈
225    pub fn with_rect(rect: IRect) -> Self {
226        let mut stack = Self::new();
227        stack.push(ClipRegion::Rect(rect));
228        stack
229    }
230
231    /// 压入新的裁剪区域
232    pub fn push(&mut self, region: ClipRegion) {
233        self.stack.push(region);
234    }
235
236    /// 弹出裁剪区域
237    pub fn pop(&mut self) -> Option<ClipRegion> {
238        self.stack.pop()
239    }
240
241    /// 获取当前有效的裁剪区域
242    pub fn current(&self) -> Option<&ClipRegion> {
243        self.stack.last()
244    }
245
246    /// 应用裁剪操作
247    pub fn clip(&mut self, region: ClipRegion, op: ClipOp) {
248        if let Some(current) = self.stack.last() {
249            let new_region = match op {
250                ClipOp::Intersect => current.intersect(&region),
251                ClipOp::Difference => current.difference(&region),
252            };
253            self.stack.push(new_region);
254        } else {
255            // 如果栈为空,直接压入新区域
256            self.stack.push(region);
257        }
258    }
259
260    /// 裁剪矩形
261    pub fn clip_rect(&mut self, rect: IRect, op: ClipOp) {
262        self.clip(ClipRegion::Rect(rect), op);
263    }
264
265    /// 裁剪路径
266    pub fn clip_path(&mut self, path: Path, op: ClipOp) {
267        self.clip(ClipRegion::Path(path), op);
268    }
269
270    /// 判断点是否在当前裁剪区域内
271    pub fn contains_point(&self, x: i32, y: i32) -> bool {
272        if let Some(region) = self.current() {
273            region.contains_point(x, y)
274        } else {
275            true // 没有裁剪区域时,所有点都可见
276        }
277    }
278
279    /// 获取当前裁剪区域的边界
280    pub fn bounds(&self) -> Option<IRect> {
281        self.current().and_then(|r| r.bounds())
282    }
283
284    /// 判断栈是否为空
285    pub fn is_empty(&self) -> bool {
286        self.stack.is_empty()
287    }
288
289    /// 获取栈的深度
290    pub fn depth(&self) -> usize {
291        self.stack.len()
292    }
293
294    /// 清空裁剪栈
295    pub fn clear(&mut self) {
296        self.stack.clear();
297    }
298
299    /// 保存当前裁剪状态(用于状态栈)
300    pub fn save(&mut self) {
301        // 复制当前裁剪区域到栈顶
302        if let Some(current) = self.current() {
303            self.stack.push(current.clone());
304        }
305    }
306
307    /// 恢复之前的裁剪状态
308    pub fn restore(&mut self) {
309        self.pop();
310    }
311
312    /// 获取当前裁剪区域的边界(别名方法)
313    pub fn get_bounds(&self) -> Option<Rect> {
314        self.bounds()
315            .map(|r| Rect::new(r.x, r.y, r.width, r.height))
316    }
317}
318
319impl Default for ClipStack {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_clip_region_rect() {
331        let rect = IRect::new(10, 10, 100, 100);
332        let region = ClipRegion::from_rect(rect);
333
334        assert!(region.contains_point(50, 50));
335        assert!(!region.contains_point(5, 5));
336        assert!(!region.contains_point(150, 150));
337    }
338
339    #[test]
340    fn test_clip_region_intersect() {
341        let rect1 = IRect::new(0, 0, 100, 100);
342        let rect2 = IRect::new(50, 50, 100, 100);
343
344        let region1 = ClipRegion::from_rect(rect1);
345        let region2 = ClipRegion::from_rect(rect2);
346
347        let intersection = region1.intersect(&region2);
348
349        if let ClipRegion::Rect(result) = intersection {
350            assert_eq!(result.x, 50);
351            assert_eq!(result.y, 50);
352            assert_eq!(result.width, 50);
353            assert_eq!(result.height, 50);
354        } else {
355            panic!("Expected Rect region");
356        }
357    }
358
359    #[test]
360    fn test_clip_stack() {
361        let mut stack = ClipStack::new();
362        assert!(stack.is_empty());
363
364        let rect = IRect::new(0, 0, 100, 100);
365        stack.push(ClipRegion::from_rect(rect));
366        assert_eq!(stack.depth(), 1);
367
368        assert!(stack.contains_point(50, 50));
369        assert!(!stack.contains_point(150, 150));
370
371        stack.pop();
372        assert!(stack.is_empty());
373    }
374
375    #[test]
376    fn test_clip_stack_intersect() {
377        let mut stack = ClipStack::new();
378
379        let rect1 = IRect::new(0, 0, 100, 100);
380        stack.clip_rect(rect1, ClipOp::Intersect);
381
382        let rect2 = IRect::new(50, 50, 100, 100);
383        stack.clip_rect(rect2, ClipOp::Intersect);
384
385        // 交集应该是 (50, 50, 50, 50)
386        assert!(stack.contains_point(75, 75));
387        assert!(!stack.contains_point(25, 25));
388        assert!(!stack.contains_point(125, 125));
389    }
390
391    #[test]
392    fn test_clip_region_bounds() {
393        let rect = IRect::new(10, 20, 100, 50);
394        let region = ClipRegion::from_rect(rect);
395        let bounds = region.bounds().unwrap();
396
397        assert_eq!(bounds.x, 10);
398        assert_eq!(bounds.y, 20);
399        assert_eq!(bounds.width, 100);
400        assert_eq!(bounds.height, 50);
401    }
402
403    #[test]
404    fn test_empty_clip_region() {
405        let region = ClipRegion::empty();
406        assert!(region.is_empty());
407        assert!(!region.contains_point(0, 0));
408        assert!(region.bounds().is_none());
409    }
410}