Implement flood fill

main
kts of kettek 2024-02-14 15:49:55 -08:00
parent b851faef53
commit 2ed69cf167
3 changed files with 60 additions and 4 deletions

View File

@ -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 @@
</section>
<menu class='toolbar'>
<Button disabled kind="ghost" size="small" icon={Select_01} iconDescription="selection" tooltipPosition="right"></Button>
<Button disabled kind="ghost" size="small" icon={RainDrop} iconDescription="fill" tooltipPosition="right"></Button>
<Button isSelected={currentTool === toolFill} kind="ghost" size="small" icon={RainDrop} iconDescription="fill" tooltipPosition="right" on:click={()=>swapTool(toolFill)}></Button>
<Button isSelected={currentTool === toolBrush} kind="ghost" size="small" icon={PaintBrushAlt} iconDescription="paint" tooltipPosition="right" on:click={()=>swapTool(toolBrush)}></Button>
<Button isSelected={currentTool === toolErase} kind="ghost" size="small" icon={Erase} iconDescription="erase" tooltipPosition="right" on:click={()=>swapTool(toolErase)}></Button>
</menu>

View File

@ -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 })
}
}
}

View File

@ -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<number>()
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
}
}