From 2ed69cf167d31159341e9960b977b0addb5d7d70 Mon Sep 17 00:00:00 2001 From: kts of kettek Date: Wed, 14 Feb 2024 15:49:55 -0800 Subject: [PATCH] Implement flood fill --- frontend/src/App.svelte | 5 +-- frontend/src/sections/Editor2D.svelte | 10 +++++- frontend/src/types/tools.ts | 49 ++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 94dd5fb..a5429e4 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -17,7 +17,7 @@ import { Close, Erase, PaintBrushAlt, RainDrop, Redo, Select_01, Undo } from "carbon-icons-svelte" import StackPreview from './sections/StackPreview.svelte' import type { Canvas } from './types/canvas' - import { BrushTool, EraserTool, type Tool } from './types/tools'; + import { BrushTool, EraserTool, FillTool, type Tool } from './types/tools'; let theme: 'white'|'g10'|'g80'|'g90'|'g100' = 'g90' @@ -34,6 +34,7 @@ let showPreview: boolean = false // let toolSelect = new SelectTool() + let toolFill = new FillTool() let toolErase = new EraserTool() let toolBrush = new BrushTool() let currentTool: Tool = toolBrush @@ -99,7 +100,7 @@ - + diff --git a/frontend/src/sections/Editor2D.svelte b/frontend/src/sections/Editor2D.svelte index 357d3a6..ab65131 100644 --- a/frontend/src/sections/Editor2D.svelte +++ b/frontend/src/sections/Editor2D.svelte @@ -4,7 +4,7 @@ import type { data } from '../../wailsjs/go/models.ts' import type { LoadedFile } from '../types/file' import type { PixelPosition } from '../types/shapes' - import { BrushTool, EraserTool, type Tool } from '../types/tools' + import { BrushTool, EraserTool, FillTool, type Tool } from '../types/tools' export let file: LoadedFile export let animation: data.Animation @@ -189,6 +189,10 @@ currentTool.pointerDown({file, brushSize: 3, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: e.button }) } else if (currentTool instanceof EraserTool) { currentTool.pointerDown({file, brushSize: 3}, {x: mousePixelX, y: mousePixelY, id: e.button }) + } else if (currentTool instanceof FillTool) { + currentTool.pointerDown({file, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: e.button }) + } else { + currentTool.pointerDown({file}, {x: mousePixelX, y: mousePixelY, id: e.button }) } } }) @@ -252,6 +256,10 @@ currentTool.pointerMove({file, brushSize: 3, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: 0 }) } else if (currentTool instanceof EraserTool) { currentTool.pointerMove({file, brushSize: 3}, {x: mousePixelX, y: mousePixelY, id: 0 }) + } else if (currentTool instanceof FillTool) { + currentTool.pointerMove({file, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: 0 }) + } else { + currentTool.pointerMove({file}, {x: mousePixelX, y: mousePixelY, id: 0 }) } } } diff --git a/frontend/src/types/tools.ts b/frontend/src/types/tools.ts index 60411ce..c5b782c 100644 --- a/frontend/src/types/tools.ts +++ b/frontend/src/types/tools.ts @@ -1,5 +1,5 @@ import { PixelPlaceUndoable, type LoadedFile, PixelsPlaceUndoable } from "./file" -import { FilledCircle } from "./shapes" +import { FilledCircle, type PixelPosition } from "./shapes" export interface ToolContext { file: LoadedFile @@ -27,6 +27,10 @@ export interface EraserToolContext { brushSize: number } +export interface FloodToolContext { + colorIndex: number +} + export class BrushTool implements Tool { private active: boolean isActive(): boolean { @@ -88,4 +92,47 @@ export class EraserTool extends BrushTool { pointerMove(ctx: ToolContext & EraserToolContext, ptr: Pointer) { super.pointerMove({...ctx, colorIndex: 0}, ptr) } +} + +export class FillTool implements Tool { + private active: boolean + isActive(): boolean { + return this.active + } + + private pixels: PixelPosition[] = [] + + pointerDown(ctx: ToolContext & FloodToolContext, ptr: Pointer) { + this.active = true + this.pixels = [] + + let traversed = new Set() + + let p = ctx.file.canvas.getPixel(ptr.x, ptr.y) + if (p !== -1) { + let queue = [{x: ptr.x, y: ptr.y}] + while (queue.length > 0) { + let {x, y} = queue.shift() + let index = y * ctx.file.canvas.width + x + if (traversed.has(index)) { + continue + } + traversed.add(index) + let p2 = ctx.file.canvas.getPixel(x, y) + if (p2 === p) { + this.pixels.push({x, y, index: ctx.colorIndex}) + if (x > 0) queue.push({x: x-1, y}) + if (x < ctx.file.canvas.width-1) queue.push({x: x+1, y}) + if (y > 0) queue.push({x, y: y-1}) + if (y < ctx.file.canvas.height-1) queue.push({x, y: y+1}) + } + } + } + } + pointerMove(ctx: ToolContext & FloodToolContext, ptr: Pointer) { + } + pointerUp(ctx: ToolContext & FloodToolContext, ptr: Pointer) { + ctx.file.push(new PixelsPlaceUndoable(this.pixels)) + this.active = false + } } \ No newline at end of file