Files
mspaint/src/paint.rs

809 lines
26 KiB
Rust
Raw Normal View History

use ::image::{ImageBuffer, ImageError, Rgba};
2026-02-24 22:20:06 +08:00
use iced::Theme;
use iced::padding;
2026-02-28 14:32:39 +08:00
use iced::time::{self, Instant, milliseconds};
2026-02-24 22:20:06 +08:00
use iced::widget::container;
2026-02-27 22:29:17 +08:00
use iced::widget::{Column, button, column, image, pick_list, row, text};
2026-02-26 13:18:05 +08:00
use iced::{Border, Color, Element, Length, Point, Renderer, Task};
use iced::{Subscription, color, event, mouse};
use std::thread;
2026-02-24 22:20:06 +08:00
2026-02-28 01:09:51 +08:00
use crate::image_button::image_button;
use crate::mouse_area::mouse_area;
2026-02-26 19:38:34 +08:00
use crate::mscanvas::MSCanvas;
2026-02-24 22:20:06 +08:00
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
2026-02-27 22:29:17 +08:00
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum BrushKind {
#[default]
Circle,
Square,
Slash,
Backslash,
}
impl BrushKind {
const ALL: [BrushKind; 4] = [
BrushKind::Circle,
BrushKind::Square,
BrushKind::Slash,
BrushKind::Backslash,
];
}
impl std::fmt::Display for BrushKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
BrushKind::Circle => "Circle",
BrushKind::Square => "Square",
BrushKind::Slash => "Slash",
BrushKind::Backslash => "Backslash",
}
)
}
}
2026-02-24 22:20:06 +08:00
#[derive(Debug, Clone, Copy)]
2026-02-27 22:29:17 +08:00
enum Message {
2026-02-24 22:20:06 +08:00
MousePressed(Point),
2026-02-25 11:51:03 +08:00
MouseReleased(Point),
2026-02-24 22:20:06 +08:00
MouseMoved(Point),
2026-02-28 01:09:51 +08:00
2026-02-24 22:20:06 +08:00
Clear,
2026-02-28 01:09:51 +08:00
SavePNG,
/// 内部消息:请求刷新图像
2026-02-24 22:20:06 +08:00
RefreshImage,
ClickTool(Tool),
2026-02-26 19:38:34 +08:00
2026-02-27 22:29:17 +08:00
Increment(ConfigOption),
Decrement(ConfigOption),
BrushSelected(BrushKind),
/// 全局鼠标释放
WindowMouseRelease,
2026-02-28 14:32:39 +08:00
Tick(Instant),
2026-02-24 22:20:06 +08:00
}
2026-02-28 14:32:39 +08:00
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2026-02-27 22:29:17 +08:00
enum Tool {
2026-02-24 22:20:06 +08:00
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,
}
}
}
2026-02-27 22:29:17 +08:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConfigOption {
EraserWidth,
LineWidth,
2026-02-28 14:32:39 +08:00
AirbrushRadius,
AirbrushDensity,
2026-02-27 22:29:17 +08:00
}
#[derive(Debug, Clone, Copy)]
struct Config {
eraser_width: i32,
line_width: i32,
2026-02-28 14:32:39 +08:00
airbrush_radius: i32,
airbrush_density: i32,
2026-02-27 22:29:17 +08:00
}
impl Default for Config {
fn default() -> Self {
Self::DEFAULT.clone()
}
}
impl Config {
const DEFAULT: Config = Config {
line_width: 1,
eraser_width: 4,
2026-02-28 14:32:39 +08:00
airbrush_radius: 4,
airbrush_density: 16,
2026-02-27 22:29:17 +08:00
};
fn incr(&mut self, option: ConfigOption, step: i32) {
match option {
ConfigOption::EraserWidth => {
self.eraser_width += step;
if self.eraser_width < Self::DEFAULT.eraser_width {
self.eraser_width = Self::DEFAULT.eraser_width;
}
}
ConfigOption::LineWidth => {
self.line_width += step;
if self.line_width < Self::DEFAULT.line_width {
self.line_width = Self::DEFAULT.line_width;
}
}
2026-02-28 14:32:39 +08:00
ConfigOption::AirbrushRadius => {
self.airbrush_radius += step;
if self.airbrush_radius < Self::DEFAULT.airbrush_radius {
self.airbrush_radius = Self::DEFAULT.airbrush_radius;
}
}
ConfigOption::AirbrushDensity => {
self.airbrush_density += step;
if self.airbrush_density < Self::DEFAULT.airbrush_density {
self.airbrush_density = Self::DEFAULT.airbrush_density;
}
}
2026-02-27 22:29:17 +08:00
}
}
}
2026-02-26 13:18:05 +08:00
struct PaintApp {
2026-02-25 11:51:03 +08:00
tool_states: [bool; Tool::Count as usize],
tool_selected: Tool,
2026-02-26 19:38:34 +08:00
canvas: MSCanvas,
2026-02-25 11:51:03 +08:00
2026-02-28 16:17:17 +08:00
/// 是否正在绘制
2026-02-25 11:51:03 +08:00
is_drawing: bool,
2026-02-28 16:17:17 +08:00
/// pencil brush line curve rectangle
2026-02-25 11:51:03 +08:00
begin_point: Point,
2026-02-28 16:17:17 +08:00
/// curve
end_point: Point,
2026-02-25 11:51:03 +08:00
2026-02-28 16:17:17 +08:00
/// 贝塞尔曲线控制
is_controlling: bool,
control_points: Vec<Point>,
/// 用于显示的图像句柄缓存
/// 每次像素变化后需要重新生成
2026-02-25 11:51:03 +08:00
image_handle: image::Handle,
2026-02-28 16:17:17 +08:00
/// 标记像素是否被修改,用于优化图像句柄的生成
2026-02-25 11:51:03 +08:00
dirty: bool,
2026-02-26 19:38:34 +08:00
2026-02-27 22:29:17 +08:00
config: Config,
brush_selected: Option<BrushKind>,
2026-02-25 11:51:03 +08:00
}
2026-02-24 22:20:06 +08:00
2026-02-26 13:18:05 +08:00
impl PaintApp {
// region iced application
2026-02-24 22:20:06 +08:00
pub fn new() -> Self {
2026-02-27 22:29:17 +08:00
let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
let (width, height) = canvas.size();
2026-02-26 19:38:34 +08:00
let pixels = canvas.get_pixels();
2026-02-27 22:29:17 +08:00
let config = Config::default();
canvas.set_line_width(config.line_width);
2026-02-24 22:20:06 +08:00
Self {
tool_states: [false; Tool::Count as usize],
tool_selected: Tool::Count,
2026-02-26 19:38:34 +08:00
canvas,
2026-02-24 22:20:06 +08:00
is_drawing: false,
2026-02-25 11:51:03 +08:00
begin_point: Point::ORIGIN,
2026-02-28 16:17:17 +08:00
end_point: Point::ORIGIN,
is_controlling: false,
control_points: Vec::with_capacity(2),
2026-02-27 22:29:17 +08:00
image_handle: image::Handle::from_rgba(width as u32, height as u32, pixels),
2026-02-24 22:20:06 +08:00
dirty: false,
2026-02-27 22:29:17 +08:00
config,
brush_selected: None,
2026-02-24 22:20:06 +08:00
}
}
pub fn view(&self) -> Column<'_, Message> {
// 创建显示图像的 Widget
2026-02-27 22:29:17 +08:00
let (width, height) = self.canvas.size();
2026-02-24 22:20:06 +08:00
let image_widget = image(self.image_handle.clone())
2026-02-27 22:29:17 +08:00
.width(Length::Fixed(width as f32))
.height(Length::Fixed(height as f32));
2026-02-24 22:20:06 +08:00
let canvas_area = mouse_area(image_widget)
2026-02-25 11:51:03 +08:00
.on_press(|pos| Message::MousePressed(pos)) // 占位,实际逻辑在 on_drag 或自定义
.on_release(|pos| Message::MouseReleased(pos))
2026-02-24 22:20:06 +08:00
.on_move(|pos| Message::MouseMoved(pos));
// 注意mouse_area 的 on_move 给出的坐标通常是相对于 widget 左上角的,这正是我们需要的!
2026-02-26 01:10:43 +08:00
let canvas_area = container(canvas_area)
.width(Length::Fill)
.height(Length::Fill)
2026-02-28 01:09:51 +08:00
.padding(padding::left(5).top(5).right(5))
2026-02-26 13:18:05 +08:00
.style(|_| container::Style {
background: Some(color!(0x808080).into()),
..container::Style::default()
2026-02-26 01:10:43 +08:00
});
2026-02-24 22:20:06 +08:00
2026-02-26 01:10:43 +08:00
let mut columns: Vec<Element<'_, Message, Theme, Renderer>> = Vec::new();
for i in (0..(Tool::Count as usize)).step_by(2) {
let btn1 = image_button(
2026-02-24 22:20:06 +08:00
format!("image/normal/normal_{:02}.jpg", i + 1),
format!("image/selected/selected_{:02}.jpg", i + 1),
self.tool_states[i],
)
2026-02-26 13:18:05 +08:00
.on_press(Message::ClickTool(Tool::from(i)));
2026-02-26 01:10:43 +08:00
let btn2 = image_button(
format!("image/normal/normal_{:02}.jpg", i + 2),
format!("image/selected/selected_{:02}.jpg", i + 2),
2026-02-26 13:18:05 +08:00
self.tool_states[i + 1],
2026-02-26 01:10:43 +08:00
)
2026-02-26 13:18:05 +08:00
.on_press(Message::ClickTool(Tool::from(i + 1)));
2026-02-26 01:10:43 +08:00
columns.push(row![btn1, btn2].into());
2026-02-24 22:20:06 +08:00
}
2026-02-26 01:10:43 +08:00
let tool_config_area = container("").width(90).height(200).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()
}
});
let tool_config_area = container(tool_config_area).padding(padding::top(5).left(5));
columns.push(tool_config_area.into());
2026-02-26 19:38:34 +08:00
let grid = Column::from_vec(columns);
2026-02-24 22:20:06 +08:00
let tool_area = container(grid)
2026-02-26 19:38:34 +08:00
.padding(padding::top(5).left(5).right(5).bottom(10))
2026-02-24 22:20:06 +08:00
.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()
}
});
2026-02-28 01:09:51 +08:00
let mut debug_area = row![
column![
2026-02-28 14:32:39 +08:00
text("Eraser Width"),
2026-02-28 01:09:51 +08:00
row![
button("+").on_press(Message::Increment(ConfigOption::EraserWidth)),
text(self.config.eraser_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::EraserWidth)),
],
2026-02-28 14:32:39 +08:00
]
.padding(padding::right(5)),
2026-02-28 01:09:51 +08:00
column![
2026-02-28 14:32:39 +08:00
text("Line Width"),
2026-02-28 01:09:51 +08:00
row![
button("+").on_press(Message::Increment(ConfigOption::LineWidth)),
text(self.config.line_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::LineWidth)),
],
2026-02-28 14:32:39 +08:00
]
.padding(padding::right(5)),
2026-02-27 22:29:17 +08:00
pick_list(
&BrushKind::ALL[..],
self.brush_selected,
Message::BrushSelected,
)
.placeholder("Brush..."),
2026-02-28 14:32:39 +08:00
column![
text("Airbrush Radius"),
row![
button("+").on_press(Message::Increment(ConfigOption::AirbrushRadius)),
text(self.config.airbrush_radius).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::AirbrushRadius)),
],
]
.padding(padding::right(5)),
column![
text("Airbrush Density"),
row![
button("+").on_press(Message::Increment(ConfigOption::AirbrushDensity)),
text(self.config.airbrush_density).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::AirbrushDensity)),
],
]
.padding(padding::right(5)),
2026-02-26 19:38:34 +08:00
];
2026-02-28 01:09:51 +08:00
debug_area = debug_area.padding(padding::top(10).left(5).bottom(10));
2026-02-26 19:38:34 +08:00
2026-02-24 22:20:06 +08:00
column![
2026-02-28 01:09:51 +08:00
row![
button("CLEAR").on_press(Message::Clear),
button("PNG").on_press(Message::SavePNG),
],
row![tool_area, canvas_area],
debug_area,
2026-02-24 22:20:06 +08:00
]
}
pub fn update(&mut self, message: Message) -> Task<Message> {
match self.tool_selected {
2026-02-27 22:29:17 +08:00
Tool::Eraser => {
self.update_with_eraser(message);
}
2026-02-25 11:51:03 +08:00
Tool::FillWithColor => {
self.update_with_fill_with_color(message);
}
2026-02-24 22:20:06 +08:00
Tool::Pencil => {
self.update_with_pencil(message);
}
2026-02-28 01:09:51 +08:00
Tool::Brush => {
self.update_with_brush(message);
}
2026-02-28 14:32:39 +08:00
Tool::Airbrush => {
self.update_with_airbrush(message);
}
2026-02-24 22:20:06 +08:00
Tool::Line => {
self.update_with_line(message);
}
2026-02-28 16:17:17 +08:00
Tool::Curve => {
self.update_with_curve(message);
}
2026-02-26 13:18:05 +08:00
Tool::Rectangle => {
self.update_with_rectangle(message);
}
2026-02-24 22:20:06 +08:00
_ => {}
}
match message {
Message::Clear => {
// 重置为白色
2026-02-26 19:38:34 +08:00
self.canvas.clear();
2026-02-24 22:20:06 +08:00
self.dirty = true;
}
2026-02-28 01:09:51 +08:00
Message::SavePNG => {
let (width, height) = self.canvas.size();
let pixels = self.canvas.get_pixels();
let path = "mspaint.png";
save_pixels_async(pixels, width as u32, height as u32, 4, path.to_string());
2026-02-28 01:09:51 +08:00
}
2026-02-24 22:20:06 +08:00
Message::RefreshImage => {
if self.dirty {
self.update_image_handle();
self.dirty = false;
}
}
Message::ClickTool(tool) => {
self.update_tool_states(tool);
}
2026-02-27 22:29:17 +08:00
Message::Increment(opt) => {
self.config.incr(opt, 1);
if opt == ConfigOption::LineWidth {
self.canvas.set_line_width(self.config.line_width);
}
2026-02-26 19:38:34 +08:00
}
2026-02-27 22:29:17 +08:00
Message::Decrement(opt) => {
2026-02-28 01:09:51 +08:00
self.config.incr(opt, -1);
2026-02-27 22:29:17 +08:00
if opt == ConfigOption::LineWidth {
self.canvas.set_line_width(self.config.line_width);
2026-02-26 19:38:34 +08:00
}
}
2026-02-28 01:09:51 +08:00
Message::BrushSelected(kind) => {
self.brush_selected = Some(kind);
}
Message::WindowMouseRelease => {
// 处理鼠标在 canvas_area 外面释放
self.is_drawing = false;
}
2026-02-28 14:32:39 +08:00
Message::Tick(_) => {
if self.is_drawing && self.tool_selected == Tool::Airbrush {
self.canvas.spray_paint(
self.begin_point,
self.config.airbrush_radius,
self.config.airbrush_density as u32,
);
self.dirty = true;
}
}
2026-02-24 22:20:06 +08:00
_ => {}
}
// 如果像素被修改了,我们需要触发一次 RefreshImage 来更新 UI
// 在实际复杂应用中,可能需要防抖或异步处理,这里为了实时性直接同步触发
if self.dirty {
// 像素变了,安排下一帧刷新图像句柄
// 注意:频繁生成 Handle 可能消耗 CPU生产环境建议加节流
return Task::perform(async { Message::RefreshImage }, |msg| msg);
}
Task::none()
}
pub fn subscription(&self) -> Subscription<Message> {
2026-02-28 14:32:39 +08:00
let ev = event::listen().filter_map(|event| match event {
// 只关心鼠标释放事件
iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
Some(Message::WindowMouseRelease)
}
_ => None,
2026-02-28 14:32:39 +08:00
});
let tick = time::every(milliseconds(50)).map(Message::Tick);
2026-02-28 16:17:17 +08:00
Subscription::batch(vec![ev, tick])
}
2026-02-24 22:20:06 +08:00
// endregion
// region tool update
2026-02-27 22:29:17 +08:00
pub fn update_with_eraser(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
self.is_drawing = true;
self.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.dirty = true;
}
Message::MouseReleased(_) => {
self.is_drawing = false;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
self.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.dirty = true;
}
}
_ => {}
}
}
2026-02-24 22:20:06 +08:00
pub fn update_with_fill_with_color(&mut self, message: Message) {
match message {
2026-02-25 11:51:03 +08:00
Message::MousePressed(pos) => {
2026-02-26 19:38:34 +08:00
self.canvas.fill_scanline(pos);
self.dirty = true;
2026-02-24 22:20:06 +08:00
}
_ => {}
}
}
pub fn update_with_pencil(&mut self, message: Message) {
match message {
2026-02-25 11:51:03 +08:00
Message::MousePressed(pos) => {
2026-02-24 22:20:06 +08:00
self.is_drawing = true;
2026-02-26 19:38:34 +08:00
self.canvas.draw_pixel_at(pos);
2026-02-25 11:51:03 +08:00
self.begin_point = pos;
2026-02-28 14:32:39 +08:00
self.dirty = true;
2026-02-24 22:20:06 +08:00
}
2026-02-25 11:51:03 +08:00
Message::MouseReleased(pos) => {
2026-02-24 22:20:06 +08:00
self.is_drawing = false;
2026-02-25 11:51:03 +08:00
self.begin_point = pos;
2026-02-24 22:20:06 +08:00
}
Message::MouseMoved(pos) => {
if self.is_drawing {
2026-02-26 19:38:34 +08:00
self.canvas.draw_line(self.begin_point, pos);
2026-02-25 11:51:03 +08:00
self.begin_point = pos;
2026-02-26 19:38:34 +08:00
self.dirty = true;
2026-02-24 22:20:06 +08:00
}
}
_ => {}
}
}
2026-02-28 01:09:51 +08:00
pub fn update_with_brush(&mut self, message: Message) {
let mut brush_fn: fn(&mut MSCanvas, Point) = MSCanvas::brush_circle;
if let Some(kind) = self.brush_selected {
brush_fn = match kind {
BrushKind::Circle => MSCanvas::brush_circle,
BrushKind::Square => MSCanvas::brush_square,
BrushKind::Slash => MSCanvas::brush_slash,
BrushKind::Backslash => MSCanvas::brush_backslash,
}
}
match message {
Message::MousePressed(pos) => {
self.is_drawing = true;
brush_fn(&mut self.canvas, pos);
self.begin_point = pos;
self.dirty = true
}
Message::MouseReleased(pos) => {
self.is_drawing = false;
self.begin_point = pos;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
self.canvas.draw_line_with(self.begin_point, pos, brush_fn);
self.begin_point = pos;
self.dirty = true;
}
}
_ => {}
}
}
2026-02-28 14:32:39 +08:00
pub fn update_with_airbrush(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
self.is_drawing = true;
self.canvas.spray_paint(
pos,
self.config.airbrush_radius,
self.config.airbrush_density as u32,
);
self.begin_point = pos;
self.dirty = true;
}
Message::MouseReleased(pos) => {
self.is_drawing = false;
self.begin_point = pos;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
self.canvas.spray_paint(
pos,
self.config.airbrush_radius,
self.config.airbrush_density as u32,
);
self.begin_point = pos;
self.dirty = true;
}
}
_ => {}
}
}
2026-02-24 22:20:06 +08:00
pub fn update_with_line(&mut self, message: Message) {
match message {
2026-02-25 11:51:03 +08:00
Message::MousePressed(pos) => {
2026-02-24 22:20:06 +08:00
self.is_drawing = true;
2026-02-27 22:29:17 +08:00
self.canvas.brush_circle(pos);
2026-02-26 19:38:34 +08:00
self.canvas.save_pixels();
2026-02-25 11:51:03 +08:00
self.begin_point = pos;
2026-02-26 19:38:34 +08:00
self.dirty = true;
2026-02-24 22:20:06 +08:00
}
2026-02-25 11:51:03 +08:00
Message::MouseReleased(pos) => {
2026-02-24 22:20:06 +08:00
self.is_drawing = false;
2026-02-25 11:51:03 +08:00
self.begin_point = pos;
2026-02-24 22:20:06 +08:00
}
Message::MouseMoved(pos) => {
if self.is_drawing {
2026-02-26 19:38:34 +08:00
self.canvas.restore_pixels();
2026-02-27 22:29:17 +08:00
self.canvas
.draw_line_with_circle_brush(self.begin_point, pos);
2026-02-26 19:38:34 +08:00
self.dirty = true;
2026-02-24 22:20:06 +08:00
}
}
_ => {}
}
}
2026-02-28 16:17:17 +08:00
pub fn update_with_curve(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
if self.is_controlling {
if self.control_points.len() == 0 {
self.canvas.restore_pixels();
self.canvas
.quadratic_bezier(self.begin_point, self.end_point, pos);
self.control_points.push(pos);
} else {
self.canvas.restore_pixels();
self.canvas.cubic_bezier(
self.begin_point,
self.end_point,
self.control_points[0],
pos,
);
self.control_points.push(pos);
}
self.dirty = true;
} else {
self.is_drawing = true;
self.canvas.save_pixels();
self.begin_point = pos;
}
}
Message::MouseReleased(pos) => {
if self.control_points.len() == 0 {
self.is_drawing = false;
self.end_point = pos;
self.is_controlling = true;
} else if self.control_points.len() == 2 {
self.control_points.clear();
self.is_controlling = false;
}
}
Message::MouseMoved(pos) => {
if self.is_drawing {
self.canvas.restore_pixels();
self.canvas
.draw_line_with_circle_brush(self.begin_point, pos);
self.dirty = true;
}
}
_ => {}
}
}
2026-02-26 13:18:05 +08:00
pub fn update_with_rectangle(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
self.is_drawing = true;
2026-02-26 19:38:34 +08:00
self.canvas.save_pixels();
2026-02-26 13:18:05 +08:00
self.begin_point = pos;
}
Message::MouseReleased(pos) => {
self.is_drawing = false;
self.begin_point = pos;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
2026-02-26 19:38:34 +08:00
self.canvas.restore_pixels();
self.canvas.stroke_rect1(self.begin_point, pos);
self.dirty = true;
2026-02-26 13:18:05 +08:00
}
}
_ => {}
}
}
2026-02-24 22:20:06 +08:00
// 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) 来说很快
2026-02-26 19:38:34 +08:00
let data = self.canvas.get_pixels();
2026-02-24 22:20:06 +08:00
self.image_handle = image::Handle::from_rgba(WIDTH, HEIGHT, data);
}
}
pub fn main() -> iced::Result {
2026-02-26 13:18:05 +08:00
iced::application(PaintApp::new, PaintApp::update, PaintApp::view)
2026-02-26 01:10:43 +08:00
.theme(Theme::Dark)
.subscription(PaintApp::subscription)
2026-02-24 22:20:06 +08:00
.run()
}
2026-02-28 01:09:51 +08:00
fn save_rgba_to_png(
rgba_data: Vec<u8>,
2026-02-28 01:09:51 +08:00
width: u32,
height: u32,
path: &str,
) -> Result<(), ImageError> {
// Each pixel is 4 bytes (R, G, B, A)
let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(width, height, rgba_data)
.expect("Failed to create ImageBuffer: data size mismatch");
2026-02-28 01:09:51 +08:00
img.save(path)?;
Ok(())
}
fn scale_pixels(pixels: Vec<u8>, width: u32, height: u32, scale: u32) -> Vec<u8> {
if scale <= 1 {
return pixels;
}
let dst_width = width * scale;
let dst_height = height * scale;
let mut dst = vec![0; (dst_width * dst_height * 4) as usize]; // RGBA
for y in 0..height {
for x in 0..width {
// 源像素索引
let src_idx = ((y * width + x) * 4) as usize;
// 源像素颜色
let r = pixels[src_idx];
let g = pixels[src_idx + 1];
let b = pixels[src_idx + 2];
let a = pixels[src_idx + 3];
// 在目标图像中填充 scale×scale 区域
for dy in 0..scale {
for dx in 0..scale {
let dst_x = x * scale + dx;
let dst_y = y * scale + dy;
let dst_idx = ((dst_y * dst_width + dst_x) * 4) as usize;
dst[dst_idx] = r;
dst[dst_idx + 1] = g;
dst[dst_idx + 2] = b;
dst[dst_idx + 3] = a;
}
}
}
}
dst
}
fn save_pixels_async(pixels: Vec<u8>, width: u32, height: u32, scale: u32, path: String) {
// 用 Arc 共享数据(或直接 move 进去)
thread::spawn(move || {
let pixels = scale_pixels(pixels, width, height, scale);
match save_rgba_to_png(pixels, width, height, &path) {
Ok(()) => println!("✅ Image saved to {}", path),
Err(e) => eprintln!("❌ Failed to save image: {}", e),
}
});
}