import { BaseTool } from './base-tool'; import type { PointerState, ToolSettings } from '@/types'; /** * Fill tool - Flood fill algorithm */ export class FillTool extends BaseTool { constructor() { super('Fill'); } onPointerDown( pointer: PointerState, ctx: CanvasRenderingContext2D, settings: ToolSettings ): void { const x = Math.floor(pointer.x); const y = Math.floor(pointer.y); this.floodFill(x, y, settings.color, ctx); } onPointerMove(): void { // No-op for fill tool } onPointerUp(): void { // No-op for fill tool } /** * Flood fill implementation using scanline algorithm */ private floodFill( startX: number, startY: number, fillColor: string, ctx: CanvasRenderingContext2D ): void { const canvas = ctx.canvas; const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Get target color at start position const startPos = (startY * canvas.width + startX) * 4; const targetR = data[startPos]; const targetG = data[startPos + 1]; const targetB = data[startPos + 2]; const targetA = data[startPos + 3]; // Convert fill color to RGBA const tempCanvas = document.createElement('canvas'); tempCanvas.width = 1; tempCanvas.height = 1; const tempCtx = tempCanvas.getContext('2d')!; tempCtx.fillStyle = fillColor; tempCtx.fillRect(0, 0, 1, 1); const fillData = tempCtx.getImageData(0, 0, 1, 1).data; const fillR = fillData[0]; const fillG = fillData[1]; const fillB = fillData[2]; const fillA = fillData[3]; // Check if target and fill colors are the same if ( targetR === fillR && targetG === fillG && targetB === fillB && targetA === fillA ) { return; // No need to fill } // Scanline flood fill const stack: [number, number][] = [[startX, startY]]; const visited = new Set(); while (stack.length > 0) { const [x, y] = stack.pop()!; if (x < 0 || x >= canvas.width || y < 0 || y >= canvas.height) continue; const key = `${x},${y}`; if (visited.has(key)) continue; visited.add(key); const pos = (y * canvas.width + x) * 4; // Check if pixel matches target color if ( data[pos] !== targetR || data[pos + 1] !== targetG || data[pos + 2] !== targetB || data[pos + 3] !== targetA ) { continue; } // Fill pixel data[pos] = fillR; data[pos + 1] = fillG; data[pos + 2] = fillB; data[pos + 3] = fillA; // Add neighbors to stack stack.push([x + 1, y]); stack.push([x - 1, y]); stack.push([x, y + 1]); stack.push([x, y - 1]); } // Put modified image data back ctx.putImageData(imageData, 0, 0); } }