Compare commits
2 Commits
25c906278c
...
d7f4571217
| Author | SHA1 | Date | |
|---|---|---|---|
| d7f4571217 | |||
| 9337f15da1 |
115
src/mscanvas.rs
115
src/mscanvas.rs
@@ -37,6 +37,20 @@ impl MSColor {
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Path2D {
|
||||
points: Vec<Point>,
|
||||
}
|
||||
|
||||
impl Path2D {
|
||||
pub fn new() -> Self {
|
||||
Self { points: vec![] }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, point: Point) {
|
||||
self.points.push(point);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MSCanvas {
|
||||
width: i32,
|
||||
height: i32,
|
||||
@@ -50,6 +64,8 @@ pub struct MSCanvas {
|
||||
color: MSColor,
|
||||
|
||||
line_width: i32,
|
||||
|
||||
path2d: Path2D,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@@ -62,6 +78,7 @@ impl MSCanvas {
|
||||
pixels_bak: Vec::new(),
|
||||
color: MSColor::BLACK,
|
||||
line_width: 1,
|
||||
path2d: Path2D::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,6 +652,104 @@ impl MSCanvas {
|
||||
let points = vec![begin, p1, p2, end];
|
||||
self.bezier(points);
|
||||
}
|
||||
|
||||
pub fn ellipse(&mut self, center: Point, rx: f32, ry: f32) {
|
||||
let mut x = 0.0;
|
||||
let mut y = ry; // 初始时将y设置为半高
|
||||
|
||||
// 计算初始决策参数
|
||||
let mut decision = ry * ry - rx * rx * ry + rx * rx / 4.0;
|
||||
|
||||
while ry * ry * x < rx * rx * y {
|
||||
// 在每个阶段,根据对称性绘制四个方向上的点
|
||||
self.draw_pixel_at(Point::new(center.x + x, center.y + y));
|
||||
self.draw_pixel_at(Point::new(center.x - x, center.y + y));
|
||||
self.draw_pixel_at(Point::new(center.x + x, center.y - y));
|
||||
self.draw_pixel_at(Point::new(center.x - x, center.y - y));
|
||||
|
||||
if decision < 0.0 {
|
||||
x += 1.0;
|
||||
decision += 2.0 * ry * ry * x + ry * ry;
|
||||
} else {
|
||||
x += 1.0;
|
||||
y -= 1.0;
|
||||
decision += 2.0 * ry * ry * x - 2.0 * rx * rx * y + ry * ry;
|
||||
}
|
||||
}
|
||||
|
||||
decision =
|
||||
ry * ry * (x + 0.5) * (x + 0.5) + rx * rx * (y - 1.0) * (y - 1.0) - rx * rx * ry * ry;
|
||||
|
||||
while y > 0.0 {
|
||||
// 同样地,根据对称性绘制四个方向上的点
|
||||
self.draw_pixel_at(Point::new(center.x + x, center.y + y));
|
||||
self.draw_pixel_at(Point::new(center.x - x, center.y + y));
|
||||
self.draw_pixel_at(Point::new(center.x + x, center.y - y));
|
||||
self.draw_pixel_at(Point::new(center.x - x, center.y - y));
|
||||
|
||||
if decision > 0.0 {
|
||||
y -= 1.0;
|
||||
decision += rx * rx - 2.0 * rx * rx * y;
|
||||
} else {
|
||||
x += 1.0;
|
||||
y -= 1.0;
|
||||
decision += 2.0 * ry * ry * x - 2.0 * rx * rx * y + rx * rx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ellipse1(&mut self, p1: Point, p2: Point) {
|
||||
let mut rx = (p2.x - p1.x) / 2.0;
|
||||
let mut ry = (p2.y - p1.y) / 2.0;
|
||||
if rx > 0.0 && ry > 0.0 {
|
||||
let center = Point::new(p1.x + rx, p1.y + ry);
|
||||
self.ellipse(center, rx, ry);
|
||||
} else if rx < 0.0 && ry < 0.0 {
|
||||
rx = -rx;
|
||||
ry = -ry;
|
||||
let center = Point::new(p1.x - rx, p1.y - ry);
|
||||
self.ellipse(center, rx, ry);
|
||||
} else if ry < 0.0 {
|
||||
ry = -ry;
|
||||
let center = Point::new(p1.x + rx, p1.y - ry);
|
||||
self.ellipse(center, rx, ry);
|
||||
} else {
|
||||
rx = -rx;
|
||||
let center = Point::new(p1.x - rx, p1.y + ry);
|
||||
self.ellipse(center, rx, ry);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_path(&mut self) {
|
||||
self.path2d = Path2D::new();
|
||||
}
|
||||
|
||||
pub fn close_path(&mut self) {
|
||||
let points = &self.path2d.points;
|
||||
if points.len() < 3 {
|
||||
return;
|
||||
}
|
||||
self.path2d.push(points[0]);
|
||||
}
|
||||
|
||||
pub fn move_to(&mut self, point: Point) {
|
||||
self.path2d.push(point);
|
||||
}
|
||||
|
||||
pub fn line_to(&mut self, point: Point) {
|
||||
self.path2d.push(point);
|
||||
}
|
||||
|
||||
pub fn stroke(&mut self) {
|
||||
let mut points = self.path2d.points.clone();
|
||||
if points.len() < 2 {
|
||||
return;
|
||||
}
|
||||
// 连线
|
||||
for i in 0..(points.len() - 1) {
|
||||
self.draw_line_with_circle_brush(points[i], points[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn point_muln(point: Point, t: f32) -> Point {
|
||||
|
||||
121
src/paint.rs
121
src/paint.rs
@@ -53,6 +53,7 @@ enum Message {
|
||||
MousePressed(Point),
|
||||
MouseReleased(Point),
|
||||
MouseMoved(Point),
|
||||
MouseDoubleClick(Point),
|
||||
|
||||
Clear,
|
||||
SavePNG,
|
||||
@@ -94,6 +95,34 @@ enum Tool {
|
||||
Count,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Tool {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Tool::FreeFormSelect => "FreeFormSelect",
|
||||
Tool::Select => "Select",
|
||||
Tool::Eraser => "Eraser",
|
||||
Tool::FillWithColor => "FillWithColor",
|
||||
Tool::PickColor => "PickColor",
|
||||
Tool::Magnifier => "Magnifier",
|
||||
Tool::Pencil => "Pencil",
|
||||
Tool::Brush => "Brush",
|
||||
Tool::Airbrush => "Airbrush",
|
||||
Tool::Text => "Text",
|
||||
Tool::Line => "Line",
|
||||
Tool::Curve => "Curve",
|
||||
Tool::Rectangle => "Rectangle",
|
||||
Tool::Polygon => "Polygon",
|
||||
Tool::Ellipse => "Ellipse",
|
||||
Tool::RoundedRectangle => "RoundedRectangle",
|
||||
Tool::Count => "Count",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Tool {
|
||||
fn from(tool: usize) -> Self {
|
||||
match tool {
|
||||
@@ -219,6 +248,8 @@ struct PaintApp {
|
||||
is_controlling: bool,
|
||||
control_points: Vec<Point>,
|
||||
|
||||
is_path_beginning: bool,
|
||||
|
||||
/// 用于显示的图像句柄缓存
|
||||
/// 每次像素变化后需要重新生成
|
||||
image_handle: image::Handle,
|
||||
@@ -249,6 +280,7 @@ impl PaintApp {
|
||||
end_point: Point::ORIGIN,
|
||||
is_controlling: false,
|
||||
control_points: Vec::with_capacity(2),
|
||||
is_path_beginning: false,
|
||||
image_handle: image::Handle::from_rgba(width as u32, height as u32, pixels),
|
||||
dirty: false,
|
||||
config,
|
||||
@@ -266,7 +298,8 @@ impl PaintApp {
|
||||
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));
|
||||
.on_move(|pos| Message::MouseMoved(pos))
|
||||
.on_double_click(|pos| Message::MouseDoubleClick(pos));
|
||||
// 注意:mouse_area 的 on_move 给出的坐标通常是相对于 widget 左上角的,这正是我们需要的!
|
||||
let canvas_area = container(canvas_area)
|
||||
.width(Length::Fill)
|
||||
@@ -293,7 +326,10 @@ impl PaintApp {
|
||||
.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 tool_config_area = container(text(format!("{}", self.tool_selected)))
|
||||
.width(90)
|
||||
.height(200)
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Style {
|
||||
@@ -408,6 +444,12 @@ impl PaintApp {
|
||||
Tool::Rectangle => {
|
||||
self.update_with_rectangle(message);
|
||||
}
|
||||
Tool::Polygon => {
|
||||
self.update_with_polygon(message);
|
||||
}
|
||||
Tool::Ellipse => {
|
||||
self.update_with_ellipse(message);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -712,6 +754,71 @@ impl PaintApp {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_with_polygon(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::MousePressed(pos) => {
|
||||
if self.is_path_beginning {
|
||||
self.is_drawing = true;
|
||||
self.canvas.restore_pixels();
|
||||
self.canvas.stroke();
|
||||
self.canvas
|
||||
.draw_line_with_circle_brush(self.begin_point, pos);
|
||||
self.dirty = true;
|
||||
} else {
|
||||
self.is_path_beginning = true;
|
||||
self.is_drawing = true;
|
||||
self.canvas.save_pixels();
|
||||
self.begin_point = pos;
|
||||
self.canvas.begin_path();
|
||||
self.canvas.move_to(pos);
|
||||
}
|
||||
}
|
||||
Message::MouseReleased(pos) => {
|
||||
self.canvas.line_to(pos);
|
||||
self.begin_point = pos;
|
||||
}
|
||||
Message::MouseMoved(pos) => {
|
||||
if self.is_drawing {
|
||||
self.canvas.restore_pixels();
|
||||
self.canvas.stroke();
|
||||
self.canvas
|
||||
.draw_line_with_circle_brush(self.begin_point, pos);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
Message::MouseDoubleClick(_pos) => {
|
||||
self.canvas.close_path();
|
||||
self.canvas.stroke();
|
||||
self.is_drawing = false;
|
||||
self.is_path_beginning = false;
|
||||
self.dirty = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_with_ellipse(&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.ellipse1(self.begin_point, pos);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
pub fn update_tool_states(&mut self, tool: Tool) {
|
||||
@@ -719,12 +826,22 @@ impl PaintApp {
|
||||
if idx >= self.tool_states.len() {
|
||||
return;
|
||||
}
|
||||
if self.tool_selected == Tool::Polygon {
|
||||
// 切换到其他工具,闭合路径
|
||||
self.canvas.close_path();
|
||||
self.canvas.stroke();
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
self.is_drawing = false;
|
||||
self.is_path_beginning = false;
|
||||
}
|
||||
|
||||
/// 将原始字节转换为 Iced 的图像句柄
|
||||
|
||||
Reference in New Issue
Block a user