From b06d33b66e70453451bc5eb5ade9164defea4849 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 28 Mar 2021 09:51:41 +0530 Subject: implement flood fill; new fill brush --- src/app.rs | 139 +++++++++++++++++++++++++++++++++++++--------------------- src/bitmap.rs | 37 ++++++++++++---- 2 files changed, 118 insertions(+), 58 deletions(-) diff --git a/src/app.rs b/src/app.rs index 09f52f9..7bca6ba 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,10 @@ use crate::{ bitmap::{MapPoint, Pixmap}, + brush::Brush, command::CommandBox, consts::{colors::*, FONT_PATH}, dither, - lisp::{eval, lex::Lexer, parse::Parser, prelude, EnvList, Environment}, + lisp::{eval, lex::Lexer, parse::Parser, prelude, EnvList}, message::Message, rect, symmetry::Symmetry, @@ -11,12 +12,7 @@ use crate::{ utils::{draw_text, is_copy_event, is_paste_event}, }; -use std::{ - convert::From, - fs::File, - io::prelude::*, - path::{Path, PathBuf}, -}; +use std::{convert::From, fs::File, io::prelude::*, path::Path}; use obi::Image; use sdl2::{ @@ -40,6 +36,7 @@ pub enum Mode { pub struct AppState<'ctx, 'file> { pub active_color: bool, pub brush_size: u8, + pub brush: Brush, pub canvas: Canvas, pub command_box: CommandBox, pub context: &'ctx Sdl, @@ -204,7 +201,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { val, } in op.into_iter() { - if self.pixmap.is_inside(point) { + if self.pixmap.contains(point) { match op_kind { OpKind::Undo => self.pixmap.set(point, old_val), OpKind::Redo => self.pixmap.set(point, val), @@ -346,12 +343,13 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { format!("---, ---") }; let status_text = format!( - "[DITHER {}][BRUSH {}][SYM {}][PT {}][ACTIVE {}]", + "[DITHER {}][BRUSH {}][SYM {}][PT {}][ACTIVE {}][KIND {}]", self.dither_level, self.brush_size + 1, self.symmetry, mouse_coords, - if self.active_color { "WHT" } else { "BLK" } + if self.active_color { "WHT" } else { "BLK" }, + self.brush ); draw_text( &mut self.canvas, @@ -426,13 +424,31 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { } } + fn draw_symmetry(&mut self) { + let (winsize_x, winsize_y) = self.canvas.window().size(); + let cs = self.zoom as u32; + let Symmetry { x: sym_x, y: sym_y } = self.symmetry; + if let Some(line) = sym_x { + self.canvas.set_draw_color(CYAN); + let line_coord = (line * cs) as i32 + self.start.y() + (cs / 2) as i32; + self.canvas + .draw_line((0, line_coord), (winsize_x as i32, line_coord)) + .unwrap(); + } + if let Some(line) = sym_y { + self.canvas.set_draw_color(CYAN); + let line_coord = (line * cs) as i32 + self.start.x() + (cs / 2) as i32; + self.canvas + .draw_line((line_coord, 0), (line_coord, winsize_y as i32)) + .unwrap(); + } + } + fn draw(&mut self) { let cs = self.zoom as u32; let (width, height) = (self.width(), self.height()); let start = self.start; let grid_enabled = self.grid.enabled; - let (winsize_x, winsize_y) = self.canvas.window().size(); - let Symmetry { x: sym_x, y: sym_y } = self.symmetry; for (idx, val) in self.pixmap.data.iter().enumerate() { if *val { let idx = idx as i32; @@ -451,20 +467,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { if grid_enabled { self.draw_grid(); } - if let Some(line) = sym_x { - self.canvas.set_draw_color(CYAN); - let line_coord = (line * cs) as i32 + self.start.y() + (cs / 2) as i32; - self.canvas - .draw_line((0, line_coord), (winsize_x as i32, line_coord)) - .unwrap(); - } - if let Some(line) = sym_y { - self.canvas.set_draw_color(CYAN); - let line_coord = (line * cs) as i32 + self.start.x() + (cs / 2) as i32; - self.canvas - .draw_line((line_coord, 0), (line_coord, winsize_y as i32)) - .unwrap(); - } + self.draw_symmetry(); self.draw_statusline(); self.draw_command_box(); self.draw_mouse(); @@ -515,6 +518,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { Self { active_color: true, brush_size: 0, + brush: Brush::new(), canvas, command_box: CommandBox::new(), context, @@ -602,22 +606,14 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { Keycode::I => self.pixmap.invert(), // line drawing Keycode::F => { - let end: Point = (mouse.x(), mouse.y()).into(); - if let Some(start) = self.last_point { - if let Ok(o) = - self.paint_line(start, end, self.active_color) - { - self.commit_operation(); - self.current_operation = o - .into_iter() - .filter(|v| !v.old_val == v.val) - .collect(); - self.commit_operation(); - self.last_point = - Some(self.idx_at_coord(end).unwrap().into()); - } + if matches!(self.brush, Brush::Line { extend: false, .. }) { + self.brush = Brush::line(0, true); + } else { + self.brush = Brush::line(0, false); } } + // bucket fill tool + Keycode::B => self.brush = Brush::Fill, Keycode::V => self.cycle_symmetry(), // undo & redo Keycode::U => { @@ -646,13 +642,63 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { x, y, mouse_btn, .. } => { let pt = (x, y); + let contact = self.idx_at_coord(pt).map(MapPoint::from); self.last_point = self.idx_at_coord(pt).map(MapPoint::from); let val = match mouse_btn { MouseButton::Right => !self.active_color, _ => self.active_color, }; - if let Ok(o) = self.paint_point(pt, val) { - self.current_operation.extend(o); + match self.brush { + Brush::Circle { size } => { + if let Ok(o) = self.paint_point(pt, val) { + self.current_operation.extend(o); + } + } + Brush::Line { + size, + start, + extend, + } => { + if start.is_none() { + self.brush = Brush::Line { + size, + start: contact, + extend, + }; + } else if let Ok(o) = + self.paint_line(start.unwrap(), pt, val) + { + self.current_operation.extend(o); + self.brush = Brush::Line { + size, + start: if extend { contact } else { None }, + extend, + }; + } + } + Brush::Fill => { + if let Some(c) = contact { + let target = self.pixmap.get(c); + let mut operation = vec![]; + self.pixmap.flood_fill( + c, + target, + self.active_color, + &mut operation, + ); + self.current_operation.extend( + operation + .into_iter() + .map(|point| ModifyRecord { + point, + old_val: target, + val: self.active_color, + }) + .collect::>(), + ) + } + } + _ => {} } } // click and drag @@ -674,14 +720,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> { } } // end of operation - Event::MouseButtonUp { .. } => { - let op = self - .current_operation - .drain(..) - .filter(|v| !v.old_val == v.val) - .collect::>(); - self.undo_stack.push(op); - } + Event::MouseButtonUp { .. } => self.commit_operation(), Event::Quit { .. } => { break 'running; } diff --git a/src/bitmap.rs b/src/bitmap.rs index c7e8fd4..41af745 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs @@ -54,8 +54,8 @@ impl Add for MapPoint { type Output = Self; fn add(self, rhs: Self) -> Self::Output { MapPoint { - x: self.x + rhs.x, - y: self.y + rhs.y, + x: self.x.saturating_add(rhs.x), + y: self.y.saturating_add(rhs.y), } } } @@ -64,8 +64,8 @@ impl Sub for MapPoint { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { MapPoint { - x: self.x - rhs.x, - y: self.y - rhs.y, + x: self.x.saturating_sub(rhs.x), + y: self.y.saturating_sub(rhs.y), } } } @@ -102,7 +102,7 @@ impl MapPoint { impl Pixmap where - T: Copy + Clone + Default, + T: Copy + Clone + Default + PartialEq, { pub fn new(width: u32, height: u32) -> Self { let data = vec![Default::default(); (width * height) as usize]; @@ -121,7 +121,7 @@ where } } - pub fn is_inside>(&self, pt: P) -> bool { + pub fn contains>(&self, pt: P) -> bool { let MapPoint { x, y } = pt.into(); x < self.width && y < self.height } @@ -181,7 +181,7 @@ where circle .into_iter() .flat_map(|pt| MapPoint::try_from(pt)) - .filter(|&pt| self.is_inside(pt)) + .filter(|&pt| self.contains(pt)) .collect() } @@ -226,9 +226,30 @@ where coordinates .into_iter() .flat_map(|pt| MapPoint::try_from(pt)) - .filter(|&pt| self.is_inside(pt)) + .filter(|&pt| self.contains(pt)) .collect() } + + pub fn flood_fill( + &mut self, + start: MapPoint, + target: T, + replacement: T, + pts: &mut Vec, + ) { + if !self.contains(start) || self.get(start) != target || self.get(start) == replacement { + return; + } else { + pts.push(start); + self.set(start, replacement); + for (x, y) in [(1, 0), (0, 1), (-1, 0), (0, -1)].iter() { + let dir = MapPoint::try_from((start.x as i64 - x, start.y as i64 + y)); + if let Ok(pt) = dir { + self.flood_fill(pt, target, replacement, pts); + } + } + } + } } impl Pixmap { -- cgit v1.2.3