Files
mspaint/src/paint.rs

460 lines
13 KiB
Rust
Raw Normal View History

2026-02-24 22:20:06 +08:00
use iced::Theme;
use iced::padding;
use iced::widget::container;
use iced::widget::{Column, Grid, button, column, image, row, mouse_area};
use iced::{Border, Color, Length, Point, Task};
use crate::image_button::image_button;
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
struct Paint {
tool_states: [bool; Tool::Count as usize],
tool_selected: Tool,
// 原始像素数据RGBA 格式
// 长度 = WIDTH * HEIGHT * 4
pixels: Vec<u8>,
pixels_bak: Vec<u8>,
// 当前画笔颜色
color: Color,
// 是否正在绘制
is_drawing: bool,
begin_point: Option<Point>,
// 用于显示的图像句柄缓存
// 每次像素变化后需要重新生成
image_handle: image::Handle,
// 标记像素是否被修改,用于优化图像句柄的生成
dirty: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum Message {
Decrement,
MousePressed(Point),
MouseReleased,
MouseMoved(Point),
Clear,
ChangeColor(Color),
// 内部消息:请求刷新图像
RefreshImage,
ClickTool(Tool),
}
#[derive(Clone, Copy, Debug)]
pub enum Tool {
FreeFormSelect,
Select,
Eraser,
FillWithColor,
PickColor,
Magnifier,
Pencil,
Brush,
Airbrush,
Text,
Line,
Curve,
Rectangle,
Polygon,
Ellipse,
RoundedRectangle,
Count,
}
impl From<usize> for Tool {
fn from(tool: usize) -> Self {
match tool {
0 => Self::FreeFormSelect,
1 => Self::Select,
2 => Self::Eraser,
3 => Self::FillWithColor,
4 => Self::PickColor,
5 => Self::Magnifier,
6 => Self::Pencil,
7 => Self::Brush,
8 => Self::Airbrush,
9 => Self::Text,
10 => Self::Line,
11 => Self::Curve,
12 => Self::Rectangle,
13 => Self::Polygon,
14 => Self::Ellipse,
15 => Self::RoundedRectangle,
_ => Self::Count,
}
}
}
impl From<Tool> for usize {
fn from(tool: Tool) -> usize {
match tool {
Tool::FreeFormSelect => 0,
Tool::Select => 1,
Tool::Eraser => 2,
Tool::FillWithColor => 3,
Tool::PickColor => 4,
Tool::Magnifier => 5,
Tool::Pencil => 6,
Tool::Brush => 7,
Tool::Airbrush => 8,
Tool::Text => 9,
Tool::Line => 10,
Tool::Curve => 11,
Tool::Rectangle => 12,
Tool::Polygon => 13,
Tool::Ellipse => 14,
Tool::RoundedRectangle => 15,
Tool::Count => 16,
}
}
}
impl Paint {
// 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();
Self {
tool_states: [false; Tool::Count as usize],
tool_selected: Tool::Count,
pixels,
pixels_bak: Vec::new(),
color: Color::BLACK,
is_drawing: false,
begin_point: None,
image_handle: image::Handle::from_rgba(WIDTH, HEIGHT, data),
dirty: false,
}
}
pub fn view(&self) -> Column<'_, Message> {
// 创建显示图像的 Widget
// 如果 handle 还没准备好,显示一个占位符
let image_widget = image(self.image_handle.clone())
.width(Length::Fixed(WIDTH as f32))
.height(Length::Fixed(HEIGHT as f32));
let canvas_area = mouse_area(image_widget)
.on_press(Message::MousePressed(Point::ORIGIN)) // 占位,实际逻辑在 on_drag 或自定义
.on_release(Message::MouseReleased)
.on_move(|pos| Message::MouseMoved(pos));
// 注意mouse_area 的 on_move 给出的坐标通常是相对于 widget 左上角的,这正是我们需要的!
let mut grid = Grid::new();
grid = grid.columns(2).width(100);
for i in 0..(Tool::Count as usize) {
let tool = Tool::from(i);
let btn = image_button(
format!("image/normal/normal_{:02}.jpg", i + 1),
format!("image/selected/selected_{:02}.jpg", i + 1),
self.tool_states[i],
)
.on_press(Message::ClickTool(tool));
grid = grid.push(btn);
}
let tool_area = container(grid)
.padding(padding::top(5).left(5).right(5).bottom(100))
.style(|theme: &Theme| {
let palette = theme.extended_palette();
container::Style {
background: Some(Color::from_rgb8(192, 192, 192).into()),
border: Border {
width: 1.0,
radius: 5.0.into(),
color: palette.background.weak.color,
},
..container::Style::default()
}
});
// We use a column: a simple vertical layout
column![
button("-").on_press(Message::Decrement),
row![tool_area, canvas_area],
]
}
pub fn update(&mut self, message: Message) -> Task<Message> {
match self.tool_selected {
Tool::Pencil => {
self.update_with_pencil(message);
}
Tool::Line => {
self.update_with_line(message);
}
_ => {}
}
match message {
Message::Clear => {
// 重置为白色
self.pixels.fill(255);
self.dirty = true;
}
Message::ChangeColor(c) => {
self.color = c;
}
Message::RefreshImage => {
if self.dirty {
self.update_image_handle();
self.dirty = false;
}
}
Message::ClickTool(tool) => {
self.update_tool_states(tool);
}
_ => {}
}
// 如果像素被修改了,我们需要触发一次 RefreshImage 来更新 UI
// 在实际复杂应用中,可能需要防抖或异步处理,这里为了实时性直接同步触发
if self.dirty {
// 像素变了,安排下一帧刷新图像句柄
// 注意:频繁生成 Handle 可能消耗 CPU生产环境建议加节流
return Task::perform(async { Message::RefreshImage }, |msg| msg);
}
Task::none()
}
// endregion
// region tool update
pub fn update_with_fill_with_color(&mut self, message: Message) {
match message {
Message::MousePressed(_pos) => {
}
_ => {}
}
}
pub fn update_with_pencil(&mut self, message: Message) {
match message {
Message::MousePressed(_pos) => {
self.is_drawing = true;
}
Message::MouseReleased => {
self.is_drawing = false;
self.begin_point = None;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
if let Some(begin_point) = self.begin_point {
self.draw_line(begin_point, pos);
} else {
self.draw_pixel_at1(pos);
}
self.begin_point = Some(pos);
}
}
_ => {}
}
}
pub fn update_with_line(&mut self, message: Message) {
match message {
Message::MousePressed(_pos) => {
self.is_drawing = true;
self.save_pixels();
}
Message::MouseReleased => {
self.is_drawing = false;
self.begin_point = None;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
if let Some(begin_point) = self.begin_point {
self.restore_pixels();
self.draw_line(begin_point, pos);
} else {
self.begin_point = Some(pos);
}
}
}
_ => {}
}
}
// endregion
pub fn update_tool_states(&mut self, tool: Tool) {
let idx = tool as usize;
if idx >= self.tool_states.len() {
return;
}
let old_value = self.tool_states[idx];
for i in 0..(Tool::Count as usize) {
self.tool_states[i] = false;
}
self.tool_states[idx] = !old_value;
self.tool_selected = idx.into();
}
/// 将原始字节转换为 Iced 的图像句柄
fn update_image_handle(&mut self) {
// 克隆数据以避免所有权问题,或者使用 Arc 如果数据量大
// 这里为了简单直接 clone对于 800x600 (约 2MB) 来说很快
let data = self.pixels.clone();
self.image_handle = image::Handle::from_rgba(WIDTH, HEIGHT, data);
}
}
/// draw method
impl Paint {
/// 核心绘图逻辑:直接在字节数组上操作
fn draw_pixel_at(&mut self, pos: Point<i32>) {
let x = pos.x;
let y = pos.y;
// 边界检查
if x < 0 || x >= WIDTH as i32 || y < 0 || y >= HEIGHT as i32 {
return;
}
let x = x as u32;
let y = y as 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;
}
fn draw_pixel_at1(&mut self, pos: Point) {
self.draw_pixel_at(Point::new(pos.x as i32, pos.y as i32))
}
fn draw_lines(&mut self, points: &[Point]) {
if points.is_empty() {
return;
}
if points.len() == 1 {
self.draw_pixel_at1(points[0]);
return;
}
let mut begin = points[0];
for point in points.iter().skip(1) {
self.draw_line(begin, point.clone());
begin = point.clone();
}
}
/// Bresenham's line drawing algorithm
fn draw_line(&mut self, begin: Point, end: Point) {
let x1 = begin.x;
let y1 = begin.y;
let x2 = end.x;
let y2 = end.y;
let dx = (x2 - x1) as i32;
let dy = (y2 - y1) as i32;
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;
if dy1 <= dx1 {
if dx >= 0 {
x = x1 as i32;
y = y1 as i32;
xe = x2 as i32;
} else {
x = x2 as i32;
y = y2 as i32;
xe = x1 as i32;
}
let point = Point::new(x, y);
self.draw_pixel_at(point);
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);
}
let point = Point::new(x, y);
self.draw_pixel_at(point);
}
} else {
if dy >= 0 {
x = x1 as i32;
y = y1 as i32;
ye = y2 as i32;
} else {
x = x2 as i32;
y = y2 as i32;
ye = y1 as i32;
}
let point = Point::new(x, y);
self.draw_pixel_at(point);
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);
}
let point = Point::new(x, y);
self.draw_pixel_at(point);
}
}
}
fn save_pixels(&mut self) {
self.pixels_bak = self.pixels.clone();
}
fn restore_pixels(&mut self) {
self.pixels = self.pixels_bak.clone();
}
}
pub fn main() -> iced::Result {
iced::application(Paint::new, Paint::update, Paint::view)
.theme(Theme::CatppuccinMocha)
.run()
}