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();
Downloaded PNG!