refactor: Extract canvas feature
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
mod image_button;
|
||||
mod mouse_area;
|
||||
mod mscanvas;
|
||||
mod paint;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
|
||||
408
src/mscanvas.rs
Normal file
408
src/mscanvas.rs
Normal file
@@ -0,0 +1,408 @@
|
||||
use iced::Point;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct MSColor {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
a: u8,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl MSColor {
|
||||
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
|
||||
pub const ZERO: MSColor = MSColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0,
|
||||
};
|
||||
|
||||
pub const BLACK: MSColor = MSColor {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
};
|
||||
|
||||
pub const WHITE: MSColor = MSColor {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
a: 255,
|
||||
};
|
||||
}
|
||||
|
||||
pub struct MSCanvas {
|
||||
width: i32,
|
||||
height: i32,
|
||||
|
||||
// 原始像素数据:RGBA 格式
|
||||
// 长度 = width * height * 4
|
||||
pixels: Vec<u8>,
|
||||
pixels_bak: Vec<u8>,
|
||||
|
||||
// 当前笔画颜色
|
||||
color: MSColor,
|
||||
|
||||
// brush 大小
|
||||
brush_radius: f32,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl MSCanvas {
|
||||
pub fn new(width: i32, height: i32) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
pixels: vec![255; (width * height * 4) as usize],
|
||||
pixels_bak: Vec::new(),
|
||||
color: MSColor::BLACK,
|
||||
brush_radius: 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, color: MSColor) {
|
||||
self.color = color;
|
||||
}
|
||||
|
||||
pub fn set_brush_radius(&mut self, brush_radius: f32) {
|
||||
self.brush_radius = brush_radius;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl MSCanvas {
|
||||
pub fn get_pixels(&self) -> Vec<u8> {
|
||||
self.pixels.clone()
|
||||
}
|
||||
|
||||
pub fn pixel_at(&self, x: i32, y: i32) -> MSColor {
|
||||
// 边界检查
|
||||
if x < 0 || x >= self.width || y < 0 || y >= self.height as i32 {
|
||||
return MSColor::ZERO;
|
||||
}
|
||||
|
||||
let index = ((y * self.width + x) * 4) as usize;
|
||||
|
||||
MSColor::new(
|
||||
self.pixels[index],
|
||||
self.pixels[index + 1],
|
||||
self.pixels[index + 2],
|
||||
self.pixels[index + 3],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn draw_pixel_at(&mut self, point: Point) {
|
||||
let Point { x, y } = point;
|
||||
let x = x as i32;
|
||||
let y = y as i32;
|
||||
// 边界检查
|
||||
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||
return;
|
||||
}
|
||||
// 计算索引:(y * width + x) * 4
|
||||
let index = ((y * self.width + x) * 4) as usize;
|
||||
|
||||
// 写入 RGBA 数据
|
||||
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||
self.pixels[index] = self.color.r; // R
|
||||
self.pixels[index + 1] = self.color.g; // G
|
||||
self.pixels[index + 2] = self.color.b; // B
|
||||
self.pixels[index + 3] = self.color.a; // A
|
||||
}
|
||||
|
||||
fn draw_pixel_at1(&mut self, x: i32, y: i32) {
|
||||
// 边界检查
|
||||
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||
return;
|
||||
}
|
||||
// 计算索引:(y * width + x) * 4
|
||||
let index = ((y * self.width + x) * 4) as usize;
|
||||
|
||||
// 写入 RGBA 数据
|
||||
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||
self.pixels[index] = self.color.r; // R
|
||||
self.pixels[index + 1] = self.color.g; // G
|
||||
self.pixels[index + 2] = self.color.b; // B
|
||||
self.pixels[index + 3] = self.color.a; // A
|
||||
}
|
||||
|
||||
pub fn draw_pixel_row(&mut self, xs: i32, xe: i32, y: i32) {
|
||||
if y < 0 || y >= self.height {
|
||||
return;
|
||||
}
|
||||
|
||||
let xs = xs.clamp(0, self.width - 1);
|
||||
let xe = xe.clamp(0, self.width as i32);
|
||||
let y = y;
|
||||
for x in xs..xe {
|
||||
let index = ((y * self.width + x) * 4) as usize;
|
||||
|
||||
// 写入 RGBA 数据
|
||||
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||
self.pixels[index] = self.color.r; // R
|
||||
self.pixels[index + 1] = self.color.g; // G
|
||||
self.pixels[index + 2] = self.color.b; // B
|
||||
self.pixels[index + 3] = self.color.a; // A
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_pixels(&mut self, points: Vec<Point>) {
|
||||
for point in points {
|
||||
self.draw_pixel_at(point);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_brush_at(&mut self, center: Point) {
|
||||
if self.brush_radius < 1.0 {
|
||||
self.draw_pixel_at(center);
|
||||
return;
|
||||
}
|
||||
let square = self.brush_radius * self.brush_radius;
|
||||
let r = self.brush_radius.floor() as i32;
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
if (dx * dx + dy * dy) as f32 <= square {
|
||||
self.draw_pixel_at(Point::new(center.x + dx as f32, center.y + dy as f32));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bresenham_line(&mut self, begin: Point, end: Point) -> Vec<Point> {
|
||||
let x1 = begin.x;
|
||||
let y1 = begin.y;
|
||||
let x2 = end.x;
|
||||
let y2 = end.y;
|
||||
|
||||
let dx = (x2 - x1);
|
||||
let dy = (y2 - y1);
|
||||
let dx1 = dx.abs();
|
||||
let dy1 = dy.abs();
|
||||
let mut px = 2.0 * dy1 - dx1;
|
||||
let mut py = 2.0 * dx1 - dy1;
|
||||
|
||||
let mut x;
|
||||
let mut y;
|
||||
let xe;
|
||||
let ye;
|
||||
|
||||
let mut points = Vec::new();
|
||||
if dy1 <= dx1 {
|
||||
if dx >= 0.0 {
|
||||
x = x1;
|
||||
y = y1;
|
||||
xe = x2;
|
||||
ye = y2;
|
||||
} else {
|
||||
x = x2;
|
||||
y = y2;
|
||||
xe = x1;
|
||||
ye = y1;
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
while x < xe {
|
||||
x += 1.0;
|
||||
if px < 0.0 {
|
||||
px = px + 2.0 * dy1;
|
||||
} else {
|
||||
if (dx < 0.0 && dy < 0.0) || (dx > 0.0 && dy > 0.0) {
|
||||
y = y + 1.0;
|
||||
} else {
|
||||
y = y - 1.0;
|
||||
}
|
||||
px = px + 2.0 * (dy1 - dx1);
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
}
|
||||
} else {
|
||||
if dy >= 0.0 {
|
||||
x = x1;
|
||||
y = y1;
|
||||
ye = y2;
|
||||
xe = x2;
|
||||
} else {
|
||||
x = x2;
|
||||
y = y2;
|
||||
ye = y1;
|
||||
xe = x1;
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
while y < ye {
|
||||
y = y + 1.0;
|
||||
if py <= 0.0 {
|
||||
py = py + 2.0 * dx1;
|
||||
} else {
|
||||
if (dx < 0.0 && dy < 0.0) || (dx > 0.0 && dy > 0.0) {
|
||||
x = x + 1.0;
|
||||
} else {
|
||||
x = x - 1.0;
|
||||
}
|
||||
py = py + 2.0 * (dx1 - dy1);
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
}
|
||||
}
|
||||
points.push(Point::new(xe, ye));
|
||||
|
||||
points
|
||||
}
|
||||
|
||||
pub fn draw_line(&mut self, begin: Point, end: Point) {
|
||||
let points = self.bresenham_line(begin, end);
|
||||
for point in points {
|
||||
self.draw_pixel_at(point);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_line_thick(&mut self, begin: Point, end: Point) {
|
||||
let points = self.bresenham_line(begin, end);
|
||||
for point in points {
|
||||
self.draw_brush_at(point);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_pixels(&mut self) {
|
||||
self.pixels_bak = self.pixels.clone();
|
||||
}
|
||||
|
||||
pub fn restore_pixels(&mut self) {
|
||||
self.pixels = self.pixels_bak.clone();
|
||||
}
|
||||
|
||||
pub fn fill_slow(&mut self, begin: Point) -> (i32, i32) {
|
||||
let start_x = begin.x as i32;
|
||||
let start_y = begin.y as i32;
|
||||
let target_color = self.pixel_at(start_x, start_y);
|
||||
if target_color == self.color {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let mut scan_points = vec![(start_x, start_y)];
|
||||
let width = self.width;
|
||||
let height = self.height;
|
||||
let mut iter_count = 0;
|
||||
let mut fill_count = 0;
|
||||
while let Some((x, y)) = scan_points.pop() {
|
||||
iter_count += 1;
|
||||
|
||||
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||
continue;
|
||||
}
|
||||
if self.pixel_at(x, y) == target_color {
|
||||
self.draw_pixel_at1(x, y);
|
||||
fill_count += 1;
|
||||
|
||||
let p1 = (x - 1, y);
|
||||
let p2 = (x + 1, y);
|
||||
let p3 = (x, y - 1);
|
||||
let p4 = (x, y + 1);
|
||||
scan_points.push(p1);
|
||||
scan_points.push(p2);
|
||||
scan_points.push(p3);
|
||||
scan_points.push(p4);
|
||||
}
|
||||
}
|
||||
(iter_count, fill_count)
|
||||
}
|
||||
|
||||
pub fn fill_scanline(&mut self, begin: Point) -> (i32, i32) {
|
||||
let start_x = begin.x as i32;
|
||||
let start_y = begin.y as i32;
|
||||
let width = self.width;
|
||||
let height = self.height;
|
||||
|
||||
// 边界检查
|
||||
if start_x < 0 || start_x >= width || start_y < 0 || start_y >= height {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let target_color = self.pixel_at(start_x, start_y);
|
||||
if target_color == (self.color) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// 栈中存储 (y, x1, x2):表示第 y 行从 x1 到 x2(含)需要向上/下扫描
|
||||
let mut stack = vec![(start_y, start_x, start_x)];
|
||||
let mut iter_count = 0;
|
||||
let mut fill_count = 0;
|
||||
|
||||
while let Some((y, mut lx, mut rx)) = stack.pop() {
|
||||
iter_count += 1;
|
||||
|
||||
// 向左扩展 lx
|
||||
while lx - 1 >= 0 && self.pixel_at(lx - 1, y) == target_color {
|
||||
lx -= 1;
|
||||
}
|
||||
// 向右扩展 rx
|
||||
while rx + 1 < width && self.pixel_at(rx + 1, y) == target_color {
|
||||
rx += 1;
|
||||
}
|
||||
|
||||
// 填充当前行 [lx, rx]
|
||||
for x in lx..=rx {
|
||||
self.draw_pixel_at1(x, y);
|
||||
fill_count += 1;
|
||||
}
|
||||
|
||||
// 检查上一行 (y - 1)
|
||||
if y - 1 >= 0 {
|
||||
let mut x = lx;
|
||||
while x <= rx {
|
||||
if self.pixel_at(x, y - 1) == target_color {
|
||||
let span_start = x;
|
||||
// 跳过连续的目标色块
|
||||
while x <= rx && self.pixel_at(x, y - 1) == target_color {
|
||||
x += 1;
|
||||
}
|
||||
// 将这个 span 入栈(用于后续处理上一行的上一行)
|
||||
stack.push((y - 1, span_start, x - 1));
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查下一行 (y + 1)
|
||||
if y + 1 < height {
|
||||
let mut x = lx;
|
||||
while x <= rx {
|
||||
if self.pixel_at(x, y + 1) == target_color {
|
||||
let span_start = x;
|
||||
while x <= rx && self.pixel_at(x, y + 1) == target_color {
|
||||
x += 1;
|
||||
}
|
||||
stack.push((y + 1, span_start, x - 1));
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(iter_count, fill_count)
|
||||
}
|
||||
|
||||
pub fn stroke_rect(&mut self, x: f32, y: f32, width: f32, height: f32) {
|
||||
self.draw_line_thick(Point::new(x, y), Point::new(x, y + height));
|
||||
self.draw_line_thick(Point::new(x, y + height), Point::new(x + width, y + height));
|
||||
self.draw_line_thick(Point::new(x + width, y + height), Point::new(x + width, y));
|
||||
self.draw_line_thick(Point::new(x, y), Point::new(x + width, y));
|
||||
}
|
||||
|
||||
pub fn stroke_rect1(&mut self, left_top: Point, right_bottom: Point) {
|
||||
let x = left_top.x;
|
||||
let y = left_top.y;
|
||||
let width = (right_bottom.x - left_top.x);
|
||||
let height = (right_bottom.y - left_top.y);
|
||||
self.stroke_rect(x, y, width, height);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.pixels.fill(255);
|
||||
}
|
||||
}
|
||||
513
src/paint.rs
513
src/paint.rs
@@ -3,10 +3,12 @@ use crate::mouse_area::mouse_area;
|
||||
use iced::Theme;
|
||||
use iced::padding;
|
||||
use iced::widget::container;
|
||||
use iced::widget::{Column, button, column, image, row};
|
||||
use iced::widget::{Column, button, column, image, row, text};
|
||||
use iced::{Border, Color, Element, Length, Point, Renderer, Task};
|
||||
use iced_core::color;
|
||||
|
||||
use crate::mscanvas::MSCanvas;
|
||||
|
||||
const WIDTH: u32 = 800;
|
||||
const HEIGHT: u32 = 600;
|
||||
|
||||
@@ -20,6 +22,9 @@ pub enum Message {
|
||||
RefreshImage,
|
||||
|
||||
ClickTool(Tool),
|
||||
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -92,43 +97,11 @@ impl From<Tool> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct ColorU8 {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
a: u8,
|
||||
}
|
||||
|
||||
impl ColorU8 {
|
||||
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
|
||||
fn is_same(&self, color: Color) -> bool {
|
||||
self.r == (color.r * 255.0) as u8
|
||||
&& self.g == (color.g * 255.0) as u8
|
||||
&& self.b == (color.b * 255.0) as u8
|
||||
&& self.a == (color.a * 255.0) as u8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn point_to_i32(point: Point) -> Point<i32> {
|
||||
Point::new(point.x as i32, point.y as i32)
|
||||
}
|
||||
|
||||
struct PaintApp {
|
||||
tool_states: [bool; Tool::Count as usize],
|
||||
tool_selected: Tool,
|
||||
|
||||
// 原始像素数据:RGBA 格式
|
||||
// 长度 = WIDTH * HEIGHT * 4
|
||||
pixels: Vec<u8>,
|
||||
pixels_bak: Vec<u8>,
|
||||
|
||||
// 当前画笔颜色
|
||||
color: Color,
|
||||
canvas: MSCanvas,
|
||||
|
||||
// 是否正在绘制
|
||||
is_drawing: bool,
|
||||
@@ -139,32 +112,28 @@ struct PaintApp {
|
||||
// 每次像素变化后需要重新生成
|
||||
image_handle: image::Handle,
|
||||
|
||||
// brush 大小
|
||||
brush_radius: i32,
|
||||
|
||||
// 标记像素是否被修改,用于优化图像句柄的生成
|
||||
dirty: bool,
|
||||
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl PaintApp {
|
||||
// region iced application (new view update)
|
||||
|
||||
pub fn new() -> Self {
|
||||
// 初始化全白背景 (R=255, G=255, B=255, A=255)
|
||||
let pixels = vec![255u8; (WIDTH * HEIGHT * 4) as usize];
|
||||
let data = pixels.clone();
|
||||
|
||||
let canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
|
||||
let pixels = canvas.get_pixels();
|
||||
Self {
|
||||
tool_states: [false; Tool::Count as usize],
|
||||
tool_selected: Tool::Count,
|
||||
pixels,
|
||||
pixels_bak: Vec::new(),
|
||||
color: Color::BLACK,
|
||||
canvas,
|
||||
is_drawing: false,
|
||||
begin_point: Point::ORIGIN,
|
||||
image_handle: image::Handle::from_rgba(WIDTH, HEIGHT, data),
|
||||
brush_radius: 1,
|
||||
image_handle: image::Handle::from_rgba(WIDTH, HEIGHT, pixels),
|
||||
dirty: false,
|
||||
|
||||
value: 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +189,10 @@ impl PaintApp {
|
||||
});
|
||||
let tool_config_area = container(tool_config_area).padding(padding::top(5).left(5));
|
||||
columns.push(tool_config_area.into());
|
||||
let grid = Column::from_vec(columns).height(Length::Fill);
|
||||
let grid = Column::from_vec(columns);
|
||||
|
||||
let tool_area = container(grid)
|
||||
.padding(padding::top(5).left(5).right(5).bottom(100))
|
||||
.padding(padding::top(5).left(5).right(5).bottom(10))
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
@@ -238,10 +207,19 @@ impl PaintApp {
|
||||
}
|
||||
});
|
||||
|
||||
// We use a column: a simple vertical layout
|
||||
let debug_area = column![
|
||||
text("brush"),
|
||||
row![
|
||||
button("+").on_press(Message::Increment),
|
||||
text(self.value),
|
||||
button("-").on_press(Message::Decrement),
|
||||
]
|
||||
];
|
||||
|
||||
column![
|
||||
button("CLEAR").on_press(Message::Clear),
|
||||
row![tool_area, canvas_area],
|
||||
debug_area,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -265,7 +243,7 @@ impl PaintApp {
|
||||
match message {
|
||||
Message::Clear => {
|
||||
// 重置为白色
|
||||
self.pixels.fill(255);
|
||||
self.canvas.clear();
|
||||
self.dirty = true;
|
||||
}
|
||||
Message::RefreshImage => {
|
||||
@@ -277,6 +255,17 @@ impl PaintApp {
|
||||
Message::ClickTool(tool) => {
|
||||
self.update_tool_states(tool);
|
||||
}
|
||||
Message::Increment => {
|
||||
self.value = self.value + 1;
|
||||
self.canvas.set_brush_radius(self.value as f32 / 2.0);
|
||||
}
|
||||
Message::Decrement => {
|
||||
self.value = self.value - 1;
|
||||
if self.value < 1 {
|
||||
self.value = 1;
|
||||
}
|
||||
self.canvas.set_brush_radius(self.value as f32 / 2.0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -298,7 +287,8 @@ impl PaintApp {
|
||||
pub fn update_with_fill_with_color(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::MousePressed(pos) => {
|
||||
self.fill_scanline(pos);
|
||||
self.canvas.fill_scanline(pos);
|
||||
self.dirty = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -308,7 +298,7 @@ impl PaintApp {
|
||||
match message {
|
||||
Message::MousePressed(pos) => {
|
||||
self.is_drawing = true;
|
||||
self.draw_pixel_atp(pos);
|
||||
self.canvas.draw_pixel_at(pos);
|
||||
self.begin_point = pos;
|
||||
}
|
||||
Message::MouseReleased(pos) => {
|
||||
@@ -317,8 +307,9 @@ impl PaintApp {
|
||||
}
|
||||
Message::MouseMoved(pos) => {
|
||||
if self.is_drawing {
|
||||
self.draw_line(self.begin_point, pos);
|
||||
self.canvas.draw_line(self.begin_point, pos);
|
||||
self.begin_point = pos;
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -329,8 +320,10 @@ impl PaintApp {
|
||||
match message {
|
||||
Message::MousePressed(pos) => {
|
||||
self.is_drawing = true;
|
||||
self.save_pixels();
|
||||
self.canvas.draw_brush_at(pos);
|
||||
self.canvas.save_pixels();
|
||||
self.begin_point = pos;
|
||||
self.dirty = true;
|
||||
}
|
||||
Message::MouseReleased(pos) => {
|
||||
self.is_drawing = false;
|
||||
@@ -338,8 +331,9 @@ impl PaintApp {
|
||||
}
|
||||
Message::MouseMoved(pos) => {
|
||||
if self.is_drawing {
|
||||
self.restore_pixels();
|
||||
self.draw_line(self.begin_point, pos);
|
||||
self.canvas.restore_pixels();
|
||||
self.canvas.draw_line_thick(self.begin_point, pos);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -350,7 +344,7 @@ impl PaintApp {
|
||||
match message {
|
||||
Message::MousePressed(pos) => {
|
||||
self.is_drawing = true;
|
||||
self.save_pixels();
|
||||
self.canvas.save_pixels();
|
||||
self.begin_point = pos;
|
||||
}
|
||||
Message::MouseReleased(pos) => {
|
||||
@@ -359,8 +353,9 @@ impl PaintApp {
|
||||
}
|
||||
Message::MouseMoved(pos) => {
|
||||
if self.is_drawing {
|
||||
self.restore_pixels();
|
||||
self.stroke_rect1(self.begin_point, pos, self.brush_radius * 2);
|
||||
self.canvas.restore_pixels();
|
||||
self.canvas.stroke_rect1(self.begin_point, pos);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -386,410 +381,12 @@ impl PaintApp {
|
||||
fn update_image_handle(&mut self) {
|
||||
// 克隆数据以避免所有权问题,或者使用 Arc 如果数据量大
|
||||
// 这里为了简单直接 clone,对于 800x600 (约 2MB) 来说很快
|
||||
let data = self.pixels.clone();
|
||||
let data = self.canvas.get_pixels();
|
||||
|
||||
self.image_handle = image::Handle::from_rgba(WIDTH, HEIGHT, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// draw method
|
||||
#[allow(unused)]
|
||||
impl PaintApp {
|
||||
fn pixel_at(&self, x: i32, y: i32) -> ColorU8 {
|
||||
// 边界检查
|
||||
if x < 0 || x >= WIDTH as i32 || y < 0 || y >= HEIGHT as i32 {
|
||||
return ColorU8::new(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
let x = x as u32;
|
||||
let y = y as u32;
|
||||
|
||||
let index = ((y * WIDTH + x) * 4) as usize;
|
||||
|
||||
ColorU8::new(
|
||||
self.pixels[index],
|
||||
self.pixels[index + 1],
|
||||
self.pixels[index + 2],
|
||||
self.pixels[index + 3],
|
||||
)
|
||||
}
|
||||
|
||||
fn pixel_at_raw(&self, x: i32, y: i32) -> ColorU8 {
|
||||
let x = x as u32;
|
||||
let y = y as u32;
|
||||
let index = ((y * WIDTH + x) * 4) as usize;
|
||||
ColorU8::new(
|
||||
self.pixels[index],
|
||||
self.pixels[index + 1],
|
||||
self.pixels[index + 2],
|
||||
self.pixels[index + 3],
|
||||
)
|
||||
}
|
||||
|
||||
fn pixel_at1(&self, point: Point) -> ColorU8 {
|
||||
self.pixel_at(point.x as i32, point.y as i32)
|
||||
}
|
||||
|
||||
fn draw_brush_at(&mut self, center_x: i32, center_y: i32) {
|
||||
let r = self.brush_radius;
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
if dx * dx + dy * dy <= r * r {
|
||||
self.draw_pixel_at(center_x + dx, center_y + dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_pixel_at_raw(&mut self, x: u32, y: u32) {
|
||||
// 计算索引:(y * width + x) * 4
|
||||
let index = ((y * WIDTH + x) * 4) as usize;
|
||||
|
||||
// 写入 RGBA 数据
|
||||
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||
self.pixels[index] = (self.color.r * 255.0) as u8; // R
|
||||
self.pixels[index + 1] = (self.color.g * 255.0) as u8; // G
|
||||
self.pixels[index + 2] = (self.color.b * 255.0) as u8; // B
|
||||
self.pixels[index + 3] = (self.color.a * 255.0) as u8; // A
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_pixel_at(&mut self, x: i32, y: i32) {
|
||||
// 边界检查
|
||||
if x < 0 || x >= WIDTH as i32 || y < 0 || y >= HEIGHT as i32 {
|
||||
return;
|
||||
}
|
||||
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||
}
|
||||
|
||||
fn draw_pixel_row(&mut self, xs: i32, xe: i32, y: i32) {
|
||||
if y < 0 || y >= HEIGHT as i32 {
|
||||
return;
|
||||
}
|
||||
|
||||
let xs = xs.clamp(0, WIDTH as i32 - 1) as u32;
|
||||
let xe = xe.clamp(0, WIDTH as i32) as u32;
|
||||
let y = y as u32;
|
||||
for x in xs..xe {
|
||||
let index = ((y * WIDTH + x) * 4) as usize;
|
||||
|
||||
// 写入 RGBA 数据
|
||||
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||
self.pixels[index] = (self.color.r * 255.0) as u8; // R
|
||||
self.pixels[index + 1] = (self.color.g * 255.0) as u8; // G
|
||||
self.pixels[index + 2] = (self.color.b * 255.0) as u8; // B
|
||||
self.pixels[index + 3] = (self.color.a * 255.0) as u8; // A
|
||||
}
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
fn draw_pixel_atp(&mut self, pos: Point) {
|
||||
self.draw_pixel_at(pos.x as i32, pos.y as i32)
|
||||
}
|
||||
|
||||
fn draw_pixels(&mut self, points: Vec<Point<i32>>) {
|
||||
for point in &points {
|
||||
self.draw_pixel_at(point.x, point.y);
|
||||
}
|
||||
}
|
||||
|
||||
fn bresenham_line(&mut self, begin: Point<i32>, end: Point<i32>) -> Vec<Point<i32>> {
|
||||
let x1 = begin.x ;
|
||||
let y1 = begin.y;
|
||||
let x2 = end.x ;
|
||||
let y2 = end.y;
|
||||
|
||||
let dx = (x2 - x1);
|
||||
let dy = (y2 - y1);
|
||||
let dx1 = dx.abs();
|
||||
let dy1 = dy.abs();
|
||||
let mut px = 2 * dy1 - dx1;
|
||||
let mut py = 2 * dx1 - dy1;
|
||||
|
||||
let mut x;
|
||||
let mut y;
|
||||
let xe;
|
||||
let ye;
|
||||
|
||||
let mut points = Vec::new();
|
||||
if dy1 <= dx1 {
|
||||
if dx >= 0 {
|
||||
x = x1;
|
||||
y = y1;
|
||||
xe = x2;
|
||||
ye = y2;
|
||||
} else {
|
||||
x = x2;
|
||||
y = y2;
|
||||
xe = x1;
|
||||
ye = y1;
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
while x < xe {
|
||||
x += 1;
|
||||
if px < 0 {
|
||||
px = px + 2 * dy1;
|
||||
} else {
|
||||
if (dx < 0 && dy < 0) || (dx > 0 && dy > 0) {
|
||||
y = y + 1;
|
||||
} else {
|
||||
y = y - 1;
|
||||
}
|
||||
px = px + 2 * (dy1 - dx1);
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
}
|
||||
} else {
|
||||
if dy >= 0 {
|
||||
x = x1;
|
||||
y = y1;
|
||||
ye = y2;
|
||||
xe = x2;
|
||||
} else {
|
||||
x = x2;
|
||||
y = y2;
|
||||
ye = y1;
|
||||
xe = x1;
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
while y < ye {
|
||||
y = y + 1;
|
||||
if py <= 0 {
|
||||
py = py + 2 * dx1;
|
||||
} else {
|
||||
if (dx < 0 && dy < 0) || (dx > 0 && dy > 0) {
|
||||
x = x + 1;
|
||||
} else {
|
||||
x = x - 1;
|
||||
}
|
||||
py = py + 2 * (dx1 - dy1);
|
||||
}
|
||||
points.push(Point::new(x, y));
|
||||
}
|
||||
}
|
||||
points.push(Point::new(xe, ye));
|
||||
|
||||
points
|
||||
}
|
||||
|
||||
/// Bresenham's line drawing algorithm
|
||||
fn draw_line(&mut self, begin: Point, end: Point) {
|
||||
let points = self.bresenham_line(point_to_i32(begin), point_to_i32(end));
|
||||
self.draw_pixels(points);
|
||||
}
|
||||
|
||||
fn draw_line_thick(&mut self, begin: Point<i32>, end: Point<i32>, line_width: i32) {
|
||||
if line_width <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let half = (line_width) / 2;
|
||||
let points = self.bresenham_line(begin, end);
|
||||
if line_width == 1 {
|
||||
self.draw_pixels(points);
|
||||
return;
|
||||
}
|
||||
for point in &points {
|
||||
for dx in -half..=(line_width - 1 - half) {
|
||||
for dy in -half..=(line_width - 1 - half) {
|
||||
self.draw_pixel_at((point.x + dx), (point.y + dy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_pixels(&mut self) {
|
||||
self.pixels_bak = self.pixels.clone();
|
||||
}
|
||||
|
||||
fn restore_pixels(&mut self) {
|
||||
self.pixels = self.pixels_bak.clone();
|
||||
}
|
||||
|
||||
fn fill_slow(&mut self, begin: Point) -> (i32, i32) {
|
||||
let start_x = begin.x as i32;
|
||||
let start_y = begin.y as i32;
|
||||
let target_color = self.pixel_at(start_x, start_y);
|
||||
if target_color.is_same(self.color) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let mut scan_points = vec![(start_x, start_y)];
|
||||
let width = WIDTH as i32;
|
||||
let height = HEIGHT as i32;
|
||||
let mut iter_count = 0;
|
||||
let mut fill_count = 0;
|
||||
while let Some((x, y)) = scan_points.pop() {
|
||||
iter_count += 1;
|
||||
|
||||
if x < 0 || x >= width || y < 0 || y >= height {
|
||||
continue;
|
||||
}
|
||||
if self.pixel_at_raw(x, y) == target_color {
|
||||
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||
fill_count += 1;
|
||||
|
||||
let p1 = (x - 1, y);
|
||||
let p2 = (x + 1, y);
|
||||
let p3 = (x, y - 1);
|
||||
let p4 = (x, y + 1);
|
||||
scan_points.push(p1);
|
||||
scan_points.push(p2);
|
||||
scan_points.push(p3);
|
||||
scan_points.push(p4);
|
||||
}
|
||||
}
|
||||
(iter_count, fill_count)
|
||||
}
|
||||
|
||||
fn fill_less_slow(&mut self, begin: Point) -> (i32, i32) {
|
||||
let start_x = begin.x as i32;
|
||||
let start_y = begin.y as i32;
|
||||
let width = WIDTH as i32;
|
||||
let height = HEIGHT as i32;
|
||||
|
||||
if start_x < 0 || start_x >= width || start_y < 0 || start_y >= height {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let target_color = self.pixel_at(start_x, start_y);
|
||||
if target_color.is_same(self.color) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let mut stack = vec![(start_x, start_y)];
|
||||
let mut iter_count = 0;
|
||||
let mut fill_count = 0;
|
||||
|
||||
while let Some((x, y)) = stack.pop() {
|
||||
iter_count += 1;
|
||||
|
||||
if x < 0 || x >= width || y < 0 || y >= height {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.pixel_at_raw(x, y) != target_color {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||
fill_count += 1;
|
||||
|
||||
// 提前检查边界再入栈
|
||||
if x > 0 {
|
||||
stack.push((x - 1, y));
|
||||
}
|
||||
if x + 1 < width {
|
||||
stack.push((x + 1, y));
|
||||
}
|
||||
if y > 0 {
|
||||
stack.push((x, y - 1));
|
||||
}
|
||||
if y + 1 < height {
|
||||
stack.push((x, y + 1));
|
||||
}
|
||||
}
|
||||
|
||||
(iter_count, fill_count)
|
||||
}
|
||||
|
||||
fn fill_scanline(&mut self, begin: Point) -> (i32, i32) {
|
||||
let start_x = begin.x as i32;
|
||||
let start_y = begin.y as i32;
|
||||
let width = WIDTH as i32;
|
||||
let height = HEIGHT as i32;
|
||||
|
||||
// 边界检查
|
||||
if start_x < 0 || start_x >= width || start_y < 0 || start_y >= height {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let target_color = self.pixel_at(start_x, start_y);
|
||||
if target_color.is_same(self.color) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// 栈中存储 (y, x1, x2):表示第 y 行从 x1 到 x2(含)需要向上/下扫描
|
||||
let mut stack = vec![(start_y, start_x, start_x)];
|
||||
let mut iter_count = 0;
|
||||
let mut fill_count = 0;
|
||||
|
||||
while let Some((y, mut lx, mut rx)) = stack.pop() {
|
||||
iter_count += 1;
|
||||
|
||||
// 向左扩展 lx
|
||||
while lx - 1 >= 0 && self.pixel_at_raw(lx - 1, y) == target_color {
|
||||
lx -= 1;
|
||||
}
|
||||
// 向右扩展 rx
|
||||
while rx + 1 < width && self.pixel_at_raw(rx + 1, y) == target_color {
|
||||
rx += 1;
|
||||
}
|
||||
|
||||
// 填充当前行 [lx, rx]
|
||||
for x in lx..=rx {
|
||||
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||
fill_count += 1;
|
||||
}
|
||||
|
||||
// 检查上一行 (y - 1)
|
||||
if y - 1 >= 0 {
|
||||
let mut x = lx;
|
||||
while x <= rx {
|
||||
if self.pixel_at_raw(x, y - 1) == target_color {
|
||||
let span_start = x;
|
||||
// 跳过连续的目标色块
|
||||
while x <= rx && self.pixel_at_raw(x, y - 1) == target_color {
|
||||
x += 1;
|
||||
}
|
||||
// 将这个 span 入栈(用于后续处理上一行的上一行)
|
||||
stack.push((y - 1, span_start, x - 1));
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查下一行 (y + 1)
|
||||
if y + 1 < height {
|
||||
let mut x = lx;
|
||||
while x <= rx {
|
||||
if self.pixel_at_raw(x, y + 1) == target_color {
|
||||
let span_start = x;
|
||||
while x <= rx && self.pixel_at_raw(x, y + 1) == target_color {
|
||||
x += 1;
|
||||
}
|
||||
stack.push((y + 1, span_start, x - 1));
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(iter_count, fill_count)
|
||||
}
|
||||
|
||||
fn stroke_rect(&mut self, x: i32, y: i32, width: i32, height: i32, line_width: i32) {
|
||||
self.draw_line_thick(Point::new(x, y), Point::new(x, y + height), line_width);
|
||||
self.draw_line_thick(Point::new(x, y + height), Point::new(x + width, y + height), line_width);
|
||||
self.draw_line_thick(Point::new(x + width, y + height), Point::new(x + width, y), line_width);
|
||||
self.draw_line_thick(Point::new(x, y), Point::new(x + width, y), line_width);
|
||||
}
|
||||
|
||||
fn stroke_rect1(&mut self, left_top: Point, right_bottom: Point, line_width: i32) {
|
||||
let x = left_top.x as i32;
|
||||
let y = left_top.y as i32;
|
||||
let width = (right_bottom.x - left_top.x) as i32;
|
||||
let height = (right_bottom.y - left_top.y) as i32;
|
||||
self.stroke_rect(x, y, width, height, line_width);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::application(PaintApp::new, PaintApp::update, PaintApp::view)
|
||||
.theme(Theme::Dark)
|
||||
|
||||
Reference in New Issue
Block a user