diff --git a/src/mscanvas.rs b/src/mscanvas.rs index ae86611..3859917 100644 --- a/src/mscanvas.rs +++ b/src/mscanvas.rs @@ -113,6 +113,40 @@ impl Path2D { } } +pub struct RectSelection { + /// 左上角坐标 + x: i32, + y: i32, + w: i32, + h: i32, + pixels: Vec, +} + +impl RectSelection { + pub fn new(x: i32, y: i32, w: i32, h: i32, pixels: Vec) -> RectSelection { + Self { x, y, w, h, pixels } + } + + pub fn contains(&self, p: Point) -> bool { + let x = p.x as i32; + let y = p.y as i32; + !(x < self.x || y < self.y || x >= self.x + self.w || y >= self.y + self.h) + } + + pub fn move_offset(&mut self, offset_x: i32, offset_y: i32) { + self.x += offset_x; + self.y += offset_y; + } + + pub fn size(&self) -> (i32, i32) { + (self.w, self.h) + } + + pub fn position(&self) -> (i32, i32) { + (self.x, self.y) + } +} + pub struct MSCanvas { width: i32, height: i32, @@ -209,20 +243,45 @@ impl MSCanvas { dst } - pub fn pixel_at(&self, x: i32, y: i32) -> MSColor { + pub fn pixel_at(&self, x: i32, y: i32) -> Option { // 边界检查 if x < 0 || x >= self.width || y < 0 || y >= self.height { - return MSColor::ZERO; + return None; } let index = ((y * self.width + x) * 4) as usize; - MSColor::new( + Some(MSColor::new( self.pixels[index], self.pixels[index + 1], self.pixels[index + 2], self.pixels[index + 3], - ) + )) + } + + pub fn pixel_is(&self, x: i32, y: i32, color: MSColor) -> bool { + match self.pixel_at(x, y) { + Some(c) => c == color, + None => false, + } + } + + pub fn select_rect(&self, x: i32, y: i32, w: i32, h: i32) -> RectSelection { + let mut result = vec![255; (w * h * 4) as usize]; + + let mut index = 0; + for yi in y..(y + h) { + for xi in x..(x + w) { + let color = self.pixel_at(xi, yi).unwrap_or(MSColor::WHITE); + result[index] = color.r; + result[index + 1] = color.g; + result[index + 2] = color.b; + result[index + 3] = color.a; + index += 4; + } + } + + RectSelection::new(x, y, w, h, result) } pub fn overlay_pixels(&mut self, canvas: &MSCanvas) -> Vec { @@ -540,7 +599,7 @@ impl MSCanvas { pub fn fill_slow(&mut self, begin: Point) { let start_x = begin.x as i32; let start_y = begin.y as i32; - let target_color = self.pixel_at(start_x, start_y); + let target_color = self.pixel_at(start_x, start_y).unwrap_or(MSColor::ZERO); if target_color == self.fill_color { return; } @@ -552,7 +611,7 @@ impl MSCanvas { if x < 0 || x >= self.width || y < 0 || y >= self.height { continue; } - if self.pixel_at(x, y) == target_color { + if self.pixel_is(x, y, target_color) { self.fill_pixel_at1(x, y); let p1 = (x - 1, y); @@ -578,7 +637,7 @@ impl MSCanvas { return; } - let target_color = self.pixel_at(start_x, start_y); + let target_color = self.pixel_at(start_x, start_y).unwrap_or(MSColor::ZERO); if target_color == self.fill_color { return; } @@ -588,11 +647,11 @@ impl MSCanvas { while let Some((y, mut lx, mut rx)) = stack.pop() { // 向左扩展 lx - while lx - 1 >= 0 && self.pixel_at(lx - 1, y) == target_color { + while lx - 1 >= 0 && self.pixel_is(lx - 1, y, target_color) { lx -= 1; } // 向右扩展 rx - while rx + 1 < width && self.pixel_at(rx + 1, y) == target_color { + while rx + 1 < width && self.pixel_is(rx + 1, y, target_color) { rx += 1; } @@ -605,10 +664,10 @@ impl MSCanvas { if y - 1 >= 0 { let mut x = lx; while x <= rx { - if self.pixel_at(x, y - 1) == target_color { + if self.pixel_is(x, y - 1, target_color) { let span_start = x; // 跳过连续的目标色块 - while x <= rx && self.pixel_at(x, y - 1) == target_color { + while x <= rx && self.pixel_is(x, y - 1, target_color) { x += 1; } // 将这个 span 入栈(用于后续处理上一行的上一行) @@ -623,9 +682,9 @@ impl MSCanvas { if y + 1 < height { let mut x = lx; while x <= rx { - if self.pixel_at(x, y + 1) == target_color { + if self.pixel_is(x, y + 1, target_color) { let span_start = x; - while x <= rx && self.pixel_at(x, y + 1) == target_color { + while x <= rx && self.pixel_is(x, y + 1, target_color) { x += 1; } stack.push((y + 1, span_start, x - 1)); @@ -1314,6 +1373,23 @@ impl MSCanvas { let points = self.path2d.points.clone(); self.fill_polygon(&points[..]); } + + pub fn draw_rect_selection(&mut self, x: i32, y: i32, rect: &RectSelection) { + let w = rect.w.min(self.width - x); + let h = rect.h.min(self.height - y); + + for dy in 0..h { + for dx in 0..w { + let src_idx = ((dy * rect.w + dx) * 4) as usize; + let dst_idx = (((y + dy) * self.width + x + dx) * 4) as usize; + + self.pixels[dst_idx] = rect.pixels[src_idx]; + self.pixels[dst_idx + 1] = rect.pixels[src_idx + 1]; + self.pixels[dst_idx + 2] = rect.pixels[src_idx + 2]; + self.pixels[dst_idx + 3] = rect.pixels[src_idx + 3]; + } + } + } } fn point_muln(point: Point, t: f32) -> Point { diff --git a/src/paint.rs b/src/paint.rs index 0b9ce8d..05af759 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -13,7 +13,7 @@ use iced::{color, event, mouse}; use crate::color_box::{ColorSelectionBox, CurrentColorBox}; use crate::image_button::image_button; use crate::mouse_area::mouse_area; -use crate::mscanvas::{MSCanvas, MSColor}; +use crate::mscanvas::{MSCanvas, MSColor, RectSelection}; const WIDTH: u32 = 800; const HEIGHT: u32 = 600; @@ -80,6 +80,7 @@ impl std::fmt::Display for ShapeStyle { } } +#[allow(unused)] #[derive(Debug, Clone, Copy)] enum Message { MousePressed(Point), @@ -329,6 +330,8 @@ struct PaintApp { foreground_color: MSColor, background_color: MSColor, + + rect_selection: Option, } impl PaintApp { @@ -361,11 +364,18 @@ impl PaintApp { config: Config::default(), foreground_color: MSColor::BLACK, background_color: MSColor::WHITE, + rect_selection: None, }; - ins.canvas.fill_color(ins.foreground_color); - ins.canvas.stroke_color(ins.foreground_color); - ins.canvas.line_width(ins.config.line_width); + ins.canvas + .fill_color(ins.foreground_color) + .stroke_color(ins.foreground_color) + .line_width(ins.config.line_width); + + ins.view_canvas + .fill_color(MSColor::BLACK) + .stroke_color(MSColor::BLACK) + .line_width(1); ins } @@ -716,27 +726,154 @@ impl PaintApp { // region tool update + fn draw_rect_selection(&mut self, x: i32, y: i32) { + if self.rect_selection.is_none() { + return; + } + let rect_selection = self.rect_selection.as_ref().unwrap(); + let (width, height) = rect_selection.size(); + self.view_canvas.draw_rect_selection(x, y, rect_selection); + self.view_canvas + .stroke_dashed_rect(x, y, width, height, 1, 1); + // 画矩形四条边上的三个点 + { + let sw = 3; + let half_sw = sw / 2; + self.view_canvas.fill_rect(x - half_sw, y - half_sw, sw, sw); + self.view_canvas + .fill_rect((x + width / 2) - half_sw, y - half_sw, sw, sw); + self.view_canvas + .fill_rect((x + width - 1) - half_sw, y - half_sw, sw, sw); + self.view_canvas + .fill_rect(x - half_sw, (y + height / 2) - half_sw, sw, sw); + self.view_canvas.fill_rect( + (x + width - 1) - half_sw, + (y + height / 2) - half_sw, + sw, + sw, + ); + self.view_canvas + .fill_rect(x - half_sw, (y + height - 1) - half_sw, sw, sw); + self.view_canvas.fill_rect( + (x + width / 2) - half_sw, + (y + height - 1) - half_sw, + sw, + sw, + ); + self.view_canvas.fill_rect( + (x + width - 1) - half_sw, + (y + height - 1) - half_sw, + sw, + sw, + ); + } + } + + fn release_rect_selection(&mut self) { + if self.rect_selection.is_none() { + return; + } + let rect_selection = self.rect_selection.as_ref().unwrap(); + let (x, y) = rect_selection.position(); + self.view_canvas.restore_pixels(); + self.canvas.restore_pixels(); + self.canvas.draw_rect_selection(x, y, rect_selection); + self.dirty = true; + + self.rect_selection = None; + } + 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::MousePressed(pos) => match self.control_state { + ControlState::Zero => { + self.is_drawing = true; + self.view_canvas.save_pixels(); + self.begin_point = pos; + self.control_state = ControlState::One; + } + ControlState::One => { + if let Some(rect_selection) = self.rect_selection.as_ref() { + if rect_selection.contains(pos) { + self.is_drawing = true; + self.begin_point = pos; + self.control_state = ControlState::Two; + } else { + self.release_rect_selection(); + self.control_state = ControlState::Zero; + } + } + } + _ => {} + }, 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; + match self.control_state { + ControlState::One => { + self.view_canvas.restore_pixels(); + let p1 = self.begin_point; + let p2 = pos; + 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.rect_selection = Some(self.canvas.select_rect( + x as i32, + y as i32, + width as i32, + height as i32, + )); + self.canvas + .clear_rect(x as i32, y as i32, width as i32, height as i32); + self.canvas.save_pixels(); + self.draw_rect_selection(x as i32, y as i32); + self.begin_point = pos; + self.dirty = true; + } + ControlState::Two => { + self.control_state = ControlState::One; + if let Some(rect_selection) = self.rect_selection.as_mut() { + let offset_x = pos.x - self.begin_point.x; + let offset_y = pos.y - self.begin_point.y; + rect_selection.move_offset(offset_x as i32, offset_y as i32); + } + } + _ => {} + } } 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; + match self.control_state { + ControlState::One => { + self.view_canvas.restore_pixels(); + self.view_canvas + .stroke_dashed_rect1(self.begin_point, pos, 3, 1); + self.dirty = true; + } + ControlState::Two => { + if let Some(rect_selection) = self.rect_selection.as_ref() { + let offset_x = pos.x - self.begin_point.x; + let offset_y = pos.y - self.begin_point.y; + let (x, y) = rect_selection.position(); + self.view_canvas.restore_pixels(); + self.draw_rect_selection(x + offset_x as i32, y + offset_y as i32); + self.dirty = true; + } + } + _ => {} + } } } _ => {} @@ -1037,6 +1174,7 @@ impl PaintApp { self.canvas.stroke(); } } + self.dirty = true; } pub fn update_with_polygon(&mut self, message: Message) { @@ -1078,7 +1216,6 @@ impl PaintApp { self.is_drawing = false; self.control_state = ControlState::Zero; self.close_polygon(); - self.dirty = true; } _ => {} } @@ -1188,7 +1325,19 @@ impl PaintApp { if self.tool_selected == Tool::Polygon && self.control_state != ControlState::Zero { // 切换到其他工具,闭合路径 self.close_polygon(); - self.dirty = true; + } + if self.tool_selected == Tool::Select && self.control_state != ControlState::Zero { + // 切换到其他工具,释放选择框 + self.release_rect_selection(); + } + + self.is_drawing = false; + self.control_state = ControlState::Zero; + self.view_canvas.clear_zero(); + self.dirty = true; + + if self.tool_selected == idx.into() { + return; } let old_value = self.tool_states[idx]; @@ -1197,11 +1346,6 @@ impl PaintApp { } self.tool_states[idx] = !old_value; self.tool_selected = idx.into(); - - self.is_drawing = false; - self.control_state = ControlState::Zero; - self.view_canvas.clear_zero(); - self.dirty = true; } /// 将原始字节转换为 Iced 的图像句柄