use ::image::{ImageBuffer, ImageError, Rgba}; use iced::Theme; use iced::padding; use iced::time::{self, Instant, milliseconds}; use iced::widget::container; use iced::widget::{Column, button, column, image, pick_list, row, text}; use iced::{Border, Color, Element, Length, Point, Renderer, Task}; use iced::{Subscription, color, event, mouse}; use std::thread; use crate::image_button::image_button; use crate::mouse_area::mouse_area; use crate::mscanvas::MSCanvas; const WIDTH: u32 = 800; const HEIGHT: u32 = 600; #[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", } ) } } #[derive(Debug, Clone, Copy)] enum Message { MousePressed(Point), MouseReleased(Point), MouseMoved(Point), Clear, SavePNG, /// 内部消息:请求刷新图像 RefreshImage, ClickTool(Tool), Increment(ConfigOption), Decrement(ConfigOption), BrushSelected(BrushKind), /// 全局鼠标释放 WindowMouseRelease, Tick(Instant), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Tool { FreeFormSelect, Select, Eraser, FillWithColor, PickColor, Magnifier, Pencil, Brush, Airbrush, Text, Line, Curve, Rectangle, Polygon, Ellipse, RoundedRectangle, Count, } impl From 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 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, } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum ConfigOption { EraserWidth, LineWidth, AirbrushRadius, AirbrushDensity, } #[derive(Debug, Clone, Copy)] struct Config { eraser_width: i32, line_width: i32, airbrush_radius: i32, airbrush_density: i32, } impl Default for Config { fn default() -> Self { Self::DEFAULT.clone() } } impl Config { const DEFAULT: Config = Config { line_width: 1, eraser_width: 4, airbrush_radius: 4, airbrush_density: 16, }; 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; } } 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; } } } } } struct PaintApp { tool_states: [bool; Tool::Count as usize], tool_selected: Tool, canvas: MSCanvas, /// 是否正在绘制 is_drawing: bool, /// pencil brush line curve rectangle begin_point: Point, /// curve end_point: Point, /// 贝塞尔曲线控制 is_controlling: bool, control_points: Vec, /// 用于显示的图像句柄缓存 /// 每次像素变化后需要重新生成 image_handle: image::Handle, /// 标记像素是否被修改,用于优化图像句柄的生成 dirty: bool, config: Config, brush_selected: Option, } impl PaintApp { // region iced application pub fn new() -> Self { let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32); let (width, height) = canvas.size(); let pixels = canvas.get_pixels(); let config = Config::default(); canvas.set_line_width(config.line_width); Self { tool_states: [false; Tool::Count as usize], tool_selected: Tool::Count, canvas, is_drawing: false, begin_point: Point::ORIGIN, end_point: Point::ORIGIN, is_controlling: false, control_points: Vec::with_capacity(2), image_handle: image::Handle::from_rgba(width as u32, height as u32, pixels), dirty: false, config, brush_selected: None, } } pub fn view(&self) -> Column<'_, Message> { // 创建显示图像的 Widget let (width, height) = self.canvas.size(); 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(|pos| Message::MousePressed(pos)) // 占位,实际逻辑在 on_drag 或自定义 .on_release(|pos| Message::MouseReleased(pos)) .on_move(|pos| Message::MouseMoved(pos)); // 注意:mouse_area 的 on_move 给出的坐标通常是相对于 widget 左上角的,这正是我们需要的! let canvas_area = container(canvas_area) .width(Length::Fill) .height(Length::Fill) .padding(padding::left(5).top(5).right(5)) .style(|_| container::Style { background: Some(color!(0x808080).into()), ..container::Style::default() }); let mut columns: Vec> = Vec::new(); for i in (0..(Tool::Count as usize)).step_by(2) { let btn1 = 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::from(i))); let btn2 = image_button( format!("image/normal/normal_{:02}.jpg", i + 2), format!("image/selected/selected_{:02}.jpg", i + 2), self.tool_states[i + 1], ) .on_press(Message::ClickTool(Tool::from(i + 1))); columns.push(row![btn1, btn2].into()); } 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()); let grid = Column::from_vec(columns); let tool_area = container(grid) .padding(padding::top(5).left(5).right(5).bottom(10)) .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 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)), ], ] .padding(padding::right(5)), 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)), ], ] .padding(padding::right(5)), pick_list( &BrushKind::ALL[..], self.brush_selected, Message::BrushSelected, ) .placeholder("Brush..."), 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)), ]; debug_area = debug_area.padding(padding::top(10).left(5).bottom(10)); column![ row![ button("CLEAR").on_press(Message::Clear), button("PNG").on_press(Message::SavePNG), ], row![tool_area, canvas_area], debug_area, ] } pub fn update(&mut self, message: Message) -> Task { match self.tool_selected { Tool::Eraser => { self.update_with_eraser(message); } Tool::FillWithColor => { self.update_with_fill_with_color(message); } Tool::Pencil => { self.update_with_pencil(message); } Tool::Brush => { self.update_with_brush(message); } Tool::Airbrush => { self.update_with_airbrush(message); } Tool::Line => { self.update_with_line(message); } Tool::Curve => { self.update_with_curve(message); } Tool::Rectangle => { self.update_with_rectangle(message); } _ => {} } match message { Message::Clear => { // 重置为白色 self.canvas.clear(); self.dirty = true; } 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()); } Message::RefreshImage => { if self.dirty { self.update_image_handle(); self.dirty = false; } } Message::ClickTool(tool) => { self.update_tool_states(tool); } Message::Increment(opt) => { self.config.incr(opt, 1); if opt == ConfigOption::LineWidth { self.canvas.set_line_width(self.config.line_width); } } Message::Decrement(opt) => { 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); } Message::WindowMouseRelease => { // 处理鼠标在 canvas_area 外面释放 self.is_drawing = false; } 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; } } _ => {} } // 如果像素被修改了,我们需要触发一次 RefreshImage 来更新 UI // 在实际复杂应用中,可能需要防抖或异步处理,这里为了实时性直接同步触发 if self.dirty { // 像素变了,安排下一帧刷新图像句柄 // 注意:频繁生成 Handle 可能消耗 CPU,生产环境建议加节流 return Task::perform(async { Message::RefreshImage }, |msg| msg); } Task::none() } pub fn subscription(&self) -> Subscription { let ev = event::listen().filter_map(|event| match event { // 只关心鼠标释放事件 iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { Some(Message::WindowMouseRelease) } _ => None, }); let tick = time::every(milliseconds(50)).map(Message::Tick); Subscription::batch(vec![ev, tick]) } // endregion // region tool update 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; } } _ => {} } } pub fn update_with_fill_with_color(&mut self, message: Message) { match message { Message::MousePressed(pos) => { self.canvas.fill_scanline(pos); self.dirty = true; } _ => {} } } pub fn update_with_pencil(&mut self, message: Message) { match message { Message::MousePressed(pos) => { self.is_drawing = true; self.canvas.draw_pixel_at(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(self.begin_point, pos); self.begin_point = pos; self.dirty = true; } } _ => {} } } 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_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; } } _ => {} } } pub fn update_with_line(&mut self, message: Message) { match message { Message::MousePressed(pos) => { self.is_drawing = true; self.canvas.brush_circle(pos); self.canvas.save_pixels(); 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.restore_pixels(); self.canvas .draw_line_with_circle_brush(self.begin_point, pos); self.dirty = true; } } _ => {} } } 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; } } _ => {} } } pub fn update_with_rectangle(&mut self, message: Message) { match message { Message::MousePressed(pos) => { self.is_drawing = true; self.canvas.save_pixels(); self.begin_point = pos; } Message::MouseReleased(pos) => { self.is_drawing = false; self.begin_point = pos; } Message::MouseMoved(pos) => { if self.is_drawing { self.canvas.restore_pixels(); self.canvas.stroke_rect1(self.begin_point, pos); self.dirty = true; } } _ => {} } } // 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.canvas.get_pixels(); self.image_handle = image::Handle::from_rgba(WIDTH, HEIGHT, data); } } pub fn main() -> iced::Result { iced::application(PaintApp::new, PaintApp::update, PaintApp::view) .theme(Theme::Dark) .subscription(PaintApp::subscription) .run() } fn save_rgba_to_png( rgba_data: Vec, width: u32, height: u32, path: &str, ) -> Result<(), ImageError> { // Each pixel is 4 bytes (R, G, B, A) let img: ImageBuffer, Vec> = ImageBuffer::from_raw(width, height, rgba_data) .expect("Failed to create ImageBuffer: data size mismatch"); img.save(path)?; Ok(()) } fn scale_pixels(pixels: Vec, width: u32, height: u32, scale: u32) -> Vec { 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, 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), } }); }