From 09d29473bb03d1cc0c953581aad7ff644e9e4c92 Mon Sep 17 00:00:00 2001 From: yeqing Date: Tue, 3 Mar 2026 21:06:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A4=AD=E5=9C=86=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=BA=BF=E5=AE=BD=E5=92=8C=E4=B8=80=E4=BA=9B=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mscanvas.rs | 122 ++++++++++++++++++++++++++---------------------- src/paint.rs | 65 +++++++++++--------------- 2 files changed, 91 insertions(+), 96 deletions(-) diff --git a/src/mscanvas.rs b/src/mscanvas.rs index d6a5732..faed9e4 100644 --- a/src/mscanvas.rs +++ b/src/mscanvas.rs @@ -87,8 +87,8 @@ impl Edge { return None; } - let dx = (bottom.x - top.x); - let dy = (bottom.y - top.y); + let dx = bottom.x - top.x; + let dy = bottom.y - top.y; let dx_dy = dx / dy; Some(Edge { @@ -208,7 +208,7 @@ impl MSCanvas { pub fn pixel_at(&self, x: i32, y: i32) -> MSColor { // 边界检查 - if x < 0 || x >= self.width || y < 0 || y >= self.height as i32 { + if x < 0 || x >= self.width || y < 0 || y >= self.height { return MSColor::ZERO; } @@ -273,7 +273,7 @@ impl MSCanvas { } let xs = xs.clamp(0, self.width - 1); - let xe = xe.clamp(0, self.width as i32); + let xe = xe.clamp(0, self.width); for x in xs..xe { let index = ((y * self.width + x) * 4) as usize; @@ -885,7 +885,7 @@ impl MSCanvas { } let xs = xs.clamp(0, self.width - 1); - let xe = xe.clamp(0, self.width as i32); + let xe = xe.clamp(0, self.width); for x in xs..xe { let index = ((y * self.width + x) * 4) as usize; @@ -951,47 +951,63 @@ impl MSCanvas { self.bezier(points); } + /// 绘制带线宽的椭圆(描边) + /// - center: 椭圆中心 + /// - rx, ry: 椭圆的水平/垂直半径(指中心线位置) pub fn stroke_ellipse(&mut self, center: Point, rx: f32, ry: f32) { - let mut x = 0.0; - let mut y = ry; // 初始时将y设置为半高 - - // 计算初始决策参数 - let mut decision = ry * ry - rx * rx * ry + rx * rx / 4.0; - - while ry * ry * x < rx * rx * y { - // 在每个阶段,根据对称性绘制四个方向上的点 - self.stroke_pixel_at(Point::new(center.x + x, center.y + y)); - self.stroke_pixel_at(Point::new(center.x - x, center.y + y)); - self.stroke_pixel_at(Point::new(center.x + x, center.y - y)); - self.stroke_pixel_at(Point::new(center.x - x, center.y - y)); - - if decision < 0.0 { - x += 1.0; - decision += 2.0 * ry * ry * x + ry * ry; - } else { - x += 1.0; - y -= 1.0; - decision += 2.0 * ry * ry * x - 2.0 * rx * rx * y + ry * ry; - } + if self.line_width <= 0 || rx <= 0.0 || ry <= 0.0 { + return; } - decision = - ry * ry * (x + 0.5) * (x + 0.5) + rx * rx * (y - 1.0) * (y - 1.0) - rx * rx * ry * ry; + // 外椭圆半径(膨胀) + let outer_rx = rx; + let outer_ry = ry; - while y > 0.0 { - // 同样地,根据对称性绘制四个方向上的点 - self.stroke_pixel_at(Point::new(center.x + x, center.y + y)); - self.stroke_pixel_at(Point::new(center.x - x, center.y + y)); - self.stroke_pixel_at(Point::new(center.x + x, center.y - y)); - self.stroke_pixel_at(Point::new(center.x - x, center.y - y)); + // 内椭圆半径(收缩)——不能为负 + let inner_rx = (rx - self.line_width as f32).max(0.0); + let inner_ry = (ry - self.line_width as f32).max(0.0); - if decision > 0.0 { - y -= 1.0; - decision += rx * rx - 2.0 * rx * rx * y; - } else { - x += 1.0; - y -= 1.0; - decision += 2.0 * ry * ry * x - 2.0 * rx * rx * y + rx * rx; + // 计算包围矩形(基于外椭圆) + let min_x = (center.x - outer_rx).ceil() as i32; + let max_x = (center.x + outer_rx).floor() as i32; + let min_y = (center.y - outer_ry).ceil() as i32; + let max_y = (center.y + outer_ry).floor() as i32; + + // 预计算外椭圆参数 + let outer_rx2 = outer_rx * outer_rx; + let outer_ry2 = outer_ry * outer_ry; + let outer_threshold = outer_rx2 * outer_ry2; + + // 预计算内椭圆参数(仅当存在内区域时) + let (inner_rx2, inner_ry2, inner_threshold) = if inner_rx > 0.0 && inner_ry > 0.0 { + let rx2 = inner_rx * inner_rx; + let ry2 = inner_ry * inner_ry; + (rx2, ry2, rx2 * ry2) + } else { + (0.0, 0.0, -1.0) // inner_threshold < 0 表示无内区域(实心圆) + }; + + for y in min_y..=max_y { + for x in min_x..=max_x { + let dx = x as f32 - center.x; + let dy = y as f32 - center.y; + + // 判断是否在外椭圆内 + let outer_val = dx * dx * outer_ry2 + dy * dy * outer_rx2; + if outer_val > outer_threshold { + continue; // 在外椭圆外 + } + + // 判断是否在内椭圆内(如果是,则跳过) + if inner_threshold >= 0.0 { + let inner_val = dx * dx * inner_ry2 + dy * dy * inner_rx2; + if inner_val <= inner_threshold { + continue; // 在内椭圆内 → 不绘制(空心) + } + } + + // 否则:在外椭圆内,且不在内椭圆内 → 属于描边区域 + self.stroke_pixel_at1(x, y); } } } @@ -1027,14 +1043,14 @@ impl MSCanvas { return; } - let rx = rx as f32; - let ry = ry as f32; + let rx = rx; + let ry = ry; // 计算包围矩形(整数边界) - let min_x = (center.x as f32 - rx).ceil() as i32; - let max_x = (center.x as f32 + rx).floor() as i32; - let min_y = (center.y as f32 - ry).ceil() as i32; - let max_y = (center.y as f32 + ry).floor() as i32; + let min_x = (center.x - rx).ceil() as i32; + let max_x = (center.x + rx).floor() as i32; + let min_y = (center.y - ry).ceil() as i32; + let max_y = (center.y + ry).floor() as i32; // 预计算常量:rx², ry², rx² * ry² let rx2 = rx * rx; @@ -1043,8 +1059,8 @@ impl MSCanvas { for y in min_y..=max_y { for x in min_x..=max_x { - let dx = x as f32 - center.x as f32; - let dy = y as f32 - center.y as f32; + let dx = x as f32 - center.x; + let dy = y as f32 - center.y; // 判断是否在椭圆内:dx²/ rx² + dy²/ ry² <= 1 // 等价于:dx² * ry² + dy² * rx² <= rx² * ry² @@ -1197,14 +1213,6 @@ fn normalize_radian(radian: f32) -> f32 { r } -#[inline] -/// 辅助函数:计算两点间距离的平方(避免开方) -fn distance_sq(a: Point, b: Point) -> f32 { - let dx = a.x - b.x; - let dy = a.y - b.y; - dx * dx + dy * dy -} - #[inline] /// 辅助函数:计算两点间距离的平方(避免开方) fn distance_sq1(a: (i32, i32), b: (i32, i32)) -> f32 { diff --git a/src/paint.rs b/src/paint.rs index 157ec97..5871c30 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -766,15 +766,12 @@ impl PaintApp { } pub fn update_with_brush(&mut self, message: Message) { - let mut brush_fn: fn(&mut MSCanvas, Point) = MSCanvas::brush_circle; - if let Some(kind) = self.config.brush_kind { - brush_fn = match kind { - BrushKind::Circle => MSCanvas::brush_circle, - BrushKind::Square => MSCanvas::brush_square, - BrushKind::Slash => MSCanvas::brush_slash, - BrushKind::Backslash => MSCanvas::brush_backslash, - } - } + let brush_fn = match self.get_brush_kind() { + BrushKind::Circle => MSCanvas::brush_circle, + BrushKind::Square => MSCanvas::brush_square, + BrushKind::Slash => MSCanvas::brush_slash, + BrushKind::Backslash => MSCanvas::brush_backslash, + }; match message { Message::MousePressed(pos) => { self.is_drawing = true; @@ -969,6 +966,24 @@ impl PaintApp { } } + fn close_polygon(&mut self) { + self.canvas.close_path(); + match self.get_shape_style() { + ShapeStyle::Stroke => { + self.canvas.stroke(); + } + ShapeStyle::Fill => { + self.canvas.fill(); + } + ShapeStyle::FillAndStroke => { + self.canvas.fill_color(self.background_color); + self.canvas.fill(); + self.canvas.fill_color(self.foreground_color); + self.canvas.stroke(); + } + } + } + pub fn update_with_polygon(&mut self, message: Message) { match message { Message::MousePressed(pos) => match self.control_state { @@ -1005,23 +1020,9 @@ impl PaintApp { } } Message::MouseDoubleClick(_pos) => { - self.canvas.close_path(); - match self.get_shape_style() { - ShapeStyle::Stroke => { - self.canvas.stroke(); - } - ShapeStyle::Fill => { - self.canvas.fill(); - } - ShapeStyle::FillAndStroke => { - self.canvas.fill_color(self.background_color); - self.canvas.fill(); - self.canvas.fill_color(self.foreground_color); - self.canvas.stroke(); - } - } self.is_drawing = false; self.control_state = ControlState::Zero; + self.close_polygon(); self.dirty = true; } _ => {} @@ -1131,21 +1132,7 @@ impl PaintApp { } if self.tool_selected == Tool::Polygon && self.control_state != ControlState::Zero { // 切换到其他工具,闭合路径 - self.canvas.close_path(); - match self.get_shape_style() { - ShapeStyle::Stroke => { - self.canvas.stroke(); - } - ShapeStyle::Fill => { - self.canvas.fill(); - } - ShapeStyle::FillAndStroke => { - self.canvas.fill_color(self.background_color); - self.canvas.fill(); - self.canvas.fill_color(self.foreground_color); - self.canvas.stroke(); - } - } + self.close_polygon(); self.dirty = true; }