aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-03-28 05:21:41 +0100
committerAkshay <[email protected]>2021-03-28 05:21:41 +0100
commitb06d33b66e70453451bc5eb5ade9164defea4849 (patch)
tree3fa736dccaf1a29df34d71d86e6d67aa693bfd55
parentbbfca639547fcd08e51181b70e449e71281d1d9b (diff)
implement flood fill; new fill brush
-rw-r--r--src/app.rs139
-rw-r--r--src/bitmap.rs37
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 @@
1use crate::{ 1use crate::{
2 bitmap::{MapPoint, Pixmap}, 2 bitmap::{MapPoint, Pixmap},
3 brush::Brush,
3 command::CommandBox, 4 command::CommandBox,
4 consts::{colors::*, FONT_PATH}, 5 consts::{colors::*, FONT_PATH},
5 dither, 6 dither,
6 lisp::{eval, lex::Lexer, parse::Parser, prelude, EnvList, Environment}, 7 lisp::{eval, lex::Lexer, parse::Parser, prelude, EnvList},
7 message::Message, 8 message::Message,
8 rect, 9 rect,
9 symmetry::Symmetry, 10 symmetry::Symmetry,
@@ -11,12 +12,7 @@ use crate::{
11 utils::{draw_text, is_copy_event, is_paste_event}, 12 utils::{draw_text, is_copy_event, is_paste_event},
12}; 13};
13 14
14use std::{ 15use std::{convert::From, fs::File, io::prelude::*, path::Path};
15 convert::From,
16 fs::File,
17 io::prelude::*,
18 path::{Path, PathBuf},
19};
20 16
21use obi::Image; 17use obi::Image;
22use sdl2::{ 18use sdl2::{
@@ -40,6 +36,7 @@ pub enum Mode {
40pub struct AppState<'ctx, 'file> { 36pub struct AppState<'ctx, 'file> {
41 pub active_color: bool, 37 pub active_color: bool,
42 pub brush_size: u8, 38 pub brush_size: u8,
39 pub brush: Brush,
43 pub canvas: Canvas<Window>, 40 pub canvas: Canvas<Window>,
44 pub command_box: CommandBox, 41 pub command_box: CommandBox,
45 pub context: &'ctx Sdl, 42 pub context: &'ctx Sdl,
@@ -204,7 +201,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
204 val, 201 val,
205 } in op.into_iter() 202 } in op.into_iter()
206 { 203 {
207 if self.pixmap.is_inside(point) { 204 if self.pixmap.contains(point) {
208 match op_kind { 205 match op_kind {
209 OpKind::Undo => self.pixmap.set(point, old_val), 206 OpKind::Undo => self.pixmap.set(point, old_val),
210 OpKind::Redo => self.pixmap.set(point, val), 207 OpKind::Redo => self.pixmap.set(point, val),
@@ -346,12 +343,13 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
346 format!("---, ---") 343 format!("---, ---")
347 }; 344 };
348 let status_text = format!( 345 let status_text = format!(
349 "[DITHER {}][BRUSH {}][SYM {}][PT {}][ACTIVE {}]", 346 "[DITHER {}][BRUSH {}][SYM {}][PT {}][ACTIVE {}][KIND {}]",
350 self.dither_level, 347 self.dither_level,
351 self.brush_size + 1, 348 self.brush_size + 1,
352 self.symmetry, 349 self.symmetry,
353 mouse_coords, 350 mouse_coords,
354 if self.active_color { "WHT" } else { "BLK" } 351 if self.active_color { "WHT" } else { "BLK" },
352 self.brush
355 ); 353 );
356 draw_text( 354 draw_text(
357 &mut self.canvas, 355 &mut self.canvas,
@@ -426,13 +424,31 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
426 } 424 }
427 } 425 }
428 426
427 fn draw_symmetry(&mut self) {
428 let (winsize_x, winsize_y) = self.canvas.window().size();
429 let cs = self.zoom as u32;
430 let Symmetry { x: sym_x, y: sym_y } = self.symmetry;
431 if let Some(line) = sym_x {
432 self.canvas.set_draw_color(CYAN);
433 let line_coord = (line * cs) as i32 + self.start.y() + (cs / 2) as i32;
434 self.canvas
435 .draw_line((0, line_coord), (winsize_x as i32, line_coord))
436 .unwrap();
437 }
438 if let Some(line) = sym_y {
439 self.canvas.set_draw_color(CYAN);
440 let line_coord = (line * cs) as i32 + self.start.x() + (cs / 2) as i32;
441 self.canvas
442 .draw_line((line_coord, 0), (line_coord, winsize_y as i32))
443 .unwrap();
444 }
445 }
446
429 fn draw(&mut self) { 447 fn draw(&mut self) {
430 let cs = self.zoom as u32; 448 let cs = self.zoom as u32;
431 let (width, height) = (self.width(), self.height()); 449 let (width, height) = (self.width(), self.height());
432 let start = self.start; 450 let start = self.start;
433 let grid_enabled = self.grid.enabled; 451 let grid_enabled = self.grid.enabled;
434 let (winsize_x, winsize_y) = self.canvas.window().size();
435 let Symmetry { x: sym_x, y: sym_y } = self.symmetry;
436 for (idx, val) in self.pixmap.data.iter().enumerate() { 452 for (idx, val) in self.pixmap.data.iter().enumerate() {
437 if *val { 453 if *val {
438 let idx = idx as i32; 454 let idx = idx as i32;
@@ -451,20 +467,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
451 if grid_enabled { 467 if grid_enabled {
452 self.draw_grid(); 468 self.draw_grid();
453 } 469 }
454 if let Some(line) = sym_x { 470 self.draw_symmetry();
455 self.canvas.set_draw_color(CYAN);
456 let line_coord = (line * cs) as i32 + self.start.y() + (cs / 2) as i32;
457 self.canvas
458 .draw_line((0, line_coord), (winsize_x as i32, line_coord))
459 .unwrap();
460 }
461 if let Some(line) = sym_y {
462 self.canvas.set_draw_color(CYAN);
463 let line_coord = (line * cs) as i32 + self.start.x() + (cs / 2) as i32;
464 self.canvas
465 .draw_line((line_coord, 0), (line_coord, winsize_y as i32))
466 .unwrap();
467 }
468 self.draw_statusline(); 471 self.draw_statusline();
469 self.draw_command_box(); 472 self.draw_command_box();
470 self.draw_mouse(); 473 self.draw_mouse();
@@ -515,6 +518,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
515 Self { 518 Self {
516 active_color: true, 519 active_color: true,
517 brush_size: 0, 520 brush_size: 0,
521 brush: Brush::new(),
518 canvas, 522 canvas,
519 command_box: CommandBox::new(), 523 command_box: CommandBox::new(),
520 context, 524 context,
@@ -602,22 +606,14 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
602 Keycode::I => self.pixmap.invert(), 606 Keycode::I => self.pixmap.invert(),
603 // line drawing 607 // line drawing
604 Keycode::F => { 608 Keycode::F => {
605 let end: Point = (mouse.x(), mouse.y()).into(); 609 if matches!(self.brush, Brush::Line { extend: false, .. }) {
606 if let Some(start) = self.last_point { 610 self.brush = Brush::line(0, true);
607 if let Ok(o) = 611 } else {
608 self.paint_line(start, end, self.active_color) 612 self.brush = Brush::line(0, false);
609 {
610 self.commit_operation();
611 self.current_operation = o
612 .into_iter()
613 .filter(|v| !v.old_val == v.val)
614 .collect();
615 self.commit_operation();
616 self.last_point =
617 Some(self.idx_at_coord(end).unwrap().into());
618 }
619 } 613 }
620 } 614 }
615 // bucket fill tool
616 Keycode::B => self.brush = Brush::Fill,
621 Keycode::V => self.cycle_symmetry(), 617 Keycode::V => self.cycle_symmetry(),
622 // undo & redo 618 // undo & redo
623 Keycode::U => { 619 Keycode::U => {
@@ -646,13 +642,63 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
646 x, y, mouse_btn, .. 642 x, y, mouse_btn, ..
647 } => { 643 } => {
648 let pt = (x, y); 644 let pt = (x, y);
645 let contact = self.idx_at_coord(pt).map(MapPoint::from);
649 self.last_point = self.idx_at_coord(pt).map(MapPoint::from); 646 self.last_point = self.idx_at_coord(pt).map(MapPoint::from);
650 let val = match mouse_btn { 647 let val = match mouse_btn {
651 MouseButton::Right => !self.active_color, 648 MouseButton::Right => !self.active_color,
652 _ => self.active_color, 649 _ => self.active_color,
653 }; 650 };
654 if let Ok(o) = self.paint_point(pt, val) { 651 match self.brush {
655 self.current_operation.extend(o); 652 Brush::Circle { size } => {
653 if let Ok(o) = self.paint_point(pt, val) {
654 self.current_operation.extend(o);
655 }
656 }
657 Brush::Line {
658 size,
659 start,
660 extend,
661 } => {
662 if start.is_none() {
663 self.brush = Brush::Line {
664 size,
665 start: contact,
666 extend,
667 };
668 } else if let Ok(o) =
669 self.paint_line(start.unwrap(), pt, val)
670 {
671 self.current_operation.extend(o);
672 self.brush = Brush::Line {
673 size,
674 start: if extend { contact } else { None },
675 extend,
676 };
677 }
678 }
679 Brush::Fill => {
680 if let Some(c) = contact {
681 let target = self.pixmap.get(c);
682 let mut operation = vec![];
683 self.pixmap.flood_fill(
684 c,
685 target,
686 self.active_color,
687 &mut operation,
688 );
689 self.current_operation.extend(
690 operation
691 .into_iter()
692 .map(|point| ModifyRecord {
693 point,
694 old_val: target,
695 val: self.active_color,
696 })
697 .collect::<Vec<ModifyRecord>>(),
698 )
699 }
700 }
701 _ => {}
656 } 702 }
657 } 703 }
658 // click and drag 704 // click and drag
@@ -674,14 +720,7 @@ impl<'ctx, 'file> AppState<'ctx, 'file> {
674 } 720 }
675 } 721 }
676 // end of operation 722 // end of operation
677 Event::MouseButtonUp { .. } => { 723 Event::MouseButtonUp { .. } => self.commit_operation(),
678 let op = self
679 .current_operation
680 .drain(..)
681 .filter(|v| !v.old_val == v.val)
682 .collect::<Vec<_>>();
683 self.undo_stack.push(op);
684 }
685 Event::Quit { .. } => { 724 Event::Quit { .. } => {
686 break 'running; 725 break 'running;
687 } 726 }
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 {
54 type Output = Self; 54 type Output = Self;
55 fn add(self, rhs: Self) -> Self::Output { 55 fn add(self, rhs: Self) -> Self::Output {
56 MapPoint { 56 MapPoint {
57 x: self.x + rhs.x, 57 x: self.x.saturating_add(rhs.x),
58 y: self.y + rhs.y, 58 y: self.y.saturating_add(rhs.y),
59 } 59 }
60 } 60 }
61} 61}
@@ -64,8 +64,8 @@ impl Sub for MapPoint {
64 type Output = Self; 64 type Output = Self;
65 fn sub(self, rhs: Self) -> Self::Output { 65 fn sub(self, rhs: Self) -> Self::Output {
66 MapPoint { 66 MapPoint {
67 x: self.x - rhs.x, 67 x: self.x.saturating_sub(rhs.x),
68 y: self.y - rhs.y, 68 y: self.y.saturating_sub(rhs.y),
69 } 69 }
70 } 70 }
71} 71}
@@ -102,7 +102,7 @@ impl MapPoint {
102 102
103impl<T> Pixmap<T> 103impl<T> Pixmap<T>
104where 104where
105 T: Copy + Clone + Default, 105 T: Copy + Clone + Default + PartialEq,
106{ 106{
107 pub fn new(width: u32, height: u32) -> Self { 107 pub fn new(width: u32, height: u32) -> Self {
108 let data = vec![Default::default(); (width * height) as usize]; 108 let data = vec![Default::default(); (width * height) as usize];
@@ -121,7 +121,7 @@ where
121 } 121 }
122 } 122 }
123 123
124 pub fn is_inside<P: Into<MapPoint>>(&self, pt: P) -> bool { 124 pub fn contains<P: Into<MapPoint>>(&self, pt: P) -> bool {
125 let MapPoint { x, y } = pt.into(); 125 let MapPoint { x, y } = pt.into();
126 x < self.width && y < self.height 126 x < self.width && y < self.height
127 } 127 }
@@ -181,7 +181,7 @@ where
181 circle 181 circle
182 .into_iter() 182 .into_iter()
183 .flat_map(|pt| MapPoint::try_from(pt)) 183 .flat_map(|pt| MapPoint::try_from(pt))
184 .filter(|&pt| self.is_inside(pt)) 184 .filter(|&pt| self.contains(pt))
185 .collect() 185 .collect()
186 } 186 }
187 187
@@ -226,9 +226,30 @@ where
226 coordinates 226 coordinates
227 .into_iter() 227 .into_iter()
228 .flat_map(|pt| MapPoint::try_from(pt)) 228 .flat_map(|pt| MapPoint::try_from(pt))
229 .filter(|&pt| self.is_inside(pt)) 229 .filter(|&pt| self.contains(pt))
230 .collect() 230 .collect()
231 } 231 }
232
233 pub fn flood_fill(
234 &mut self,
235 start: MapPoint,
236 target: T,
237 replacement: T,
238 pts: &mut Vec<MapPoint>,
239 ) {
240 if !self.contains(start) || self.get(start) != target || self.get(start) == replacement {
241 return;
242 } else {
243 pts.push(start);
244 self.set(start, replacement);
245 for (x, y) in [(1, 0), (0, 1), (-1, 0), (0, -1)].iter() {
246 let dir = MapPoint::try_from((start.x as i64 - x, start.y as i64 + y));
247 if let Ok(pt) = dir {
248 self.flood_fill(pt, target, replacement, pts);
249 }
250 }
251 }
252 }
232} 253}
233 254
234impl Pixmap<bool> { 255impl Pixmap<bool> {