Implement fill with color
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
mod image_button;
|
mod image_button;
|
||||||
mod paint;
|
|
||||||
mod mouse_area;
|
mod mouse_area;
|
||||||
|
mod paint;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
paint::main()
|
paint::main()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! code copy from: https://github.com/airstrike/sweeten/blob/master/src/widget/mouse_area.rs
|
||||||
//! A container for capturing mouse events.
|
//! A container for capturing mouse events.
|
||||||
//!
|
//!
|
||||||
//! This is a sweetened version of `iced`'s [`MouseArea`] where all event
|
//! This is a sweetened version of `iced`'s [`MouseArea`] where all event
|
||||||
@@ -31,17 +32,11 @@ use iced_core::renderer;
|
|||||||
use iced_core::touch;
|
use iced_core::touch;
|
||||||
use iced_core::widget::{Operation, Tree, tree};
|
use iced_core::widget::{Operation, Tree, tree};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size,
|
Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||||
Vector, Widget,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Emit messages on mouse events.
|
/// Emit messages on mouse events.
|
||||||
pub struct MouseArea<
|
pub struct MouseArea<'a, Message, Theme = iced_core::Theme, Renderer = iced::Renderer> {
|
||||||
'a,
|
|
||||||
Message,
|
|
||||||
Theme = iced_core::Theme,
|
|
||||||
Renderer = iced::Renderer,
|
|
||||||
> {
|
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
on_press: Option<Box<dyn Fn(Point) -> Message + 'a>>,
|
on_press: Option<Box<dyn Fn(Point) -> Message + 'a>>,
|
||||||
on_release: Option<Box<dyn Fn(Point) -> Message + 'a>>,
|
on_release: Option<Box<dyn Fn(Point) -> Message + 'a>>,
|
||||||
@@ -71,10 +66,7 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
|||||||
///
|
///
|
||||||
/// The closure receives the click position as a [`Point`].
|
/// The closure receives the click position as a [`Point`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_press_maybe(
|
pub fn on_press_maybe(mut self, f: Option<impl Fn(Point) -> Message + 'a>) -> Self {
|
||||||
mut self,
|
|
||||||
f: Option<impl Fn(Point) -> Message + 'a>,
|
|
||||||
) -> Self {
|
|
||||||
self.on_press = f.map(|f| Box::new(f) as _);
|
self.on_press = f.map(|f| Box::new(f) as _);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -101,10 +93,7 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
|||||||
/// [`on_press`]: Self::on_press
|
/// [`on_press`]: Self::on_press
|
||||||
/// [`on_release`]: Self::on_release
|
/// [`on_release`]: Self::on_release
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_double_click(
|
pub fn on_double_click(mut self, f: impl Fn(Point) -> Message + 'a) -> Self {
|
||||||
mut self,
|
|
||||||
f: impl Fn(Point) -> Message + 'a,
|
|
||||||
) -> Self {
|
|
||||||
self.on_double_click = Some(Box::new(f));
|
self.on_double_click = Some(Box::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -122,10 +111,7 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
|||||||
///
|
///
|
||||||
/// The closure receives the release position as a [`Point`].
|
/// The closure receives the release position as a [`Point`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_right_release(
|
pub fn on_right_release(mut self, f: impl Fn(Point) -> Message + 'a) -> Self {
|
||||||
mut self,
|
|
||||||
f: impl Fn(Point) -> Message + 'a,
|
|
||||||
) -> Self {
|
|
||||||
self.on_right_release = Some(Box::new(f));
|
self.on_right_release = Some(Box::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -134,10 +120,7 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
|||||||
///
|
///
|
||||||
/// The closure receives the click position as a [`Point`].
|
/// The closure receives the click position as a [`Point`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_middle_press(
|
pub fn on_middle_press(mut self, f: impl Fn(Point) -> Message + 'a) -> Self {
|
||||||
mut self,
|
|
||||||
f: impl Fn(Point) -> Message + 'a,
|
|
||||||
) -> Self {
|
|
||||||
self.on_middle_press = Some(Box::new(f));
|
self.on_middle_press = Some(Box::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -146,20 +129,14 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
|||||||
///
|
///
|
||||||
/// The closure receives the release position as a [`Point`].
|
/// The closure receives the release position as a [`Point`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_middle_release(
|
pub fn on_middle_release(mut self, f: impl Fn(Point) -> Message + 'a) -> Self {
|
||||||
mut self,
|
|
||||||
f: impl Fn(Point) -> Message + 'a,
|
|
||||||
) -> Self {
|
|
||||||
self.on_middle_release = Some(Box::new(f));
|
self.on_middle_release = Some(Box::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the message to emit when the scroll wheel is used.
|
/// Sets the message to emit when the scroll wheel is used.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn on_scroll(
|
pub fn on_scroll(mut self, on_scroll: impl Fn(mouse::ScrollDelta) -> Message + 'a) -> Self {
|
||||||
mut self,
|
|
||||||
on_scroll: impl Fn(mouse::ScrollDelta) -> Message + 'a,
|
|
||||||
) -> Self {
|
|
||||||
self.on_scroll = Some(Box::new(on_scroll));
|
self.on_scroll = Some(Box::new(on_scroll));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -210,9 +187,7 @@ struct State {
|
|||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
|
||||||
/// Creates a [`MouseArea`] with the given content.
|
/// Creates a [`MouseArea`] with the given content.
|
||||||
pub fn new(
|
pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
|
||||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
|
||||||
) -> Self {
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
on_press: None,
|
on_press: None,
|
||||||
@@ -262,11 +237,9 @@ where
|
|||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.content.as_widget_mut().layout(
|
self.content
|
||||||
&mut tree.children[0],
|
.as_widget_mut()
|
||||||
renderer,
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
limits,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
@@ -276,12 +249,9 @@ where
|
|||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn Operation,
|
operation: &mut dyn Operation,
|
||||||
) {
|
) {
|
||||||
self.content.as_widget_mut().operate(
|
self.content
|
||||||
&mut tree.children[0],
|
.as_widget_mut()
|
||||||
layout,
|
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||||
renderer,
|
|
||||||
operation,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
@@ -330,9 +300,7 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
match (self.interaction, content_interaction) {
|
match (self.interaction, content_interaction) {
|
||||||
(Some(interaction), mouse::Interaction::None)
|
(Some(interaction), mouse::Interaction::None) if cursor.is_over(layout.bounds()) => {
|
||||||
if cursor.is_over(layout.bounds()) =>
|
|
||||||
{
|
|
||||||
interaction
|
interaction
|
||||||
}
|
}
|
||||||
_ => content_interaction,
|
_ => content_interaction,
|
||||||
@@ -451,11 +419,8 @@ fn update<Message, Theme, Renderer>(
|
|||||||
if let Some(position) = cursor.position_in(layout.bounds())
|
if let Some(position) = cursor.position_in(layout.bounds())
|
||||||
&& let Some(on_double_click) = widget.on_double_click.as_ref()
|
&& let Some(on_double_click) = widget.on_double_click.as_ref()
|
||||||
{
|
{
|
||||||
let new_click = mouse::Click::new(
|
let new_click =
|
||||||
position,
|
mouse::Click::new(position, mouse::Button::Left, state.previous_click);
|
||||||
mouse::Button::Left,
|
|
||||||
state.previous_click,
|
|
||||||
);
|
|
||||||
|
|
||||||
if new_click.kind() == mouse::click::Kind::Double {
|
if new_click.kind() == mouse::click::Kind::Double {
|
||||||
shell.publish(on_double_click(position));
|
shell.publish(on_double_click(position));
|
||||||
|
|||||||
432
src/paint.rs
432
src/paint.rs
@@ -1,44 +1,21 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use iced::Theme;
|
use iced::Theme;
|
||||||
use iced::padding;
|
use iced::padding;
|
||||||
use iced::widget::container;
|
use iced::widget::container;
|
||||||
use iced::widget::{Column, Grid, button, column, image, row, mouse_area};
|
use iced::widget::{Column, Grid, button, column, image, row};
|
||||||
use iced::{Border, Color, Length, Point, Task};
|
use iced::{Border, Color, Length, Point, Task};
|
||||||
|
|
||||||
|
|
||||||
use crate::image_button::image_button;
|
use crate::image_button::image_button;
|
||||||
|
use crate::mouse_area::mouse_area;
|
||||||
|
|
||||||
const WIDTH: u32 = 800;
|
const WIDTH: u32 = 800;
|
||||||
const HEIGHT: u32 = 600;
|
const HEIGHT: u32 = 600;
|
||||||
|
|
||||||
struct Paint {
|
|
||||||
tool_states: [bool; Tool::Count as usize],
|
|
||||||
tool_selected: Tool,
|
|
||||||
|
|
||||||
// 原始像素数据:RGBA 格式
|
|
||||||
// 长度 = WIDTH * HEIGHT * 4
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
pixels_bak: Vec<u8>,
|
|
||||||
|
|
||||||
// 当前画笔颜色
|
|
||||||
color: Color,
|
|
||||||
|
|
||||||
// 是否正在绘制
|
|
||||||
is_drawing: bool,
|
|
||||||
|
|
||||||
begin_point: Option<Point>,
|
|
||||||
|
|
||||||
// 用于显示的图像句柄缓存
|
|
||||||
// 每次像素变化后需要重新生成
|
|
||||||
image_handle: image::Handle,
|
|
||||||
|
|
||||||
// 标记像素是否被修改,用于优化图像句柄的生成
|
|
||||||
dirty: bool,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Decrement,
|
|
||||||
MousePressed(Point),
|
MousePressed(Point),
|
||||||
MouseReleased,
|
MouseReleased(Point),
|
||||||
MouseMoved(Point),
|
MouseMoved(Point),
|
||||||
Clear,
|
Clear,
|
||||||
ChangeColor(Color),
|
ChangeColor(Color),
|
||||||
@@ -118,6 +95,54 @@ impl From<Tool> for usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
struct ColorU8 {
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
a: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorU8 {
|
||||||
|
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
|
Self { r, g, b, a }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_same(&self, color: Color) -> bool {
|
||||||
|
self.r == (color.r * 255.0) as u8
|
||||||
|
&& self.g == (color.g * 255.0) as u8
|
||||||
|
&& self.b == (color.b * 255.0) as u8
|
||||||
|
&& self.a == (color.a * 255.0) as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Paint {
|
||||||
|
tool_states: [bool; Tool::Count as usize],
|
||||||
|
tool_selected: Tool,
|
||||||
|
|
||||||
|
// 原始像素数据:RGBA 格式
|
||||||
|
// 长度 = WIDTH * HEIGHT * 4
|
||||||
|
pixels: Vec<u8>,
|
||||||
|
pixels_bak: Vec<u8>,
|
||||||
|
|
||||||
|
// 当前画笔颜色
|
||||||
|
color: Color,
|
||||||
|
|
||||||
|
// 是否正在绘制
|
||||||
|
is_drawing: bool,
|
||||||
|
|
||||||
|
begin_point: Point,
|
||||||
|
|
||||||
|
// 用于显示的图像句柄缓存
|
||||||
|
// 每次像素变化后需要重新生成
|
||||||
|
image_handle: image::Handle,
|
||||||
|
|
||||||
|
// brush 大小
|
||||||
|
brush_radius: i32,
|
||||||
|
|
||||||
|
// 标记像素是否被修改,用于优化图像句柄的生成
|
||||||
|
dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Paint {
|
impl Paint {
|
||||||
// region iced application (new view update)
|
// region iced application (new view update)
|
||||||
@@ -134,8 +159,9 @@ impl Paint {
|
|||||||
pixels_bak: Vec::new(),
|
pixels_bak: Vec::new(),
|
||||||
color: Color::BLACK,
|
color: Color::BLACK,
|
||||||
is_drawing: false,
|
is_drawing: false,
|
||||||
begin_point: None,
|
begin_point: Point::ORIGIN,
|
||||||
image_handle: image::Handle::from_rgba(WIDTH, HEIGHT, data),
|
image_handle: image::Handle::from_rgba(WIDTH, HEIGHT, data),
|
||||||
|
brush_radius: 1,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,8 +174,8 @@ impl Paint {
|
|||||||
.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(Message::MousePressed(Point::ORIGIN)) // 占位,实际逻辑在 on_drag 或自定义
|
.on_press(|pos| Message::MousePressed(pos)) // 占位,实际逻辑在 on_drag 或自定义
|
||||||
.on_release(Message::MouseReleased)
|
.on_release(|pos| Message::MouseReleased(pos))
|
||||||
.on_move(|pos| Message::MouseMoved(pos));
|
.on_move(|pos| Message::MouseMoved(pos));
|
||||||
// 注意:mouse_area 的 on_move 给出的坐标通常是相对于 widget 左上角的,这正是我们需要的!
|
// 注意:mouse_area 的 on_move 给出的坐标通常是相对于 widget 左上角的,这正是我们需要的!
|
||||||
|
|
||||||
@@ -185,13 +211,16 @@ impl Paint {
|
|||||||
|
|
||||||
// We use a column: a simple vertical layout
|
// We use a column: a simple vertical layout
|
||||||
column![
|
column![
|
||||||
button("-").on_press(Message::Decrement),
|
button("CLEAR").on_press(Message::Clear),
|
||||||
row![tool_area, canvas_area],
|
row![tool_area, canvas_area],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
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::FillWithColor => {
|
||||||
|
self.update_with_fill_with_color(message);
|
||||||
|
}
|
||||||
Tool::Pencil => {
|
Tool::Pencil => {
|
||||||
self.update_with_pencil(message);
|
self.update_with_pencil(message);
|
||||||
}
|
}
|
||||||
@@ -239,8 +268,16 @@ impl Paint {
|
|||||||
|
|
||||||
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) => {
|
||||||
|
// let start = Instant::now();
|
||||||
|
// let (iter_count, fill_count) = self.fill_scanline(pos);
|
||||||
|
// let elapsed = start.elapsed();
|
||||||
|
// println!(
|
||||||
|
// "fill_scanline: {:?}, iter_count: {iter_count}, fill_count: {fill_count}, ratio: {}",
|
||||||
|
// elapsed,
|
||||||
|
// (iter_count as f32 / fill_count as f32)
|
||||||
|
// );
|
||||||
|
self.fill_scanline(pos);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -248,21 +285,20 @@ impl Paint {
|
|||||||
|
|
||||||
pub fn update_with_pencil(&mut self, message: Message) {
|
pub fn update_with_pencil(&mut self, message: Message) {
|
||||||
match message {
|
match message {
|
||||||
Message::MousePressed(_pos) => {
|
Message::MousePressed(pos) => {
|
||||||
|
println!("pressed: {:?}", pos);
|
||||||
self.is_drawing = true;
|
self.is_drawing = true;
|
||||||
|
self.draw_pixel_at1(pos);
|
||||||
|
self.begin_point = pos;
|
||||||
}
|
}
|
||||||
Message::MouseReleased => {
|
Message::MouseReleased(pos) => {
|
||||||
self.is_drawing = false;
|
self.is_drawing = false;
|
||||||
self.begin_point = None;
|
self.begin_point = pos;
|
||||||
}
|
}
|
||||||
Message::MouseMoved(pos) => {
|
Message::MouseMoved(pos) => {
|
||||||
if self.is_drawing {
|
if self.is_drawing {
|
||||||
if let Some(begin_point) = self.begin_point {
|
self.draw_line(self.begin_point, pos);
|
||||||
self.draw_line(begin_point, pos);
|
self.begin_point = pos;
|
||||||
} else {
|
|
||||||
self.draw_pixel_at1(pos);
|
|
||||||
}
|
|
||||||
self.begin_point = Some(pos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -271,23 +307,19 @@ impl Paint {
|
|||||||
|
|
||||||
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.save_pixels();
|
self.save_pixels();
|
||||||
|
self.begin_point = pos;
|
||||||
}
|
}
|
||||||
Message::MouseReleased => {
|
Message::MouseReleased(pos) => {
|
||||||
self.is_drawing = false;
|
self.is_drawing = false;
|
||||||
self.begin_point = None;
|
self.begin_point = pos;
|
||||||
|
|
||||||
}
|
}
|
||||||
Message::MouseMoved(pos) => {
|
Message::MouseMoved(pos) => {
|
||||||
if self.is_drawing {
|
if self.is_drawing {
|
||||||
if let Some(begin_point) = self.begin_point {
|
|
||||||
self.restore_pixels();
|
self.restore_pixels();
|
||||||
self.draw_line(begin_point, pos);
|
self.draw_line(self.begin_point, pos);
|
||||||
} else {
|
|
||||||
self.begin_point = Some(pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -320,20 +352,55 @@ impl Paint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// draw method
|
/// draw method
|
||||||
|
#[allow(unused)]
|
||||||
impl Paint {
|
impl Paint {
|
||||||
/// 核心绘图逻辑:直接在字节数组上操作
|
fn pixel_at(&self, x: i32, y: i32) -> ColorU8 {
|
||||||
fn draw_pixel_at(&mut self, pos: Point<i32>) {
|
|
||||||
let x = pos.x;
|
|
||||||
let y = pos.y;
|
|
||||||
|
|
||||||
// 边界检查
|
// 边界检查
|
||||||
if x < 0 || x >= WIDTH as i32 || y < 0 || y >= HEIGHT as i32 {
|
if x < 0 || x >= WIDTH as i32 || y < 0 || y >= HEIGHT as i32 {
|
||||||
return;
|
return ColorU8::new(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = x as u32;
|
let x = x as u32;
|
||||||
let y = y as u32;
|
let y = y as u32;
|
||||||
|
|
||||||
|
let index = ((y * WIDTH + x) * 4) as usize;
|
||||||
|
|
||||||
|
ColorU8::new(
|
||||||
|
self.pixels[index],
|
||||||
|
self.pixels[index + 1],
|
||||||
|
self.pixels[index + 2],
|
||||||
|
self.pixels[index + 3],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixel_at_raw(&self, x: i32, y: i32) -> ColorU8 {
|
||||||
|
let x = x as u32;
|
||||||
|
let y = y as u32;
|
||||||
|
let index = ((y * WIDTH + x) * 4) as usize;
|
||||||
|
ColorU8::new(
|
||||||
|
self.pixels[index],
|
||||||
|
self.pixels[index + 1],
|
||||||
|
self.pixels[index + 2],
|
||||||
|
self.pixels[index + 3],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixel_at1(&self, point: Point) -> ColorU8 {
|
||||||
|
self.pixel_at(point.x as i32, point.y as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_brush_at(&mut self, center_x: i32, center_y: i32) {
|
||||||
|
let r = self.brush_radius;
|
||||||
|
for dy in -r..=r {
|
||||||
|
for dx in -r..=r {
|
||||||
|
if dx * dx + dy * dy <= r * r {
|
||||||
|
self.draw_pixel_at(center_x + dx, center_y + dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel_at_raw(&mut self, x: u32, y: u32) {
|
||||||
// 计算索引:(y * width + x) * 4
|
// 计算索引:(y * width + x) * 4
|
||||||
let index = ((y * WIDTH + x) * 4) as usize;
|
let index = ((y * WIDTH + x) * 4) as usize;
|
||||||
|
|
||||||
@@ -347,8 +414,39 @@ impl Paint {
|
|||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 核心绘图逻辑:直接在字节数组上操作
|
||||||
|
fn draw_pixel_at(&mut self, x: i32, y: i32) {
|
||||||
|
// 边界检查
|
||||||
|
if x < 0 || x >= WIDTH as i32 || y < 0 || y >= HEIGHT as i32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel_row(&mut self, xs: i32, xe: i32, y: i32) {
|
||||||
|
if y < 0 || y >= HEIGHT as i32 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let xs = xs.clamp(0, WIDTH as i32 - 1) as u32;
|
||||||
|
let xe = xe.clamp(0, WIDTH as i32) as u32;
|
||||||
|
let y = y as u32;
|
||||||
|
for x in xs..xe {
|
||||||
|
let index = ((y * WIDTH + x) * 4) as usize;
|
||||||
|
|
||||||
|
// 写入 RGBA 数据
|
||||||
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
||||||
|
self.pixels[index] = (self.color.r * 255.0) as u8; // R
|
||||||
|
self.pixels[index + 1] = (self.color.g * 255.0) as u8; // G
|
||||||
|
self.pixels[index + 2] = (self.color.b * 255.0) as u8; // B
|
||||||
|
self.pixels[index + 3] = (self.color.a * 255.0) as u8; // A
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_pixel_at1(&mut self, pos: Point) {
|
fn draw_pixel_at1(&mut self, pos: Point) {
|
||||||
self.draw_pixel_at(Point::new(pos.x as i32, pos.y as i32))
|
self.draw_pixel_at(pos.x as i32, pos.y as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_lines(&mut self, points: &[Point]) {
|
fn draw_lines(&mut self, points: &[Point]) {
|
||||||
@@ -369,13 +467,17 @@ impl Paint {
|
|||||||
|
|
||||||
/// Bresenham's line drawing algorithm
|
/// Bresenham's line drawing algorithm
|
||||||
fn draw_line(&mut self, begin: Point, end: Point) {
|
fn draw_line(&mut self, begin: Point, end: Point) {
|
||||||
let x1 = begin.x;
|
let x1 = begin.x as i32;
|
||||||
let y1 = begin.y;
|
let y1 = begin.y as i32;
|
||||||
let x2 = end.x;
|
let x2 = end.x as i32;
|
||||||
let y2 = end.y;
|
let y2 = end.y as i32;
|
||||||
|
|
||||||
let dx = (x2 - x1) as i32;
|
// draw start end point, 防止多条线段在连接点出现断开(比如 ab bc)
|
||||||
let dy = (y2 - y1) as i32;
|
self.draw_pixel_at(x1, y1);
|
||||||
|
self.draw_pixel_at(x2, y2);
|
||||||
|
|
||||||
|
let dx = (x2 - x1);
|
||||||
|
let dy = (y2 - y1);
|
||||||
let dx1 = dx.abs();
|
let dx1 = dx.abs();
|
||||||
let dy1 = dy.abs();
|
let dy1 = dy.abs();
|
||||||
let mut px = 2 * dy1 - dx1;
|
let mut px = 2 * dy1 - dx1;
|
||||||
@@ -388,16 +490,15 @@ impl Paint {
|
|||||||
|
|
||||||
if dy1 <= dx1 {
|
if dy1 <= dx1 {
|
||||||
if dx >= 0 {
|
if dx >= 0 {
|
||||||
x = x1 as i32;
|
x = x1;
|
||||||
y = y1 as i32;
|
y = y1;
|
||||||
xe = x2 as i32;
|
xe = x2;
|
||||||
} else {
|
} else {
|
||||||
x = x2 as i32;
|
x = x2;
|
||||||
y = y2 as i32;
|
y = y2;
|
||||||
xe = x1 as i32;
|
xe = x1;
|
||||||
}
|
}
|
||||||
let point = Point::new(x, y);
|
self.draw_pixel_at(x, y);
|
||||||
self.draw_pixel_at(point);
|
|
||||||
while x < xe {
|
while x < xe {
|
||||||
x += 1;
|
x += 1;
|
||||||
if px < 0 {
|
if px < 0 {
|
||||||
@@ -410,21 +511,19 @@ impl Paint {
|
|||||||
}
|
}
|
||||||
px = px + 2 * (dy1 - dx1);
|
px = px + 2 * (dy1 - dx1);
|
||||||
}
|
}
|
||||||
let point = Point::new(x, y);
|
self.draw_pixel_at(x, y);
|
||||||
self.draw_pixel_at(point);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if dy >= 0 {
|
if dy >= 0 {
|
||||||
x = x1 as i32;
|
x = x1;
|
||||||
y = y1 as i32;
|
y = y1;
|
||||||
ye = y2 as i32;
|
ye = y2;
|
||||||
} else {
|
} else {
|
||||||
x = x2 as i32;
|
x = x2;
|
||||||
y = y2 as i32;
|
y = y2;
|
||||||
ye = y1 as i32;
|
ye = y1;
|
||||||
}
|
}
|
||||||
let point = Point::new(x, y);
|
self.draw_pixel_at(x, y);
|
||||||
self.draw_pixel_at(point);
|
|
||||||
while y < ye {
|
while y < ye {
|
||||||
y = y + 1;
|
y = y + 1;
|
||||||
if py <= 0 {
|
if py <= 0 {
|
||||||
@@ -437,8 +536,7 @@ impl Paint {
|
|||||||
}
|
}
|
||||||
py = py + 2 * (dx1 - dy1);
|
py = py + 2 * (dx1 - dy1);
|
||||||
}
|
}
|
||||||
let point = Point::new(x, y);
|
self.draw_pixel_at(x, y);
|
||||||
self.draw_pixel_at(point);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,6 +548,170 @@ impl Paint {
|
|||||||
fn restore_pixels(&mut self) {
|
fn restore_pixels(&mut self) {
|
||||||
self.pixels = self.pixels_bak.clone();
|
self.pixels = self.pixels_bak.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fill_slow(&mut self, begin: Point) -> (i32, i32) {
|
||||||
|
let start_x = begin.x as i32;
|
||||||
|
let start_y = begin.y as i32;
|
||||||
|
let target_color = self.pixel_at(start_x, start_y);
|
||||||
|
if target_color.is_same(self.color) {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut scan_points = vec![(start_x, start_y)];
|
||||||
|
let width = WIDTH as i32;
|
||||||
|
let height = HEIGHT as i32;
|
||||||
|
let mut iter_count = 0;
|
||||||
|
let mut fill_count = 0;
|
||||||
|
while let Some((x, y)) = scan_points.pop() {
|
||||||
|
iter_count += 1;
|
||||||
|
|
||||||
|
if x < 0 || x >= width || y < 0 || y >= height {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.pixel_at_raw(x, y) == target_color {
|
||||||
|
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||||
|
fill_count += 1;
|
||||||
|
|
||||||
|
let p1 = (x - 1, y);
|
||||||
|
let p2 = (x + 1, y);
|
||||||
|
let p3 = (x, y - 1);
|
||||||
|
let p4 = (x, y + 1);
|
||||||
|
scan_points.push(p1);
|
||||||
|
scan_points.push(p2);
|
||||||
|
scan_points.push(p3);
|
||||||
|
scan_points.push(p4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(iter_count, fill_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_less_slow(&mut self, begin: Point) -> (i32, i32) {
|
||||||
|
let start_x = begin.x as i32;
|
||||||
|
let start_y = begin.y as i32;
|
||||||
|
let width = WIDTH as i32;
|
||||||
|
let height = HEIGHT as i32;
|
||||||
|
|
||||||
|
if start_x < 0 || start_x >= width || start_y < 0 || start_y >= height {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_color = self.pixel_at(start_x, start_y);
|
||||||
|
if target_color.is_same(self.color) {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stack = vec![(start_x, start_y)];
|
||||||
|
let mut iter_count = 0;
|
||||||
|
let mut fill_count = 0;
|
||||||
|
|
||||||
|
while let Some((x, y)) = stack.pop() {
|
||||||
|
iter_count += 1;
|
||||||
|
|
||||||
|
if x < 0 || x >= width || y < 0 || y >= height {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pixel_at_raw(x, y) != target_color {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||||
|
fill_count += 1;
|
||||||
|
|
||||||
|
// 提前检查边界再入栈
|
||||||
|
if x > 0 {
|
||||||
|
stack.push((x - 1, y));
|
||||||
|
}
|
||||||
|
if x + 1 < width {
|
||||||
|
stack.push((x + 1, y));
|
||||||
|
}
|
||||||
|
if y > 0 {
|
||||||
|
stack.push((x, y - 1));
|
||||||
|
}
|
||||||
|
if y + 1 < height {
|
||||||
|
stack.push((x, y + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(iter_count, fill_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_scanline(&mut self, begin: Point) -> (i32, i32) {
|
||||||
|
let start_x = begin.x as i32;
|
||||||
|
let start_y = begin.y as i32;
|
||||||
|
let width = WIDTH as i32;
|
||||||
|
let height = HEIGHT as i32;
|
||||||
|
|
||||||
|
// 边界检查
|
||||||
|
if start_x < 0 || start_x >= width || start_y < 0 || start_y >= height {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_color = self.pixel_at(start_x, start_y);
|
||||||
|
if target_color.is_same(self.color) {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 栈中存储 (y, x1, x2):表示第 y 行从 x1 到 x2(含)需要向上/下扫描
|
||||||
|
let mut stack = vec![(start_y, start_x, start_x)];
|
||||||
|
let mut iter_count = 0;
|
||||||
|
let mut fill_count = 0;
|
||||||
|
|
||||||
|
while let Some((y, mut lx, mut rx)) = stack.pop() {
|
||||||
|
iter_count += 1;
|
||||||
|
|
||||||
|
// 向左扩展 lx
|
||||||
|
while lx - 1 >= 0 && self.pixel_at_raw(lx - 1, y) == target_color {
|
||||||
|
lx -= 1;
|
||||||
|
}
|
||||||
|
// 向右扩展 rx
|
||||||
|
while rx + 1 < width && self.pixel_at_raw(rx + 1, y) == target_color {
|
||||||
|
rx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充当前行 [lx, rx]
|
||||||
|
for x in lx..=rx {
|
||||||
|
self.draw_pixel_at_raw(x as u32, y as u32);
|
||||||
|
fill_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查上一行 (y - 1)
|
||||||
|
if y - 1 >= 0 {
|
||||||
|
let mut x = lx;
|
||||||
|
while x <= rx {
|
||||||
|
if self.pixel_at_raw(x, y - 1) == target_color {
|
||||||
|
let span_start = x;
|
||||||
|
// 跳过连续的目标色块
|
||||||
|
while x <= rx && self.pixel_at_raw(x, y - 1) == target_color {
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
// 将这个 span 入栈(用于后续处理上一行的上一行)
|
||||||
|
stack.push((y - 1, span_start, x - 1));
|
||||||
|
} else {
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查下一行 (y + 1)
|
||||||
|
if y + 1 < height {
|
||||||
|
let mut x = lx;
|
||||||
|
while x <= rx {
|
||||||
|
if self.pixel_at_raw(x, y + 1) == target_color {
|
||||||
|
let span_start = x;
|
||||||
|
while x <= rx && self.pixel_at_raw(x, y + 1) == target_color {
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
stack.push((y + 1, span_start, x - 1));
|
||||||
|
} else {
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(iter_count, fill_count)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|||||||
Reference in New Issue
Block a user