Compare commits
2 Commits
da99cfc765
...
6f6e6f326b
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f6e6f326b | |||
| 1f8a2859f9 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1953,6 +1953,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"iced",
|
"iced",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
"image",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
iced = {version = "0.14.0", features = ["advanced", "image"]}
|
iced = {version = "0.14.0", features = ["advanced", "image"]}
|
||||||
iced_core = "0.14.0"
|
iced_core = "0.14.0"
|
||||||
|
image = "0.25.9"
|
||||||
|
|||||||
224
src/mscanvas.rs
224
src/mscanvas.rs
@@ -40,16 +40,15 @@ pub struct MSCanvas {
|
|||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
|
|
||||||
// 原始像素数据:RGBA 格式
|
/// 原始像素数据:RGBA 格式
|
||||||
// 长度 = width * height * 4
|
/// 长度 = width * height * 4
|
||||||
pixels: Vec<u8>,
|
pixels: Vec<u8>,
|
||||||
pixels_bak: Vec<u8>,
|
pixels_bak: Vec<u8>,
|
||||||
|
|
||||||
// 当前笔画颜色
|
/// 当前笔画颜色
|
||||||
color: MSColor,
|
color: MSColor,
|
||||||
|
|
||||||
// brush 大小
|
line_width: i32,
|
||||||
brush_radius: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@@ -61,7 +60,7 @@ impl MSCanvas {
|
|||||||
pixels: vec![255; (width * height * 4) as usize],
|
pixels: vec![255; (width * height * 4) as usize],
|
||||||
pixels_bak: Vec::new(),
|
pixels_bak: Vec::new(),
|
||||||
color: MSColor::BLACK,
|
color: MSColor::BLACK,
|
||||||
brush_radius: 0.5,
|
line_width: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,17 +68,59 @@ impl MSCanvas {
|
|||||||
self.color = color;
|
self.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_brush_radius(&mut self, brush_radius: f32) {
|
pub fn set_line_width(&mut self, line_width: i32) {
|
||||||
self.brush_radius = brush_radius;
|
self.line_width = line_width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
impl MSCanvas {
|
impl MSCanvas {
|
||||||
|
pub fn size(&self) -> (i32, i32) {
|
||||||
|
(self.width, self.height)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_pixels(&self) -> Vec<u8> {
|
pub fn get_pixels(&self) -> Vec<u8> {
|
||||||
self.pixels.clone()
|
self.pixels.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_pixels_scale(&self, scale: i32) -> Vec<u8> {
|
||||||
|
if scale <= 1 {
|
||||||
|
return self.pixels.clone();
|
||||||
|
}
|
||||||
|
let dst_width = self.width * scale;
|
||||||
|
let dst_height = self.width * scale;
|
||||||
|
let mut dst = vec![0; (self.width * self.width * 4) as usize]; // RGBA
|
||||||
|
|
||||||
|
for y in 0..self.height {
|
||||||
|
for x in 0..self.width {
|
||||||
|
// 源像素索引
|
||||||
|
let src_idx = ((y * self.width + x) * 4) as usize;
|
||||||
|
|
||||||
|
// 源像素颜色
|
||||||
|
let r = self.pixels[src_idx];
|
||||||
|
let g = self.pixels[src_idx + 1];
|
||||||
|
let b = self.pixels[src_idx + 2];
|
||||||
|
let a = self.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
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pixel_at(&self, x: i32, y: i32) -> MSColor {
|
pub fn pixel_at(&self, x: i32, y: i32) -> MSColor {
|
||||||
// 边界检查
|
// 边界检查
|
||||||
if x < 0 || x >= self.width || y < 0 || y >= self.height as i32 {
|
if x < 0 || x >= self.width || y < 0 || y >= self.height as i32 {
|
||||||
@@ -115,6 +156,25 @@ impl MSCanvas {
|
|||||||
self.pixels[index + 3] = self.color.a; // A
|
self.pixels[index + 3] = self.color.a; // A
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_pixel_color_at(&mut self, point: Point, color: MSColor) {
|
||||||
|
let Point { x, y } = point;
|
||||||
|
let x = x as i32;
|
||||||
|
let y = y as i32;
|
||||||
|
// 边界检查
|
||||||
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 计算索引:(y * width + x) * 4
|
||||||
|
let index = ((y * self.width + x) * 4) as usize;
|
||||||
|
|
||||||
|
// 写入 RGBA 数据
|
||||||
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||||
|
self.pixels[index] = color.r; // R
|
||||||
|
self.pixels[index + 1] = color.g; // G
|
||||||
|
self.pixels[index + 2] = color.b; // B
|
||||||
|
self.pixels[index + 3] = color.a; // A
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_pixel_at1(&mut self, x: i32, y: i32) {
|
fn draw_pixel_at1(&mut self, x: i32, y: i32) {
|
||||||
// 边界检查
|
// 边界检查
|
||||||
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||||
@@ -157,15 +217,27 @@ impl MSCanvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_brush_at(&mut self, center: Point) {
|
pub fn draw_cross_color(&mut self, point: Point, color: MSColor) {
|
||||||
if self.brush_radius < 1.0 {
|
let Point { x, y } = point;
|
||||||
|
let r = 5;
|
||||||
|
for dy in -r..=r {
|
||||||
|
self.draw_pixel_color_at(Point::new(point.x, point.y + dy as f32), color);
|
||||||
|
}
|
||||||
|
for dx in -r..=r {
|
||||||
|
self.draw_pixel_color_at(Point::new(point.x + dx as f32, point.y), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brush_circle(&mut self, center: Point) {
|
||||||
|
if self.line_width <= 1 {
|
||||||
self.draw_pixel_at(center);
|
self.draw_pixel_at(center);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let square = self.brush_radius * self.brush_radius;
|
let square = (self.line_width as f32) * (self.line_width as f32) / 4.0;
|
||||||
let r = self.brush_radius.floor() as i32;
|
let l = self.line_width / 2;
|
||||||
for dy in -r..=r {
|
let r = self.line_width - l;
|
||||||
for dx in -r..=r {
|
for dy in -l..r {
|
||||||
|
for dx in -l..r {
|
||||||
if (dx * dx + dy * dy) as f32 <= square {
|
if (dx * dx + dy * dy) as f32 <= square {
|
||||||
self.draw_pixel_at(Point::new(center.x + dx as f32, center.y + dy as f32));
|
self.draw_pixel_at(Point::new(center.x + dx as f32, center.y + dy as f32));
|
||||||
}
|
}
|
||||||
@@ -173,6 +245,41 @@ impl MSCanvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn brush_square(&mut self, center: Point) {
|
||||||
|
if self.line_width <= 1 {
|
||||||
|
self.draw_pixel_at(center);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let l = self.line_width / 2;
|
||||||
|
let r = self.line_width - l;
|
||||||
|
for dy in -l..r {
|
||||||
|
for dx in -l..r {
|
||||||
|
self.draw_pixel_at(Point::new(center.x + dx as f32, center.y + dy as f32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brush_slash(&mut self, center: Point) {
|
||||||
|
let r = self.line_width / 2;
|
||||||
|
let l = self.line_width - r;
|
||||||
|
// for dx in -1..1 {
|
||||||
|
// for d in -l..r {
|
||||||
|
// self.draw_pixel_at(Point::new(center.x - (d as f32) + (dx as f32), center.y - d as f32));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
for d in -l..r {
|
||||||
|
self.draw_pixel_at(Point::new(center.x - (d as f32), center.y - d as f32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brush_backslash(&mut self, center: Point) {
|
||||||
|
let r = self.line_width / 2;
|
||||||
|
let l = self.line_width - r;
|
||||||
|
for d in -l..r {
|
||||||
|
self.draw_pixel_at(Point::new(center.x + d as f32, center.y - d as f32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn bresenham_line(&mut self, begin: Point, end: Point) -> Vec<Point> {
|
fn bresenham_line(&mut self, begin: Point, end: Point) -> Vec<Point> {
|
||||||
let x1 = begin.x;
|
let x1 = begin.x;
|
||||||
let y1 = begin.y;
|
let y1 = begin.y;
|
||||||
@@ -259,10 +366,25 @@ impl MSCanvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_line_thick(&mut self, begin: Point, end: Point) {
|
pub fn draw_line_with_circle_brush(&mut self, begin: Point, end: Point) {
|
||||||
|
// let points = self.bresenham_line(begin, end);
|
||||||
|
// for point in points {
|
||||||
|
// self.draw_brush_at(point);
|
||||||
|
// }
|
||||||
|
self.draw_line_with(begin, end, MSCanvas::brush_circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_line_with_square_brush(&mut self, begin: Point, end: Point) {
|
||||||
|
self.draw_line_with(begin, end, MSCanvas::brush_square);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_line_with<F>(&mut self, begin: Point, end: Point, brush_fn: F)
|
||||||
|
where
|
||||||
|
F: Fn(&mut MSCanvas, Point) -> (),
|
||||||
|
{
|
||||||
let points = self.bresenham_line(begin, end);
|
let points = self.bresenham_line(begin, end);
|
||||||
for point in points {
|
for point in points {
|
||||||
self.draw_brush_at(point);
|
brush_fn(self, point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,22 +509,70 @@ impl MSCanvas {
|
|||||||
(iter_count, fill_count)
|
(iter_count, fill_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stroke_rect(&mut self, x: f32, y: f32, width: f32, height: f32) {
|
pub fn clear_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
|
||||||
self.draw_line_thick(Point::new(x, y), Point::new(x, y + height));
|
for yi in y..(y + height) {
|
||||||
self.draw_line_thick(Point::new(x, y + height), Point::new(x + width, y + height));
|
self.clear_row(x, x + width, yi);
|
||||||
self.draw_line_thick(Point::new(x + width, y + height), Point::new(x + width, y));
|
}
|
||||||
self.draw_line_thick(Point::new(x, y), Point::new(x + width, y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stroke_rect1(&mut self, left_top: Point, right_bottom: Point) {
|
pub fn fill_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
|
||||||
let x = left_top.x;
|
for yi in y..(y + height) {
|
||||||
let y = left_top.y;
|
self.draw_pixel_row(x, x + width, yi);
|
||||||
let width = (right_bottom.x - left_top.x);
|
}
|
||||||
let height = (right_bottom.y - left_top.y);
|
}
|
||||||
self.stroke_rect(x, y, width, height);
|
|
||||||
|
pub fn stroke_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
|
||||||
|
if width < 2 * self.line_width || height < 2 * self.line_width {
|
||||||
|
self.fill_rect(x, y, width, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.fill_rect(x, y, width, self.line_width);
|
||||||
|
self.fill_rect(x, y + height - self.line_width, width, self.line_width);
|
||||||
|
self.fill_rect(x, y, self.line_width, height);
|
||||||
|
self.fill_rect(x + width - self.line_width, y, self.line_width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stroke_rect1(&mut self, p1: Point, p2: Point) {
|
||||||
|
self.draw_cross_color(p1, MSColor::new(255, 0, 0, 255));
|
||||||
|
let mut x = p1.x;
|
||||||
|
let mut y = p1.y;
|
||||||
|
let mut width = (p2.x - p1.x);
|
||||||
|
let mut height = (p2.y - p1.y);
|
||||||
|
if width < 0.0 && height < 0.0 {
|
||||||
|
x = p2.x;
|
||||||
|
y = p2.y;
|
||||||
|
width = (p1.x - p2.x);
|
||||||
|
height = (p1.y - p2.y);
|
||||||
|
} else if width < 0.0 {
|
||||||
|
x += width;
|
||||||
|
width = -width;
|
||||||
|
} else if height < 0.0 {
|
||||||
|
y += height;
|
||||||
|
height = -height;
|
||||||
|
}
|
||||||
|
self.stroke_rect(x as i32, y as i32, width as i32, height as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.pixels.fill(255);
|
self.pixels.fill(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_row(&mut self, xs: i32, xe: i32, y: i32) {
|
||||||
|
if y < 0 || y >= self.height {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let xs = xs.clamp(0, self.width - 1);
|
||||||
|
let xe = xe.clamp(0, self.width as i32);
|
||||||
|
for x in xs..xe {
|
||||||
|
let index = ((y * self.width + x) * 4) as usize;
|
||||||
|
|
||||||
|
// 写入 RGBA 数据
|
||||||
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||||
|
self.pixels[index] = 255; // R
|
||||||
|
self.pixels[index + 1] = 255; // G
|
||||||
|
self.pixels[index + 2] = 255; // B
|
||||||
|
self.pixels[index + 3] = 255; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
280
src/paint.rs
280
src/paint.rs
@@ -1,34 +1,72 @@
|
|||||||
use crate::image_button::image_button;
|
use ::image::RgbaImage;
|
||||||
use crate::mouse_area::mouse_area;
|
|
||||||
use iced::Theme;
|
use iced::Theme;
|
||||||
use iced::padding;
|
use iced::padding;
|
||||||
use iced::widget::container;
|
use iced::widget::container;
|
||||||
use iced::widget::{Column, button, column, image, 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_core::color;
|
||||||
|
|
||||||
|
use crate::image_button::image_button;
|
||||||
|
use crate::mouse_area::mouse_area;
|
||||||
use crate::mscanvas::MSCanvas;
|
use crate::mscanvas::MSCanvas;
|
||||||
|
|
||||||
const WIDTH: u32 = 800;
|
const WIDTH: u32 = 800;
|
||||||
const HEIGHT: u32 = 600;
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Message {
|
enum Message {
|
||||||
MousePressed(Point),
|
MousePressed(Point),
|
||||||
MouseReleased(Point),
|
MouseReleased(Point),
|
||||||
MouseMoved(Point),
|
MouseMoved(Point),
|
||||||
|
|
||||||
Clear,
|
Clear,
|
||||||
|
SavePNG,
|
||||||
// 内部消息:请求刷新图像
|
// 内部消息:请求刷新图像
|
||||||
RefreshImage,
|
RefreshImage,
|
||||||
|
|
||||||
ClickTool(Tool),
|
ClickTool(Tool),
|
||||||
|
|
||||||
Increment,
|
Increment(ConfigOption),
|
||||||
Decrement,
|
Decrement(ConfigOption),
|
||||||
|
|
||||||
|
BrushSelected(BrushKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Tool {
|
enum Tool {
|
||||||
FreeFormSelect,
|
FreeFormSelect,
|
||||||
Select,
|
Select,
|
||||||
Eraser,
|
Eraser,
|
||||||
@@ -97,6 +135,47 @@ impl From<Tool> for usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum ConfigOption {
|
||||||
|
EraserWidth,
|
||||||
|
LineWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Config {
|
||||||
|
eraser_width: i32,
|
||||||
|
line_width: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::DEFAULT.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
const DEFAULT: Config = Config {
|
||||||
|
line_width: 1,
|
||||||
|
eraser_width: 4,
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct PaintApp {
|
struct PaintApp {
|
||||||
tool_states: [bool; Tool::Count as usize],
|
tool_states: [bool; Tool::Count as usize],
|
||||||
tool_selected: Tool,
|
tool_selected: Tool,
|
||||||
@@ -115,34 +194,39 @@ struct PaintApp {
|
|||||||
// 标记像素是否被修改,用于优化图像句柄的生成
|
// 标记像素是否被修改,用于优化图像句柄的生成
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
|
|
||||||
value: i32,
|
config: Config,
|
||||||
|
|
||||||
|
brush_selected: Option<BrushKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaintApp {
|
impl PaintApp {
|
||||||
// region iced application (new view update)
|
// region iced application (new view update)
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
|
let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
|
||||||
|
let (width, height) = canvas.size();
|
||||||
let pixels = canvas.get_pixels();
|
let pixels = canvas.get_pixels();
|
||||||
|
let config = Config::default();
|
||||||
|
canvas.set_line_width(config.line_width);
|
||||||
Self {
|
Self {
|
||||||
tool_states: [false; Tool::Count as usize],
|
tool_states: [false; Tool::Count as usize],
|
||||||
tool_selected: Tool::Count,
|
tool_selected: Tool::Count,
|
||||||
canvas,
|
canvas,
|
||||||
is_drawing: false,
|
is_drawing: false,
|
||||||
begin_point: Point::ORIGIN,
|
begin_point: Point::ORIGIN,
|
||||||
image_handle: image::Handle::from_rgba(WIDTH, HEIGHT, pixels),
|
image_handle: image::Handle::from_rgba(width as u32, height as u32, pixels),
|
||||||
dirty: false,
|
dirty: false,
|
||||||
|
config,
|
||||||
value: 1,
|
brush_selected: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> Column<'_, Message> {
|
pub fn view(&self) -> Column<'_, Message> {
|
||||||
// 创建显示图像的 Widget
|
// 创建显示图像的 Widget
|
||||||
// 如果 handle 还没准备好,显示一个占位符
|
let (width, height) = self.canvas.size();
|
||||||
let image_widget = image(self.image_handle.clone())
|
let image_widget = image(self.image_handle.clone())
|
||||||
.width(Length::Fixed(WIDTH as f32))
|
.width(Length::Fixed(width as f32))
|
||||||
.height(Length::Fixed(HEIGHT as f32));
|
.height(Length::Fixed(height as f32));
|
||||||
|
|
||||||
let canvas_area = mouse_area(image_widget)
|
let canvas_area = mouse_area(image_widget)
|
||||||
.on_press(|pos| Message::MousePressed(pos)) // 占位,实际逻辑在 on_drag 或自定义
|
.on_press(|pos| Message::MousePressed(pos)) // 占位,实际逻辑在 on_drag 或自定义
|
||||||
@@ -152,7 +236,7 @@ impl PaintApp {
|
|||||||
let canvas_area = container(canvas_area)
|
let canvas_area = container(canvas_area)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.padding(padding::left(5).top(5))
|
.padding(padding::left(5).top(5).right(5))
|
||||||
.style(|_| container::Style {
|
.style(|_| container::Style {
|
||||||
background: Some(color!(0x808080).into()),
|
background: Some(color!(0x808080).into()),
|
||||||
..container::Style::default()
|
..container::Style::default()
|
||||||
@@ -207,17 +291,37 @@ impl PaintApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let debug_area = column![
|
let mut debug_area = row![
|
||||||
text("brush"),
|
column![
|
||||||
row![
|
text("Eraser Width "),
|
||||||
button("+").on_press(Message::Increment),
|
row![
|
||||||
text(self.value),
|
button("+").on_press(Message::Increment(ConfigOption::EraserWidth)),
|
||||||
button("-").on_press(Message::Decrement),
|
text(self.config.eraser_width).size(20).center(),
|
||||||
]
|
button("-").on_press(Message::Decrement(ConfigOption::EraserWidth)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
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[..],
|
||||||
|
self.brush_selected,
|
||||||
|
Message::BrushSelected,
|
||||||
|
)
|
||||||
|
.placeholder("Brush..."),
|
||||||
];
|
];
|
||||||
|
debug_area = debug_area.padding(padding::top(10).left(5).bottom(10));
|
||||||
|
|
||||||
column![
|
column![
|
||||||
button("CLEAR").on_press(Message::Clear),
|
row![
|
||||||
|
button("CLEAR").on_press(Message::Clear),
|
||||||
|
button("PNG").on_press(Message::SavePNG),
|
||||||
|
],
|
||||||
row![tool_area, canvas_area],
|
row![tool_area, canvas_area],
|
||||||
debug_area,
|
debug_area,
|
||||||
]
|
]
|
||||||
@@ -225,12 +329,18 @@ impl PaintApp {
|
|||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
match self.tool_selected {
|
match self.tool_selected {
|
||||||
|
Tool::Eraser => {
|
||||||
|
self.update_with_eraser(message);
|
||||||
|
}
|
||||||
Tool::FillWithColor => {
|
Tool::FillWithColor => {
|
||||||
self.update_with_fill_with_color(message);
|
self.update_with_fill_with_color(message);
|
||||||
}
|
}
|
||||||
Tool::Pencil => {
|
Tool::Pencil => {
|
||||||
self.update_with_pencil(message);
|
self.update_with_pencil(message);
|
||||||
}
|
}
|
||||||
|
Tool::Brush => {
|
||||||
|
self.update_with_brush(message);
|
||||||
|
}
|
||||||
Tool::Line => {
|
Tool::Line => {
|
||||||
self.update_with_line(message);
|
self.update_with_line(message);
|
||||||
}
|
}
|
||||||
@@ -246,6 +356,18 @@ impl PaintApp {
|
|||||||
self.canvas.clear();
|
self.canvas.clear();
|
||||||
self.dirty = true;
|
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 => {
|
Message::RefreshImage => {
|
||||||
if self.dirty {
|
if self.dirty {
|
||||||
self.update_image_handle();
|
self.update_image_handle();
|
||||||
@@ -255,16 +377,20 @@ impl PaintApp {
|
|||||||
Message::ClickTool(tool) => {
|
Message::ClickTool(tool) => {
|
||||||
self.update_tool_states(tool);
|
self.update_tool_states(tool);
|
||||||
}
|
}
|
||||||
Message::Increment => {
|
Message::Increment(opt) => {
|
||||||
self.value = self.value + 1;
|
self.config.incr(opt, 1);
|
||||||
self.canvas.set_brush_radius(self.value as f32 / 2.0);
|
if opt == ConfigOption::LineWidth {
|
||||||
}
|
self.canvas.set_line_width(self.config.line_width);
|
||||||
Message::Decrement => {
|
|
||||||
self.value = self.value - 1;
|
|
||||||
if self.value < 1 {
|
|
||||||
self.value = 1;
|
|
||||||
}
|
}
|
||||||
self.canvas.set_brush_radius(self.value as f32 / 2.0);
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -284,6 +410,36 @@ impl PaintApp {
|
|||||||
|
|
||||||
// region tool update
|
// 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) {
|
pub fn update_with_fill_with_color(&mut self, message: Message) {
|
||||||
match message {
|
match message {
|
||||||
Message::MousePressed(pos) => {
|
Message::MousePressed(pos) => {
|
||||||
@@ -316,11 +472,43 @@ 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) {
|
pub fn update_with_line(&mut self, message: Message) {
|
||||||
match message {
|
match message {
|
||||||
Message::MousePressed(pos) => {
|
Message::MousePressed(pos) => {
|
||||||
self.is_drawing = true;
|
self.is_drawing = true;
|
||||||
self.canvas.draw_brush_at(pos);
|
self.canvas.brush_circle(pos);
|
||||||
self.canvas.save_pixels();
|
self.canvas.save_pixels();
|
||||||
self.begin_point = pos;
|
self.begin_point = pos;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
@@ -332,7 +520,8 @@ impl PaintApp {
|
|||||||
Message::MouseMoved(pos) => {
|
Message::MouseMoved(pos) => {
|
||||||
if self.is_drawing {
|
if self.is_drawing {
|
||||||
self.canvas.restore_pixels();
|
self.canvas.restore_pixels();
|
||||||
self.canvas.draw_line_thick(self.begin_point, pos);
|
self.canvas
|
||||||
|
.draw_line_with_circle_brush(self.begin_point, pos);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,3 +581,24 @@ pub fn main() -> iced::Result {
|
|||||||
.theme(Theme::Dark)
|
.theme(Theme::Dark)
|
||||||
.run()
|
.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(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user