feat: Implement crush and save to png

This commit is contained in:
2026-02-28 01:09:51 +08:00
parent 1f8a2859f9
commit 6f6e6f326b
4 changed files with 196 additions and 18 deletions

View File

@@ -1,5 +1,4 @@
use crate::image_button::image_button;
use crate::mouse_area::mouse_area;
use ::image::RgbaImage;
use iced::Theme;
use iced::padding;
use iced::widget::container;
@@ -7,6 +6,8 @@ use iced::widget::{Column, button, column, image, pick_list, row, text};
use iced::{Border, Color, Element, Length, Point, Renderer, Task};
use iced_core::color;
use crate::image_button::image_button;
use crate::mouse_area::mouse_area;
use crate::mscanvas::MSCanvas;
const WIDTH: u32 = 800;
@@ -50,7 +51,9 @@ enum Message {
MousePressed(Point),
MouseReleased(Point),
MouseMoved(Point),
Clear,
SavePNG,
// 内部消息:请求刷新图像
RefreshImage,
@@ -233,7 +236,7 @@ impl PaintApp {
let canvas_area = container(canvas_area)
.width(Length::Fill)
.height(Length::Fill)
.padding(padding::left(5).top(5))
.padding(padding::left(5).top(5).right(5))
.style(|_| container::Style {
background: Some(color!(0x808080).into()),
..container::Style::default()
@@ -288,18 +291,22 @@ impl PaintApp {
}
});
let mut debug_area = column![
text("Eraser Width"),
row![
button("+").on_press(Message::Increment(ConfigOption::EraserWidth)),
text(self.config.eraser_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::EraserWidth)),
let mut debug_area = row![
column![
text("Eraser Width "),
row![
button("+").on_press(Message::Increment(ConfigOption::EraserWidth)),
text(self.config.eraser_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::EraserWidth)),
],
],
text("Line Width"),
row![
button("+").on_press(Message::Increment(ConfigOption::LineWidth)),
text(self.config.line_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::LineWidth)),
column![
text("Line Width "),
row![
button("+").on_press(Message::Increment(ConfigOption::LineWidth)),
text(self.config.line_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::LineWidth)),
],
],
pick_list(
&BrushKind::ALL[..],
@@ -308,11 +315,15 @@ impl PaintApp {
)
.placeholder("Brush..."),
];
debug_area = debug_area.padding(padding::left(5).right(5));
debug_area = debug_area.padding(padding::top(10).left(5).bottom(10));
column![
button("CLEAR").on_press(Message::Clear),
row![tool_area, canvas_area, debug_area],
row![
button("CLEAR").on_press(Message::Clear),
button("PNG").on_press(Message::SavePNG),
],
row![tool_area, canvas_area],
debug_area,
]
}
@@ -327,6 +338,9 @@ impl PaintApp {
Tool::Pencil => {
self.update_with_pencil(message);
}
Tool::Brush => {
self.update_with_brush(message);
}
Tool::Line => {
self.update_with_line(message);
}
@@ -342,6 +356,18 @@ impl PaintApp {
self.canvas.clear();
self.dirty = true;
}
Message::SavePNG => {
let scale = 4;
let (width, height) = self.canvas.size();
let pixels = self.canvas.get_pixels_scale(4);
save_rgba_as_png(
pixels,
(width * scale) as u32,
(height * scale) as u32,
"mspaint.png",
)
.unwrap();
}
Message::RefreshImage => {
if self.dirty {
self.update_image_handle();
@@ -358,11 +384,14 @@ impl PaintApp {
}
}
Message::Decrement(opt) => {
self.config.incr(opt, 1);
self.config.incr(opt, -1);
if opt == ConfigOption::LineWidth {
self.canvas.set_line_width(self.config.line_width);
}
}
Message::BrushSelected(kind) => {
self.brush_selected = Some(kind);
}
_ => {}
}
@@ -443,6 +472,38 @@ impl PaintApp {
}
}
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;
}
}
_ => {}
}
}
pub fn update_with_line(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
@@ -520,3 +581,24 @@ pub fn main() -> iced::Result {
.theme(Theme::Dark)
.run()
}
fn save_rgba_as_png(
pixels: Vec<u8>,
width: u32,
height: u32,
path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
// 验证像素数量是否匹配
if pixels.len() != (width * height * 4) as usize {
return Err("Pixel buffer size does not match width × height × 4".into());
}
// 创建 RgbaImage
let img: RgbaImage = RgbaImage::from_vec(width, height, pixels)
.ok_or("Failed to create image from pixel data")?;
// 保存为 PNG保留 Alpha 通道)
img.save(path)?;
Ok(())
}