feat: 实现矩形选择功能

This commit is contained in:
2026-03-04 17:36:27 +08:00
parent de76574654
commit 51055b8ea8
2 changed files with 258 additions and 38 deletions

View File

@@ -113,6 +113,40 @@ impl Path2D {
}
}
pub struct RectSelection {
/// 左上角坐标
x: i32,
y: i32,
w: i32,
h: i32,
pixels: Vec<u8>,
}
impl RectSelection {
pub fn new(x: i32, y: i32, w: i32, h: i32, pixels: Vec<u8>) -> 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<MSColor> {
// 边界检查
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<u8> {
@@ -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 {

View File

@@ -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<RectSelection>,
}
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,28 +726,155 @@ 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) => {
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;
match self.control_state {
ControlState::One => {
self.view_canvas.restore_pixels();
self.view_canvas
.stroke_dashed_rect1(self.begin_point, pos, 1, 1);
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 {
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();
}
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 的图像句柄