feat: 监听全局鼠标释放和异步保存图片
This commit is contained in:
87
src/paint.rs
87
src/paint.rs
@@ -4,7 +4,8 @@ use iced::padding;
|
|||||||
use iced::widget::container;
|
use iced::widget::container;
|
||||||
use iced::widget::{Column, button, column, image, pick_list, row, text};
|
use iced::widget::{Column, button, column, image, pick_list, row, text};
|
||||||
use iced::{Border, Color, Element, Length, Point, Renderer, Task};
|
use iced::{Border, Color, Element, Length, Point, Renderer, Task};
|
||||||
use iced_core::color;
|
use iced::{Subscription, color, event, mouse};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
use crate::image_button::image_button;
|
use crate::image_button::image_button;
|
||||||
use crate::mouse_area::mouse_area;
|
use crate::mouse_area::mouse_area;
|
||||||
@@ -54,7 +55,7 @@ enum Message {
|
|||||||
|
|
||||||
Clear,
|
Clear,
|
||||||
SavePNG,
|
SavePNG,
|
||||||
// 内部消息:请求刷新图像
|
/// 内部消息:请求刷新图像
|
||||||
RefreshImage,
|
RefreshImage,
|
||||||
|
|
||||||
ClickTool(Tool),
|
ClickTool(Tool),
|
||||||
@@ -63,6 +64,9 @@ enum Message {
|
|||||||
Decrement(ConfigOption),
|
Decrement(ConfigOption),
|
||||||
|
|
||||||
BrushSelected(BrushKind),
|
BrushSelected(BrushKind),
|
||||||
|
|
||||||
|
/// 全局鼠标释放
|
||||||
|
WindowMouseRelease,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -200,7 +204,7 @@ struct PaintApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PaintApp {
|
impl PaintApp {
|
||||||
// region iced application (new view update)
|
// region iced application
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
|
let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
|
||||||
@@ -357,17 +361,10 @@ impl PaintApp {
|
|||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
Message::SavePNG => {
|
Message::SavePNG => {
|
||||||
let scale = 4;
|
|
||||||
let (width, height) = self.canvas.size();
|
let (width, height) = self.canvas.size();
|
||||||
let pixels = self.canvas.get_pixels_scale(4);
|
let pixels = self.canvas.get_pixels();
|
||||||
save_rgba_to_png(
|
let path = "mspaint.png";
|
||||||
pixels,
|
save_pixels_async(pixels, width as u32, height as u32, 4, path.to_string());
|
||||||
(width * scale) as u32,
|
|
||||||
(height * scale) as u32,
|
|
||||||
"mspaint.png",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
println!("save png");
|
|
||||||
}
|
}
|
||||||
Message::RefreshImage => {
|
Message::RefreshImage => {
|
||||||
if self.dirty {
|
if self.dirty {
|
||||||
@@ -393,6 +390,10 @@ impl PaintApp {
|
|||||||
Message::BrushSelected(kind) => {
|
Message::BrushSelected(kind) => {
|
||||||
self.brush_selected = Some(kind);
|
self.brush_selected = Some(kind);
|
||||||
}
|
}
|
||||||
|
Message::WindowMouseRelease => {
|
||||||
|
// 处理鼠标在 canvas_area 外面释放
|
||||||
|
self.is_drawing = false;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +408,16 @@ impl PaintApp {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscription(&self) -> Subscription<Message> {
|
||||||
|
event::listen().filter_map(|event| match event {
|
||||||
|
// 只关心鼠标释放事件
|
||||||
|
iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||||
|
Some(Message::WindowMouseRelease)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region tool update
|
// region tool update
|
||||||
@@ -580,6 +591,7 @@ impl PaintApp {
|
|||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::application(PaintApp::new, PaintApp::update, PaintApp::view)
|
iced::application(PaintApp::new, PaintApp::update, PaintApp::view)
|
||||||
.theme(Theme::Dark)
|
.theme(Theme::Dark)
|
||||||
|
.subscription(PaintApp::subscription)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,3 +608,52 @@ fn save_rgba_to_png(
|
|||||||
img.save(path)?;
|
img.save(path)?;
|
||||||
Ok(())
|
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),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user