2026-10-22 14:45:19 +00:00
|
|
|
import { ctx, canvas, grid, cols, rows, cellSize, brushSize, cursor } from './state.js';
|
2026-10-22 14:05:51 +00:00
|
|
|
import { config } from './config.js';
|
|
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
let offscreenCanvas = document.createElement('canvas');
|
|
|
|
|
let offscreenCtx = offscreenCanvas.getContext('2d', { willReadFrequently: false });
|
|
|
|
|
let imgData = null;
|
|
|
|
|
let pixelBuffer = null;
|
|
|
|
|
let lastCols = 0;
|
|
|
|
|
let lastRows = 0;
|
2026-10-22 14:05:51 +00:00
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
const BG_COLOR = 0xFF151211; // 0xAABBGGRR for #111215
|
2026-10-22 14:05:51 +00:00
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
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);
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|
2026-10-22 14:45:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function draw() {
|
|
|
|
|
if (cols <= 0 || rows <= 0) return;
|
|
|
|
|
ensureBuffer();
|
|
|
|
|
|
|
|
|
|
const totalPixels = cols * rows;
|
|
|
|
|
const style = config.visual.style;
|
2026-10-22 14:05:51 +00:00
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
// 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;
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
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);
|
|
|
|
|
|
2026-10-22 14:05:51 +00:00
|
|
|
if (style === 'fuzzy') {
|
|
|
|
|
ctx.filter = 'none';
|
|
|
|
|
}
|
2026-10-22 14:45:19 +00:00
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
}
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|