/** * Create a new canvas element with specified dimensions */ export function createCanvas(width: number, height: number): HTMLCanvasElement { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; return canvas; } /** * Get 2D context from canvas with error handling */ export function getContext(canvas: HTMLCanvasElement): CanvasRenderingContext2D { const ctx = canvas.getContext('2d', { willReadFrequently: true }); if (!ctx) { throw new Error('Failed to get 2D context'); } return ctx; } /** * Clear entire canvas */ export function clearCanvas(ctx: CanvasRenderingContext2D): void { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); } /** * Fill canvas with color */ export function fillCanvas(ctx: CanvasRenderingContext2D, color: string): void { ctx.fillStyle = color; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); } /** * Draw checkerboard pattern (for transparency) */ export function drawCheckerboard( ctx: CanvasRenderingContext2D, squareSize = 10, color1 = '#ffffff', color2 = '#cccccc', width?: number, height?: number ): void { const w = width ?? ctx.canvas.width; const h = height ?? ctx.canvas.height; for (let y = 0; y < h; y += squareSize) { for (let x = 0; x < w; x += squareSize) { const isEven = (Math.floor(x / squareSize) + Math.floor(y / squareSize)) % 2 === 0; ctx.fillStyle = isEven ? color1 : color2; ctx.fillRect(x, y, squareSize, squareSize); } } } /** * Clone a canvas */ export function cloneCanvas(source: HTMLCanvasElement): HTMLCanvasElement { const clone = createCanvas(source.width, source.height); const ctx = getContext(clone); ctx.drawImage(source, 0, 0); return clone; } /** * Resize canvas maintaining content */ export function resizeCanvas( canvas: HTMLCanvasElement, newWidth: number, newHeight: number ): void { const tempCanvas = cloneCanvas(canvas); canvas.width = newWidth; canvas.height = newHeight; const ctx = getContext(canvas); ctx.drawImage(tempCanvas, 0, 0); } /** * Get image data safely */ export function getImageData( ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number ): ImageData | null { try { return ctx.getImageData(x, y, width, height); } catch (e) { console.error('Failed to get image data:', e); return null; } } /** * Put image data safely */ export function putImageData( ctx: CanvasRenderingContext2D, imageData: ImageData, x: number, y: number ): void { try { ctx.putImageData(imageData, x, y); } catch (e) { console.error('Failed to put image data:', e); } } /** * Convert canvas to blob */ export async function canvasToBlob( canvas: HTMLCanvasElement, type = 'image/png', quality = 1 ): Promise { return new Promise((resolve) => { canvas.toBlob((blob) => resolve(blob), type, quality); }); } /** * Load image from URL */ export async function loadImage(url: string): Promise { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => resolve(img); img.onerror = reject; img.src = url; }); } /** * Load image from File */ export async function loadImageFromFile(file: File): Promise { const url = URL.createObjectURL(file); try { const img = await loadImage(url); URL.revokeObjectURL(url); return img; } catch (e) { URL.revokeObjectURL(url); throw e; } } /** * Draw grid on canvas */ export function drawGrid( ctx: CanvasRenderingContext2D, gridSize: number, color = 'rgba(0, 0, 0, 0.1)', width?: number, height?: number ): void { const w = width ?? ctx.canvas.width; const h = height ?? ctx.canvas.height; ctx.strokeStyle = color; ctx.lineWidth = 1; // Vertical lines for (let x = 0; x <= w; x += gridSize) { ctx.beginPath(); ctx.moveTo(x + 0.5, 0); ctx.lineTo(x + 0.5, h); ctx.stroke(); } // Horizontal lines for (let y = 0; y <= h; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y + 0.5); ctx.lineTo(w, y + 0.5); ctx.stroke(); } }