import { ctx, canvas, grid, cols, rows, cellSize, brushSize, cursor } from './state.js'; import { config } from './config.js'; let offscreenCanvas = document.createElement('canvas'); let offscreenCtx = offscreenCanvas.getContext('2d', { willReadFrequently: false }); let imgData = null; let pixelBuffer = null; let lastCols = 0; let lastRows = 0; const BG_COLOR = 0xFF151211; // 0xAABBGGRR for #111215 function ensureBuffer() { if (cols !== lastCols || rows !== lastRows || !imgData) { lastCols = cols; lastRows = rows; offscreenCanvas.width = cols; offscreenCanvas.height = rows; imgData = offscreenCtx.createImageData(cols, rows); pixelBuffer = new Uint32Array(imgData.data.buffer); } } export function draw() { if (cols <= 0 || rows <= 0) return; ensureBuffer(); const totalPixels = cols * rows; const style = config.visual.style; // Fast-fill background or render particles for (let y = 0; y < rows; y++) { const rowOffset = y * cols; for (let x = 0; x < cols; x++) { const cell = grid[x][y]; if (cell) { pixelBuffer[rowOffset + x] = cell.u32Color || 0xFFFFFFFF; } else { pixelBuffer[rowOffset + x] = BG_COLOR; } } } offscreenCtx.putImageData(imgData, 0, 0); // Blit to main canvas with desired smoothing ctx.imageSmoothingEnabled = (style === 'fuzzy' || style === 'bloby'); if (style === 'fuzzy') { ctx.filter = 'blur(1px)'; } else { ctx.filter = 'none'; } ctx.drawImage(offscreenCanvas, 0, 0, canvas.width, canvas.height); if (style === 'fuzzy') { ctx.filter = 'none'; } // Brush cursor indicator if (cursor.visible && cursor.x >= 0 && cursor.y >= 0) { ctx.save(); ctx.beginPath(); const screenRadius = Math.max(cellSize, brushSize * cellSize); ctx.arc(cursor.x, cursor.y, screenRadius, 0, Math.PI * 2); ctx.strokeStyle = 'rgba(255, 255, 255, 0.45)'; ctx.lineWidth = 1.5; ctx.setLineDash([4, 4]); ctx.stroke(); ctx.restore(); } }