diff --git a/Cargo.lock b/Cargo.lock index ee7544c..cac5ed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1061,6 +1061,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "foldhash" version = "0.1.5" @@ -1521,6 +1527,7 @@ dependencies = [ "image", "kamadak-exif", "log", + "lyon_path", "raw-window-handle", "rustc-hash 2.1.1", "thiserror 2.0.18", @@ -1595,6 +1602,7 @@ dependencies = [ "iced_debug", "iced_graphics", "log", + "lyon", "rustc-hash 2.1.1", "thiserror 2.0.18", "wgpu", @@ -1913,6 +1921,58 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +[[package]] +name = "lyon" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "malloc_buf" version = "0.0.6" diff --git a/Cargo.toml b/Cargo.toml index 8e34482..061eef3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -iced = {version = "0.14.0", features = ["advanced", "image", "smol"]} +iced = {version = "0.14.0", features = ["advanced", "image", "smol", "canvas"]} iced_core = "0.14.0" image = "0.25.9" rand = "0.10.0" diff --git a/src/color_box.rs b/src/color_box.rs new file mode 100644 index 0000000..6b7ed66 --- /dev/null +++ b/src/color_box.rs @@ -0,0 +1,246 @@ +use iced::color; +use iced::mouse; +use iced::widget::canvas::{self, Cache, Geometry}; +use iced::{Color, Point, Rectangle, Renderer, Size, Theme}; + +use crate::mscanvas::MSColor; + +pub struct CurrentColorBox { + cache: Cache, + foreground_color: MSColor, + background_color: MSColor, +} + +impl CurrentColorBox { + pub fn new(foreground_color: MSColor, background_color: MSColor) -> Self { + Self { + cache: Cache::default(), + foreground_color, + background_color, + } + } +} + +impl canvas::Program for CurrentColorBox { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> Vec { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { + // let palette = theme.palette(); + + let gray1 = color!(0x808080); + let gray2 = color!(0xc0c0c0); + let Size { width, height } = frame.size(); + + frame.fill_rectangle(Point::ORIGIN, Size::new(width - 1.0, 1.0), gray1); + frame.fill_rectangle(Point::ORIGIN, Size::new(1.0, height - 1.0), gray1); + frame.fill_rectangle( + Point::new(width - 2.0, 1.0), + Size::new(1.0, height - 2.0), + gray2, + ); + frame.fill_rectangle( + Point::new(1.0, height - 2.0), + Size::new(width - 2.0, 1.0), + gray2, + ); + + frame.fill_rectangle( + Point::new(1.0, 1.0), + Size::new(width - 3.0, 1.0), + Color::BLACK, + ); + frame.fill_rectangle( + Point::new(1.0, 1.0), + Size::new(1.0, height - 3.0), + Color::BLACK, + ); + + frame.fill_rectangle( + Point::new(width - 1.0, 0.0), + Size::new(1.0, height), + Color::WHITE, + ); + frame.fill_rectangle( + Point::new(0.0, height - 1.0), + Size::new(width, 1.0), + Color::WHITE, + ); + + let tile_colors = [Color::WHITE, gray2]; + let mut tile_idx = 0; + for y in 2..(height - 2.0) as i32 { + for x in 2..(width - 2.0) as i32 { + frame.fill_rectangle( + Point::new(x as f32, y as f32), + Size::new(1.0, 1.0), + tile_colors[tile_idx], + ); + tile_idx += 1; + if tile_idx >= tile_colors.len() { + tile_idx = 0; + } + } + } + + let box_size: f32 = 20.0; + let fore_x: f32 = 6.0; + let fore_y: f32 = 8.0; + let back_x: f32 = fore_x + box_size / 2.0 + 2.0; + let back_y: f32 = fore_y + box_size / 2.0 + 2.0; + + // 背景颜色展示框 + frame.fill_rectangle( + Point::new(back_x, back_y), + Size::new(box_size, 1.0), + Color::WHITE, + ); + frame.fill_rectangle( + Point::new(back_x, back_y), + Size::new(1.0, box_size), + Color::WHITE, + ); + frame.fill_rectangle( + Point::new(back_x + box_size - 1.0, back_y + 1.0), + Size::new(1.0, box_size - 1.0), + gray1, + ); + frame.fill_rectangle( + Point::new(back_x + 1.0, back_y + box_size - 1.0), + Size::new(box_size - 1.0, 1.0), + gray1, + ); + + frame.fill_rectangle( + Point::new(back_x + 1.0, back_y + 1.0), + Size::new(box_size - 2.0, box_size - 2.0), + gray2, + ); + frame.fill_rectangle( + Point::new(back_x + 2.0, back_y + 2.0), + Size::new(box_size - 4.0, box_size - 4.0), + self.background_color.into_color(), + ); + + // 前景颜色展示框 + frame.fill_rectangle( + Point::new(fore_x, fore_y), + Size::new(box_size, 1.0), + Color::WHITE, + ); + frame.fill_rectangle( + Point::new(fore_x, fore_y), + Size::new(1.0, box_size), + Color::WHITE, + ); + frame.fill_rectangle( + Point::new(fore_x + box_size - 1.0, fore_y + 1.0), + Size::new(1.0, box_size - 1.0), + gray1, + ); + frame.fill_rectangle( + Point::new(fore_x + 1.0, fore_y + box_size - 1.0), + Size::new(box_size - 1.0, 1.0), + gray1, + ); + + frame.fill_rectangle( + Point::new(fore_x + 1.0, fore_y + 1.0), + Size::new(box_size - 2.0, box_size - 2.0), + gray2, + ); + frame.fill_rectangle( + Point::new(fore_x + 2.0, fore_y + 2.0), + Size::new(box_size - 4.0, box_size - 4.0), + self.foreground_color.into_color(), + ); + }); + + vec![geometry] + } +} + +pub struct ColorSelectionBox { + cache: Cache, + color: MSColor, +} + +impl ColorSelectionBox { + pub fn new(color: MSColor) -> ColorSelectionBox { + Self { + cache: Cache::default(), + color, + } + } +} + +impl canvas::Program for ColorSelectionBox { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: Rectangle, + _cursor: mouse::Cursor, + ) -> Vec { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { + // let palette = theme.palette(); + + let gray1 = color!(0x808080); + let gray2 = color!(0xc0c0c0); + let Size { width, height } = frame.size(); + + frame.fill_rectangle(Point::ORIGIN, Size::new(width - 1.0, 1.0), gray1); + frame.fill_rectangle(Point::ORIGIN, Size::new(1.0, height - 1.0), gray1); + frame.fill_rectangle( + Point::new(width - 2.0, 1.0), + Size::new(1.0, height - 2.0), + gray2, + ); + frame.fill_rectangle( + Point::new(1.0, height - 2.0), + Size::new(width - 2.0, 1.0), + gray2, + ); + + frame.fill_rectangle( + Point::new(1.0, 1.0), + Size::new(width - 3.0, 1.0), + Color::BLACK, + ); + frame.fill_rectangle( + Point::new(1.0, 1.0), + Size::new(1.0, height - 3.0), + Color::BLACK, + ); + + frame.fill_rectangle( + Point::new(width - 1.0, 0.0), + Size::new(1.0, height), + Color::WHITE, + ); + frame.fill_rectangle( + Point::new(0.0, height - 1.0), + Size::new(width, 1.0), + Color::WHITE, + ); + + frame.fill_rectangle( + Point::new(2.0, 2.0), + Size::new(width - 4.0, height - 4.0), + self.color.into_color(), + ); + }); + + vec![geometry] + } +} diff --git a/src/main.rs b/src/main.rs index 1ea3e64..8b2843b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod color_box; mod image_button; mod mouse_area; mod mscanvas; diff --git a/src/mscanvas.rs b/src/mscanvas.rs index 0c653b6..6d12267 100644 --- a/src/mscanvas.rs +++ b/src/mscanvas.rs @@ -58,20 +58,23 @@ impl MSColor { }; pub fn into_color(self) -> Color { - Color::from_rgba8( - self.r, - self.g, - self.b, - (self.a / 255) as f32, - ) + Color::from_rgba8(self.r, self.g, self.b, (self.a / 255) as f32) + } + + /// rgb 十六进制 + pub fn from_hex(n: i32) -> Self { + let r = (n >> 16) as u8; + let g = (n >> 8) as u8; + let b = n as u8; + Self { r, g, b, a: 0xff } } } #[derive(Clone, Copy, PartialEq)] struct Edge { - y_max: i32, // 边的最大 y(不包含) - x: f32, // 当前扫描线 y 处的 x 值 - dx_dy: f32, // 1 / slope = Δx / Δy + y_max: i32, // 边的最大 y(不包含) + x: f32, // 当前扫描线 y 处的 x 值 + dx_dy: f32, // 1 / slope = Δx / Δy } impl Edge { @@ -677,13 +680,6 @@ impl MSCanvas { let min_sq = min_r * min_r; let max_sq = max_r * max_r; - let color = MSColor::new( - (255 + self.foreground_color.r) / 2, - self.foreground_color.g, - self.foreground_color.b, - self.foreground_color.a, - ); - let center_x = center.x as i32; let center_y = center.y as i32; for y in min_y..=max_y { @@ -1040,7 +1036,10 @@ impl MSCanvas { let x_start = active_edges[i].x.ceil() as i32; let x_end = active_edges[i + 1].x.floor() as i32; for x in x_start..=x_end { - self.draw_pixel_color_at(Point::new(x as f32, y as f32), self.background_color); + self.draw_pixel_color_at( + Point::new(x as f32, y as f32), + self.background_color, + ); } } } diff --git a/src/paint.rs b/src/paint.rs index dc857ec..12f6b6f 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -1,16 +1,19 @@ +use std::thread; + use ::image::{ImageBuffer, ImageError, Rgba}; use iced::Theme; use iced::padding; use iced::time::{self, Instant, milliseconds}; +use iced::widget::canvas::Canvas; 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 iced::{Border, Element, Length, Point, Renderer, Subscription, Task}; +use iced::{color, event, mouse}; +use crate::color_box::{ColorSelectionBox, CurrentColorBox}; use crate::image_button::image_button; use crate::mouse_area::mouse_area; -use crate::mscanvas::MSCanvas; +use crate::mscanvas::{MSCanvas, MSColor}; const WIDTH: u32 = 800; const HEIGHT: u32 = 600; @@ -71,6 +74,10 @@ enum Message { WindowMouseRelease, Tick(Instant), + + ClickForegroundColor(MSColor), + ClickBackgroundColor(MSColor), + SwapForeBackColor, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -269,6 +276,9 @@ struct PaintApp { config: Config, brush_selected: Option, + + foreground_color: MSColor, + background_color: MSColor, } impl PaintApp { @@ -280,6 +290,7 @@ impl PaintApp { let pixels = canvas.get_pixels(); let config = Config::default(); canvas.set_line_width(config.line_width); + canvas.set_background_color(MSColor::GREEN); Self { tool_states: [false; Tool::Count as usize], tool_selected: Tool::Count, @@ -294,6 +305,8 @@ impl PaintApp { dirty: false, config, brush_selected: None, + foreground_color: MSColor::BLACK, + background_color: MSColor::WHITE, } } @@ -342,7 +355,7 @@ impl PaintApp { let palette = theme.extended_palette(); container::Style { - background: Some(Color::from_rgb8(192, 192, 192).into()), + background: Some(color!(0xc0c0c0).into()), border: Border { width: 1.0, radius: 5.0.into(), @@ -361,7 +374,68 @@ impl PaintApp { let palette = theme.extended_palette(); container::Style { - background: Some(Color::from_rgb8(192, 192, 192).into()), + background: Some(color!(0xc0c0c0).into()), + border: Border { + width: 1.0, + radius: 5.0.into(), + color: palette.background.weak.color, + }, + ..container::Style::default() + } + }); + + let color_selection_fn = |n: i32| { + let cs = ColorSelectionBox::new(MSColor::from_hex(n)); + let canvas = Canvas::new(cs).width(23).height(23); + mouse_area(canvas) + .on_press(move |_| Message::ClickForegroundColor(MSColor::from_hex(n))) + .on_right_press(move |_| Message::ClickBackgroundColor(MSColor::from_hex(n))) + }; + let first_row = row![ + color_selection_fn(0x000000), + color_selection_fn(0x808080), + color_selection_fn(0x800000), + color_selection_fn(0x808000), + color_selection_fn(0x008000), + color_selection_fn(0x008080), + color_selection_fn(0x000080), + color_selection_fn(0x800080), + color_selection_fn(0x808040), + color_selection_fn(0x004040), + color_selection_fn(0x0080ff), + color_selection_fn(0x004080), + color_selection_fn(0x4000ff), + color_selection_fn(0x804000), + ]; + let second_row = row![ + color_selection_fn(0xffffff), + color_selection_fn(0xc0c0c0), + color_selection_fn(0xff0000), + color_selection_fn(0xffff00), + color_selection_fn(0x00ff00), + color_selection_fn(0x00ffff), + color_selection_fn(0x0000ff), + color_selection_fn(0xff00ff), + color_selection_fn(0xffff80), + color_selection_fn(0x00ff80), + color_selection_fn(0x80ffff), + color_selection_fn(0x8080ff), + color_selection_fn(0xff0080), + color_selection_fn(0xff8040), + ]; + let current_color = CurrentColorBox::new(self.foreground_color, self.background_color); + let current_color = Canvas::new(current_color).width(46).height(46); + let current_color = mouse_area(current_color) + .on_press(|_| Message::SwapForeBackColor) + .on_right_press(|_| Message::SwapForeBackColor); + let color_area = row![current_color, column![first_row, second_row],]; + let color_area = container(color_area) + .padding(padding::top(10).left(5).bottom(10).right(5)) + .style(|theme: &Theme| { + let palette = theme.extended_palette(); + + container::Style { + background: Some(color!(0xc0c0c0).into()), border: Border { width: 1.0, radius: 5.0.into(), @@ -432,6 +506,7 @@ impl PaintApp { button("PNG").on_press(Message::SavePNG), ], row![tool_area, canvas_area], + color_area, debug_area, ] } @@ -524,6 +599,21 @@ impl PaintApp { self.dirty = true; } } + Message::ClickForegroundColor(color) => { + self.foreground_color = color; + self.canvas.set_foreground_color(color); + } + Message::ClickBackgroundColor(color) => { + self.background_color = color; + self.canvas.set_background_color(color); + } + Message::SwapForeBackColor => { + let tmp = self.foreground_color; + self.foreground_color = self.background_color; + self.background_color = tmp; + self.canvas.set_foreground_color(self.foreground_color); + self.canvas.set_background_color(self.background_color); + } _ => {} }