From de76574654327b4cb244bcd225e0bbcbf8031258 Mon Sep 17 00:00:00 2001 From: yeqing Date: Wed, 4 Mar 2026 00:58:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E8=99=9A=E7=BA=BF=E6=A1=86=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mscanvas.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++--- src/paint.rs | 63 ++++++++++++++++++++- 2 files changed, 194 insertions(+), 11 deletions(-) diff --git a/src/mscanvas.rs b/src/mscanvas.rs index faed9e4..ae86611 100644 --- a/src/mscanvas.rs +++ b/src/mscanvas.rs @@ -145,16 +145,19 @@ impl MSCanvas { } } - pub fn line_width(&mut self, line_width: i32) { + pub fn line_width(&mut self, line_width: i32) -> &mut Self { self.line_width = line_width; + self } - pub fn fill_color(&mut self, color: MSColor) { + pub fn fill_color(&mut self, color: MSColor) -> &mut Self { self.fill_color = color; + self } - pub fn stroke_color(&mut self, color: MSColor) { + pub fn stroke_color(&mut self, color: MSColor) -> &mut Self { self.stroke_color = color; + self } pub fn size(&self) -> (i32, i32) { (self.width, self.height) @@ -221,6 +224,31 @@ impl MSCanvas { self.pixels[index + 3], ) } + + pub fn overlay_pixels(&mut self, canvas: &MSCanvas) -> Vec { + let mut pixels = canvas.get_pixels(); + + let width = canvas.width.min(self.width); + let height = canvas.height.min(self.height); + + for y in 0..height { + for x in 0..width { + let index = ((y * width + x) * 4) as usize; + + // 跳过透明像素 + if self.pixels[index + 3] == 0 { + continue; + } + + pixels[index] = self.pixels[index]; + pixels[index + 1] = self.pixels[index + 1]; + pixels[index + 2] = self.pixels[index + 2]; + pixels[index + 3] = self.pixels[index + 3]; + } + } + + pixels + } } #[allow(unused)] @@ -267,7 +295,26 @@ impl MSCanvas { self.pixels[index + 3] = self.stroke_color.a; // A } - fn fill_row(&mut self, xs: i32, xe: i32, y: i32) { + fn draw_column_with_color(&mut self, ys: i32, ye: i32, x: i32, color: MSColor) { + if x < 0 || x >= self.width { + return; + } + + let ys = ys.clamp(0, self.height - 1); + let ye = ye.clamp(0, self.height); + for y in ys..ye { + let index = ((y * self.width + x) * 4) as usize; + + // 写入 RGBA 数据 + // 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255 + self.pixels[index] = color.r; // R + self.pixels[index + 1] = color.g; // G + self.pixels[index + 2] = color.b; // B + self.pixels[index + 3] = color.a; // A + } + } + + fn draw_row_with_color(&mut self, xs: i32, xe: i32, y: i32, color: MSColor) { if y < 0 || y >= self.height { return; } @@ -279,20 +326,36 @@ impl MSCanvas { // 写入 RGBA 数据 // 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255 - self.pixels[index] = self.fill_color.r; // R - self.pixels[index + 1] = self.fill_color.g; // G - self.pixels[index + 2] = self.fill_color.b; // B - self.pixels[index + 3] = self.fill_color.a; // A + self.pixels[index] = color.r; // R + self.pixels[index + 1] = color.g; // G + self.pixels[index + 2] = color.b; // B + self.pixels[index + 3] = color.a; // A } } + fn fill_column(&mut self, ys: i32, ye: i32, x: i32) { + self.draw_column_with_color(ys, ye, x, self.fill_color); + } + + fn fill_row(&mut self, xs: i32, xe: i32, y: i32) { + self.draw_row_with_color(xs, xe, y, self.fill_color); + } + + fn stroke_column(&mut self, ys: i32, ye: i32, x: i32) { + self.draw_column_with_color(ys, ye, x, self.stroke_color); + } + + fn stroke_row(&mut self, xs: i32, xe: i32, y: i32) { + self.draw_row_with_color(xs, xe, y, self.stroke_color); + } + pub fn fill_pixels(&mut self, points: Vec) { for point in points { self.fill_pixel_at(point); } } - pub fn stroke_cross_color(&mut self, point: Point) { + pub fn stroke_cross(&mut self, point: Point) { let Point { x, y } = point; let r = 10; for dy in -r..=r { @@ -875,10 +938,71 @@ impl MSCanvas { self.stroke_rect(x as i32, y as i32, width as i32, height as i32); } + pub fn stroke_dashed_rect( + &mut self, + x: i32, + y: i32, + width: i32, + height: i32, + dash_length: i32, + gap_length: i32, + ) { + let mut is_dash = true; + + // 上下两边边框 + for dx in (x..x + width).step_by((dash_length + gap_length) as usize) { + if is_dash { + self.stroke_row(dx, dx + dash_length, y); + self.stroke_row(dx, dx + dash_length, y + height - 1); + } + is_dash = !is_dash; + } + + // 左右两边边框 + for dy in (y..y + height).step_by((dash_length + gap_length) as usize) { + if is_dash { + self.stroke_column(dy, dy + dash_length, x); + self.stroke_column(dy, dy + dash_length, x + width - 1); + } + is_dash = !is_dash; + } + } + + pub fn stroke_dashed_rect1(&mut self, p1: Point, p2: Point, dash_length: i32, gap_length: i32) { + let mut x = p1.x; + let mut y = p1.y; + let mut width = (p2.x - p1.x); + let mut height = (p2.y - p1.y); + if width < 0.0 && height < 0.0 { + x = p2.x; + y = p2.y; + width = (p1.x - p2.x); + height = (p1.y - p2.y); + } else if width < 0.0 { + x += width; + width = -width; + } else if height < 0.0 { + y += height; + height = -height; + } + self.stroke_dashed_rect( + x as i32, + y as i32, + width as i32, + height as i32, + dash_length, + gap_length, + ); + } + pub fn clear(&mut self) { self.pixels.fill(255); } + pub fn clear_zero(&mut self) { + self.pixels.fill(0); + } + pub fn clear_row(&mut self, xs: i32, xe: i32, y: i32) { if y < 0 || y >= self.height { return; diff --git a/src/paint.rs b/src/paint.rs index 5871c30..0b9ce8d 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -301,7 +301,10 @@ struct PaintApp { tool_states: [bool; Tool::Count as usize], tool_selected: Tool, + /// 画图层 canvas: MSCanvas, + /// 显示层 + view_canvas: MSCanvas, /// 是否正在绘制 is_drawing: bool, @@ -336,10 +339,18 @@ impl PaintApp { let (width, height) = canvas.size(); let pixels = canvas.get_pixels(); + let mut view_canvas = MSCanvas::new(width, height); + view_canvas.clear_zero(); + view_canvas + .fill_color(MSColor::WHITE) + .stroke_color(MSColor::BLACK) + .line_width(1); + let mut ins = Self { tool_states: [false; Tool::Count as usize], tool_selected: Tool::Count, canvas, + view_canvas, is_drawing: false, begin_point: Point::ORIGIN, end_point: Point::ORIGIN, @@ -568,6 +579,9 @@ impl PaintApp { pub fn update(&mut self, message: Message) -> Task { match self.tool_selected { + Tool::Select => { + self.update_with_select(message); + } Tool::Eraser => { self.update_with_eraser(message); } @@ -702,6 +716,33 @@ impl PaintApp { // region tool update + pub fn update_with_select(&mut self, message: Message) { + match message { + Message::MousePressed(pos) => { + self.is_drawing = true; + self.view_canvas.save_pixels(); + self.begin_point = pos; + } + Message::MouseReleased(pos) => { + self.is_drawing = false; + self.view_canvas.restore_pixels(); + self.view_canvas + .stroke_dashed_rect1(self.begin_point, pos, 1, 1); + self.begin_point = pos; + self.dirty = true; + } + Message::MouseMoved(pos) => { + if self.is_drawing { + self.view_canvas.restore_pixels(); + self.view_canvas + .stroke_dashed_rect1(self.begin_point, pos, 3, 1); + self.dirty = true; + } + } + _ => {} + } + } + pub fn update_with_eraser(&mut self, message: Message) { match message { Message::MousePressed(pos) => { @@ -727,6 +768,20 @@ impl PaintApp { ); self.dirty = true; } + self.view_canvas.clear_zero(); + self.view_canvas.clear_rect( + (pos.x as i32) - self.config.eraser_width / 2, + (pos.y as i32) - self.config.eraser_width / 2, + self.config.eraser_width, + self.config.eraser_width, + ); + self.view_canvas.stroke_rect( + (pos.x as i32) - self.config.eraser_width / 2, + (pos.y as i32) - self.config.eraser_width / 2, + self.config.eraser_width, + self.config.eraser_width, + ); + self.dirty = true; } _ => {} } @@ -1145,15 +1200,19 @@ impl PaintApp { self.is_drawing = false; self.control_state = ControlState::Zero; + self.view_canvas.clear_zero(); + self.dirty = true; } /// 将原始字节转换为 Iced 的图像句柄 fn update_image_handle(&mut self) { // 克隆数据以避免所有权问题,或者使用 Arc 如果数据量大 // 这里为了简单直接 clone,对于 800x600 (约 2MB) 来说很快 - let data = self.canvas.get_pixels(); + // let data = self.canvas.get_pixels(); + let data = self.view_canvas.overlay_pixels(&self.canvas); + let (width, height) = self.canvas.size(); - self.image_handle = image::Handle::from_rgba(WIDTH, HEIGHT, data); + self.image_handle = image::Handle::from_rgba(width as u32, height as u32, data); } }