import { BaseTool } from './base-tool'; import type { PointerState, ToolSettings } from '@/types'; import { distance } from '@/lib/utils'; /** * Brush tool - Variable size and opacity with smooth strokes */ export class BrushTool extends BaseTool { private lastX = 0; private lastY = 0; constructor() { super('Brush'); } onPointerDown( pointer: PointerState, ctx: CanvasRenderingContext2D, settings: ToolSettings ): void { this.isDrawing = true; this.lastX = pointer.x; this.lastY = pointer.y; // Draw initial stamp this.drawStamp(pointer.x, pointer.y, ctx, settings); } onPointerMove( pointer: PointerState, ctx: CanvasRenderingContext2D, settings: ToolSettings ): void { if (!this.isDrawing) return; // Calculate distance from last point const dist = distance(this.lastX, this.lastY, pointer.x, pointer.y); const spacing = settings.size * settings.spacing; if (dist >= spacing) { // Interpolate between points for smooth stroke const steps = Math.ceil(dist / spacing); for (let i = 1; i <= steps; i++) { const t = i / steps; const x = this.lastX + (pointer.x - this.lastX) * t; const y = this.lastY + (pointer.y - this.lastY) * t; this.drawStamp(x, y, ctx, settings); } this.lastX = pointer.x; this.lastY = pointer.y; } } onPointerUp( pointer: PointerState, ctx: CanvasRenderingContext2D, settings: ToolSettings ): void { this.isDrawing = false; } /** * Draw a single brush stamp */ private drawStamp( x: number, y: number, ctx: CanvasRenderingContext2D, settings: ToolSettings ): void { const size = settings.size; const hardness = settings.hardness; const opacity = settings.opacity * settings.flow; // Create radial gradient for soft brush const gradient = ctx.createRadialGradient(x, y, 0, x, y, size / 2); // Parse color to add alpha const color = settings.color; if (hardness >= 1) { // Hard brush gradient.addColorStop(0, color); gradient.addColorStop(1, color); } else { // Soft brush with hardness gradient.addColorStop(0, color); gradient.addColorStop(hardness, color); gradient.addColorStop(1, color.replace('rgb', 'rgba').replace(')', ', 0)')); } ctx.save(); ctx.globalAlpha = opacity; ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(x, y, size / 2, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } getCursor(settings: ToolSettings): string { return 'crosshair'; } }