image_renderer/
path.rs

1//! 路径系统
2//!
3//! 提供类似 skia-safe 的路径绘制功能
4
5use crate::point::PointF;
6use crate::rect::Rect;
7
8/// 路径命令
9#[derive(Debug, Clone, PartialEq)]
10pub enum PathCommand {
11    /// 移动到指定点(不绘制)
12    MoveTo(PointF),
13    /// 绘制直线到指定点
14    LineTo(PointF),
15    /// 绘制二次贝塞尔曲线
16    QuadTo {
17        /// 控制点
18        control: PointF,
19        /// 终点
20        end: PointF,
21    },
22    /// 绘制三次贝塞尔曲线
23    CubicTo {
24        /// 第一个控制点
25        control1: PointF,
26        /// 第二个控制点
27        control2: PointF,
28        /// 终点
29        end: PointF,
30    },
31    /// 绘制圆弧
32    ArcTo {
33        /// 圆弧矩形
34        oval: Rect,
35        /// 起始角度(度)
36        start_angle: f32,
37        /// 扫描角度(度)
38        sweep_angle: f32,
39        /// 是否强制移动到起点
40        force_move_to: bool,
41    },
42    /// 闭合路径
43    Close,
44}
45
46/// 路径填充规则
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum PathFillType {
49    /// 非零环绕规则
50    Winding,
51    /// 奇偶规则
52    EvenOdd,
53}
54
55impl Default for PathFillType {
56    fn default() -> Self {
57        PathFillType::Winding
58    }
59}
60
61/// 路径方向
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum PathDirection {
64    /// 顺时针
65    CW,
66    /// 逆时针
67    CCW,
68}
69
70/// 路径
71///
72/// 用于构建和绘制复杂的矢量图形
73#[derive(Debug, Clone)]
74pub struct Path {
75    /// 路径命令列表
76    commands: Vec<PathCommand>,
77    /// 填充规则
78    fill_type: PathFillType,
79    /// 当前点位置
80    current_point: Option<PointF>,
81    /// 最后一次 MoveTo 的位置
82    last_move_to: Option<PointF>,
83}
84
85impl Path {
86    /// 创建新的空路径
87    pub fn new() -> Self {
88        Self {
89            commands: Vec::new(),
90            fill_type: PathFillType::default(),
91            current_point: None,
92            last_move_to: None,
93        }
94    }
95
96    /// 移动到指定点(不绘制线条)
97    pub fn move_to(&mut self, point: impl Into<PointF>) {
98        let point = point.into();
99        self.commands.push(PathCommand::MoveTo(point));
100        self.current_point = Some(point);
101        self.last_move_to = Some(point);
102    }
103
104    /// 从当前点绘制直线到指定点
105    pub fn line_to(&mut self, point: impl Into<PointF>) {
106        let point = point.into();
107        self.commands.push(PathCommand::LineTo(point));
108        self.current_point = Some(point);
109    }
110
111    /// 绘制二次贝塞尔曲线
112    pub fn quad_to(&mut self, control: impl Into<PointF>, end: impl Into<PointF>) {
113        let control = control.into();
114        let end = end.into();
115        self.commands.push(PathCommand::QuadTo { control, end });
116        self.current_point = Some(end);
117    }
118
119    /// 绘制三次贝塞尔曲线
120    pub fn cubic_to(
121        &mut self,
122        control1: impl Into<PointF>,
123        control2: impl Into<PointF>,
124        end: impl Into<PointF>,
125    ) {
126        let control1 = control1.into();
127        let control2 = control2.into();
128        let end = end.into();
129        self.commands.push(PathCommand::CubicTo {
130            control1,
131            control2,
132            end,
133        });
134        self.current_point = Some(end);
135    }
136
137    /// 绘制圆弧
138    pub fn arc_to(&mut self, oval: Rect, start_angle: f32, sweep_angle: f32, force_move_to: bool) {
139        self.commands.push(PathCommand::ArcTo {
140            oval,
141            start_angle,
142            sweep_angle,
143            force_move_to,
144        });
145    }
146
147    /// 闭合当前路径(连接到最后一次 MoveTo 的位置)
148    pub fn close(&mut self) {
149        if self.last_move_to.is_some() {
150            self.commands.push(PathCommand::Close);
151            self.current_point = self.last_move_to;
152        }
153    }
154
155    /// 添加矩形路径
156    pub fn add_rect(&mut self, rect: Rect, dir: PathDirection) {
157        let (x, y, w, h) = (
158            rect.x as f32,
159            rect.y as f32,
160            rect.width as f32,
161            rect.height as f32,
162        );
163
164        match dir {
165            PathDirection::CW => {
166                self.move_to(PointF::new(x, y));
167                self.line_to(PointF::new(x + w, y));
168                self.line_to(PointF::new(x + w, y + h));
169                self.line_to(PointF::new(x, y + h));
170            }
171            PathDirection::CCW => {
172                self.move_to(PointF::new(x, y));
173                self.line_to(PointF::new(x, y + h));
174                self.line_to(PointF::new(x + w, y + h));
175                self.line_to(PointF::new(x + w, y));
176            }
177        }
178        self.close();
179    }
180
181    /// 添加圆形路径
182    pub fn add_circle(&mut self, center: impl Into<PointF>, radius: f32, dir: PathDirection) {
183        let center = center.into();
184        let rect = Rect::new(
185            (center.x - radius) as i32,
186            (center.y - radius) as i32,
187            (radius * 2.0) as u32,
188            (radius * 2.0) as u32,
189        );
190        self.add_oval(rect, dir);
191    }
192
193    /// 添加椭圆路径
194    pub fn add_oval(&mut self, oval: Rect, dir: PathDirection) {
195        let (cx, cy) = (
196            oval.x as f32 + oval.width as f32 / 2.0,
197            oval.y as f32 + oval.height as f32 / 2.0,
198        );
199        let (rx, ry) = (oval.width as f32 / 2.0, oval.height as f32 / 2.0);
200
201        // 使用四段三次贝塞尔曲线近似椭圆
202        let kappa = 0.5522847498; // 4/3 * (sqrt(2) - 1)
203        let ox = rx * kappa;
204        let oy = ry * kappa;
205
206        match dir {
207            PathDirection::CW => {
208                self.move_to(PointF::new(cx - rx, cy));
209                self.cubic_to(
210                    PointF::new(cx - rx, cy - oy),
211                    PointF::new(cx - ox, cy - ry),
212                    PointF::new(cx, cy - ry),
213                );
214                self.cubic_to(
215                    PointF::new(cx + ox, cy - ry),
216                    PointF::new(cx + rx, cy - oy),
217                    PointF::new(cx + rx, cy),
218                );
219                self.cubic_to(
220                    PointF::new(cx + rx, cy + oy),
221                    PointF::new(cx + ox, cy + ry),
222                    PointF::new(cx, cy + ry),
223                );
224                self.cubic_to(
225                    PointF::new(cx - ox, cy + ry),
226                    PointF::new(cx - rx, cy + oy),
227                    PointF::new(cx - rx, cy),
228                );
229            }
230            PathDirection::CCW => {
231                self.move_to(PointF::new(cx - rx, cy));
232                self.cubic_to(
233                    PointF::new(cx - rx, cy + oy),
234                    PointF::new(cx - ox, cy + ry),
235                    PointF::new(cx, cy + ry),
236                );
237                self.cubic_to(
238                    PointF::new(cx + ox, cy + ry),
239                    PointF::new(cx + rx, cy + oy),
240                    PointF::new(cx + rx, cy),
241                );
242                self.cubic_to(
243                    PointF::new(cx + rx, cy - oy),
244                    PointF::new(cx + ox, cy - ry),
245                    PointF::new(cx, cy - ry),
246                );
247                self.cubic_to(
248                    PointF::new(cx - ox, cy - ry),
249                    PointF::new(cx - rx, cy - oy),
250                    PointF::new(cx - rx, cy),
251                );
252            }
253        }
254        self.close();
255    }
256
257    /// 添加圆角矩形路径
258    pub fn add_rounded_rect(&mut self, rect: Rect, radius: f32, dir: PathDirection) {
259        let (x, y, w, h) = (
260            rect.x as f32,
261            rect.y as f32,
262            rect.width as f32,
263            rect.height as f32,
264        );
265        let r = radius.min(w / 2.0).min(h / 2.0);
266
267        if r <= 0.0 {
268            self.add_rect(rect, dir);
269            return;
270        }
271
272        let kappa = 0.5522847498;
273        let o = r * kappa;
274
275        match dir {
276            PathDirection::CW => {
277                self.move_to(PointF::new(x + r, y));
278                self.line_to(PointF::new(x + w - r, y));
279                self.cubic_to(
280                    PointF::new(x + w - r + o, y),
281                    PointF::new(x + w, y + r - o),
282                    PointF::new(x + w, y + r),
283                );
284                self.line_to(PointF::new(x + w, y + h - r));
285                self.cubic_to(
286                    PointF::new(x + w, y + h - r + o),
287                    PointF::new(x + w - r + o, y + h),
288                    PointF::new(x + w - r, y + h),
289                );
290                self.line_to(PointF::new(x + r, y + h));
291                self.cubic_to(
292                    PointF::new(x + r - o, y + h),
293                    PointF::new(x, y + h - r + o),
294                    PointF::new(x, y + h - r),
295                );
296                self.line_to(PointF::new(x, y + r));
297                self.cubic_to(
298                    PointF::new(x, y + r - o),
299                    PointF::new(x + r - o, y),
300                    PointF::new(x + r, y),
301                );
302            }
303            PathDirection::CCW => {
304                self.move_to(PointF::new(x + r, y));
305                self.cubic_to(
306                    PointF::new(x + r - o, y),
307                    PointF::new(x, y + r - o),
308                    PointF::new(x, y + r),
309                );
310                self.line_to(PointF::new(x, y + h - r));
311                self.cubic_to(
312                    PointF::new(x, y + h - r + o),
313                    PointF::new(x + r - o, y + h),
314                    PointF::new(x + r, y + h),
315                );
316                self.line_to(PointF::new(x + w - r, y + h));
317                self.cubic_to(
318                    PointF::new(x + w - r + o, y + h),
319                    PointF::new(x + w, y + h - r + o),
320                    PointF::new(x + w, y + h - r),
321                );
322                self.line_to(PointF::new(x + w, y + r));
323                self.cubic_to(
324                    PointF::new(x + w, y + r - o),
325                    PointF::new(x + w - r + o, y),
326                    PointF::new(x + w - r, y),
327                );
328            }
329        }
330        self.close();
331    }
332
333    /// 重置路径(清空所有命令)
334    pub fn reset(&mut self) {
335        self.commands.clear();
336        self.current_point = None;
337        self.last_move_to = None;
338    }
339
340    /// 设置填充规则
341    pub fn set_fill_type(&mut self, fill_type: PathFillType) {
342        self.fill_type = fill_type;
343    }
344
345    /// 获取填充规则
346    pub fn fill_type(&self) -> PathFillType {
347        self.fill_type
348    }
349
350    /// 获取路径命令列表
351    pub fn commands(&self) -> &[PathCommand] {
352        &self.commands
353    }
354
355    /// 判断路径是否为空
356    pub fn is_empty(&self) -> bool {
357        self.commands.is_empty()
358    }
359
360    /// 获取路径命令数量
361    pub fn count_points(&self) -> usize {
362        self.commands.len()
363    }
364
365    /// 获取当前点位置
366    pub fn current_point(&self) -> Option<PointF> {
367        self.current_point
368    }
369
370    /// 获取路径的边界框
371    pub fn bounds(&self) -> Option<Rect> {
372        if self.commands.is_empty() {
373            return None;
374        }
375
376        let mut min_x = f32::MAX;
377        let mut min_y = f32::MAX;
378        let mut max_x = f32::MIN;
379        let mut max_y = f32::MIN;
380
381        for cmd in &self.commands {
382            match cmd {
383                PathCommand::MoveTo(p) | PathCommand::LineTo(p) => {
384                    min_x = min_x.min(p.x);
385                    min_y = min_y.min(p.y);
386                    max_x = max_x.max(p.x);
387                    max_y = max_y.max(p.y);
388                }
389                PathCommand::QuadTo { control, end } => {
390                    min_x = min_x.min(control.x).min(end.x);
391                    min_y = min_y.min(control.y).min(end.y);
392                    max_x = max_x.max(control.x).max(end.x);
393                    max_y = max_y.max(control.y).max(end.y);
394                }
395                PathCommand::CubicTo {
396                    control1,
397                    control2,
398                    end,
399                } => {
400                    min_x = min_x.min(control1.x).min(control2.x).min(end.x);
401                    min_y = min_y.min(control1.y).min(control2.y).min(end.y);
402                    max_x = max_x.max(control1.x).max(control2.x).max(end.x);
403                    max_y = max_y.max(control1.y).max(control2.y).max(end.y);
404                }
405                PathCommand::ArcTo { oval, .. } => {
406                    min_x = min_x.min(oval.x as f32);
407                    min_y = min_y.min(oval.y as f32);
408                    max_x = max_x.max((oval.x + oval.width as i32) as f32);
409                    max_y = max_y.max((oval.y + oval.height as i32) as f32);
410                }
411                PathCommand::Close => {}
412            }
413        }
414
415        if min_x <= max_x && min_y <= max_y {
416            Some(Rect::new(
417                min_x as i32,
418                min_y as i32,
419                (max_x - min_x) as u32,
420                (max_y - min_y) as u32,
421            ))
422        } else {
423            None
424        }
425    }
426
427    /// 应用变换矩阵到路径
428    pub fn transform(&self, matrix: &crate::matrix::Matrix) -> Path {
429        let mut new_path = Path {
430            commands: Vec::new(),
431            fill_type: self.fill_type,
432            current_point: None,
433            last_move_to: None,
434        };
435
436        for cmd in &self.commands {
437            match cmd {
438                PathCommand::MoveTo(p) => {
439                    let (x, y) = matrix.map_point(p.x, p.y);
440                    let new_point = PointF { x, y };
441                    new_path.commands.push(PathCommand::MoveTo(new_point));
442                    new_path.current_point = Some(new_point);
443                    new_path.last_move_to = Some(new_point);
444                }
445                PathCommand::LineTo(p) => {
446                    let (x, y) = matrix.map_point(p.x, p.y);
447                    let new_point = PointF { x, y };
448                    new_path.commands.push(PathCommand::LineTo(new_point));
449                    new_path.current_point = Some(new_point);
450                }
451                PathCommand::QuadTo { control, end } => {
452                    let (cx, cy) = matrix.map_point(control.x, control.y);
453                    let (ex, ey) = matrix.map_point(end.x, end.y);
454                    new_path.commands.push(PathCommand::QuadTo {
455                        control: PointF { x: cx, y: cy },
456                        end: PointF { x: ex, y: ey },
457                    });
458                    new_path.current_point = Some(PointF { x: ex, y: ey });
459                }
460                PathCommand::CubicTo {
461                    control1,
462                    control2,
463                    end,
464                } => {
465                    let (c1x, c1y) = matrix.map_point(control1.x, control1.y);
466                    let (c2x, c2y) = matrix.map_point(control2.x, control2.y);
467                    let (ex, ey) = matrix.map_point(end.x, end.y);
468                    new_path.commands.push(PathCommand::CubicTo {
469                        control1: PointF { x: c1x, y: c1y },
470                        control2: PointF { x: c2x, y: c2y },
471                        end: PointF { x: ex, y: ey },
472                    });
473                    new_path.current_point = Some(PointF { x: ex, y: ey });
474                }
475                PathCommand::ArcTo {
476                    oval,
477                    start_angle,
478                    sweep_angle,
479                    force_move_to,
480                } => {
481                    // 对于圆弧,我们需要变换矩形
482                    let (x1, y1) = matrix.map_point(oval.x as f32, oval.y as f32);
483                    let (x2, y2) = matrix.map_point(
484                        (oval.x + oval.width as i32) as f32,
485                        (oval.y + oval.height as i32) as f32,
486                    );
487                    let new_oval = crate::rect::Rect::new(
488                        x1 as i32,
489                        y1 as i32,
490                        (x2 - x1).abs() as u32,
491                        (y2 - y1).abs() as u32,
492                    );
493                    new_path.commands.push(PathCommand::ArcTo {
494                        oval: new_oval,
495                        start_angle: *start_angle,
496                        sweep_angle: *sweep_angle,
497                        force_move_to: *force_move_to,
498                    });
499                }
500                PathCommand::Close => {
501                    new_path.commands.push(PathCommand::Close);
502                    new_path.current_point = new_path.last_move_to;
503                }
504            }
505        }
506
507        new_path
508    }
509
510    /// 判断点是否在路径内
511    pub fn contains(&self, x: f32, y: f32) -> bool {
512        // 使用射线法判断点是否在路径内
513        let mut crossings = 0;
514        let mut current_point = PointF { x: 0.0, y: 0.0 };
515        let mut start_point = PointF { x: 0.0, y: 0.0 };
516
517        for cmd in &self.commands {
518            match cmd {
519                PathCommand::MoveTo(p) => {
520                    current_point = *p;
521                    start_point = *p;
522                }
523                PathCommand::LineTo(p) => {
524                    if self.line_crosses_ray(current_point, *p, x, y) {
525                        crossings += 1;
526                    }
527                    current_point = *p;
528                }
529                PathCommand::Close => {
530                    if self.line_crosses_ray(current_point, start_point, x, y) {
531                        crossings += 1;
532                    }
533                    current_point = start_point;
534                }
535                _ => {
536                    // 对于曲线,简化处理:将其视为直线
537                    // 实际应用中应该使用更精确的算法
538                }
539            }
540        }
541
542        // 根据填充规则判断
543        match self.fill_type {
544            PathFillType::Winding => crossings != 0,
545            PathFillType::EvenOdd => crossings % 2 != 0,
546        }
547    }
548
549    /// 辅助方法:判断线段是否与从点 (x, y) 向右的射线相交
550    fn line_crosses_ray(&self, p1: PointF, p2: PointF, x: f32, y: f32) -> bool {
551        // 如果线段完全在点的上方或下方,不相交
552        if (p1.y > y && p2.y > y) || (p1.y < y && p2.y < y) {
553            return false;
554        }
555
556        // 如果线段是水平的且在点的高度上,特殊处理
557        if (p1.y - p2.y).abs() < 1e-6 {
558            return false;
559        }
560
561        // 计算射线与线段的交点的 x 坐标
562        let t = (y - p1.y) / (p2.y - p1.y);
563        let intersection_x = p1.x + t * (p2.x - p1.x);
564
565        // 如果交点在点的右侧,则相交
566        intersection_x > x
567    }
568}
569
570impl Default for Path {
571    fn default() -> Self {
572        Self::new()
573    }
574}
575
576// 实现 From trait 以支持 Point 到 PointF 的转换
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581
582    #[test]
583    fn test_path_creation() {
584        let path = Path::new();
585        assert!(path.is_empty());
586        assert_eq!(path.count_points(), 0);
587    }
588
589    #[test]
590    fn test_path_move_to() {
591        let mut path = Path::new();
592        path.move_to(PointF::new(10.0, 20.0));
593        assert_eq!(path.count_points(), 1);
594        assert_eq!(path.current_point(), Some(PointF::new(10.0, 20.0)));
595    }
596
597    #[test]
598    fn test_path_line_to() {
599        let mut path = Path::new();
600        path.move_to(PointF::new(0.0, 0.0));
601        path.line_to(PointF::new(10.0, 10.0));
602        assert_eq!(path.count_points(), 2);
603    }
604
605    #[test]
606    fn test_path_rect() {
607        let mut path = Path::new();
608        path.add_rect(Rect::new(10, 10, 100, 50), PathDirection::CW);
609        assert!(!path.is_empty());
610    }
611
612    #[test]
613    fn test_path_circle() {
614        let mut path = Path::new();
615        path.add_circle(PointF::new(50.0, 50.0), 25.0, PathDirection::CW);
616        assert!(!path.is_empty());
617    }
618
619    #[test]
620    fn test_path_bounds() {
621        let mut path = Path::new();
622        path.move_to(PointF::new(10.0, 20.0));
623        path.line_to(PointF::new(100.0, 80.0));
624        let bounds = path.bounds().unwrap();
625        assert_eq!(bounds.x, 10);
626        assert_eq!(bounds.y, 20);
627    }
628}