Implement flood fill
parent
b851faef53
commit
2ed69cf167
|
@ -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>
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue