Graphics & Retro Toys
PIXEL ART CANVAS STUDIO
Draw retro pixel art sprites in your browser. Grid size controls, paint bucket fill, color palettes, and instant PNG export.
Drawing Tool
Color Swatches
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
const canvas = document.getElementById('pixel-canvas');
const ctx = canvas.getContext('2d');
const colorInput = document.getElementById('input-color');
const sizeSelect = document.getElementById('select-size');
const swatchesContainer = document.getElementById('swatches');
const toast = document.getElementById('toast');
let gridSize = 16;
let cellSize = canvas.width / gridSize;
let currentColor = '#ff2200';
let currentTool = 'pencil';
let isDrawing = false;
let gridData = [];
const PALETTE = ['#ff2200', '#ff7700', '#ffcc00', '#00c896', '#00e5ff', '#3b82f6', '#8b5cf6', '#ec4899', '#ffffff', '#000000'];
function initSwatches() {
swatchesContainer.innerHTML = '';
PALETTE.forEach(c => {
const s = document.createElement('div');
s.className = 'swatch' + (c === currentColor ? ' active' : '');
s.style.backgroundColor = c;
s.addEventListener('click', () => {
document.querySelectorAll('.swatch').forEach(x => x.classList.remove('active'));
s.classList.add('active');
currentColor = c;
colorInput.value = c;
});
swatchesContainer.appendChild(s);
});
}
function initGrid() {
gridSize = parseInt(sizeSelect.value);
cellSize = canvas.width / gridSize;
gridData = Array(gridSize).fill().map(() => Array(gridSize).fill(null));
drawCanvas();
}
function drawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < gridSize; r++) {
for (let c = 0; c < gridSize; c++) {
if (gridData[r][c]) {
ctx.fillStyle = gridData[r][c];
ctx.fillRect(c * cellSize, r * cellSize, cellSize, cellSize);
}
ctx.strokeStyle = 'rgba(255,255,255,0.05)';
ctx.strokeRect(c * cellSize, r * cellSize, cellSize, cellSize);
}
}
}
function getCellCoords(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const col = Math.floor(x / (rect.width / gridSize));
const row = Math.floor(y / (rect.height / gridSize));
return { row, col };
}
function drawPixel(row, col) {
if (row < 0 || row >= gridSize || col < 0 || col >= gridSize) return;
if (currentTool === 'pencil') {
gridData[row][col] = currentColor;
} else if (currentTool === 'eraser') {
gridData[row][col] = null;
}
drawCanvas();
}
function floodFill(row, col, targetColor, replacementColor) {
if (row < 0 || row >= gridSize || col < 0 || col >= gridSize) return;
if (gridData[row][col] !== targetColor) return;
if (targetColor === replacementColor) return;
gridData[row][col] = replacementColor;
floodFill(row + 1, col, targetColor, replacementColor);
floodFill(row - 1, col, targetColor, replacementColor);
floodFill(row, col + 1, targetColor, replacementColor);
floodFill(row, col - 1, targetColor, replacementColor);
}
canvas.addEventListener('mousedown', e => {
isDrawing = true;
const { row, col } = getCellCoords(e);
if (currentTool === 'bucket') {
const target = gridData[row][col];
floodFill(row, col, target, currentColor);
drawCanvas();
} else {
drawPixel(row, col);
}
});
canvas.addEventListener('mousemove', e => {
if (!isDrawing || currentTool === 'bucket') return;
const { row, col } = getCellCoords(e);
drawPixel(row, col);
});
window.addEventListener('mouseup', () => isDrawing = false);
document.querySelectorAll('.btn-tool[data-tool]').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.btn-tool[data-tool]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentTool = btn.dataset.tool;
});
});
colorInput.addEventListener('input', () => currentColor = colorInput.value);
sizeSelect.addEventListener('change', initGrid);
document.getElementById('btn-clear').addEventListener('click', initGrid);
document.getElementById('btn-download-png').addEventListener('click', () => {
const exportCanvas = document.createElement('canvas');
exportCanvas.width = gridSize * 16;
exportCanvas.height = gridSize * 16;
const eCtx = exportCanvas.getContext('2d');
eCtx.imageSmoothingEnabled = false;
for (let r = 0; r < gridSize; r++) {
for (let c = 0; c < gridSize; c++) {
if (gridData[r][c]) {
eCtx.fillStyle = gridData[r][c];
eCtx.fillRect(c * 16, r * 16, 16, 16);
}
}
}
const a = document.createElement('a');
a.href = exportCanvas.toDataURL('image/png');
a.download = 'pixel-art.png';
a.click();
});
initSwatches();
initGrid();