Creative

PIXEL ART EDITOR

Draw pixel art with pencil, fill, and eraser tools. Export your creation as PNG.

|
Recent:
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('pixelCanvas');
const ctx = canvas.getContext('2d');
let gridSize = 16, cellSize, currentColor = '#ff2200', currentTool = 'pencil';
let pixels = [], drawing = false;
let history = [], historyIdx = -1;
let recentColors = ['#ff2200','#ffffff','#000000','#00c896','#f5c518'];

const defaultPalette = [
  '#000000','#ffffff','#ff2200','#ff6600','#ffcc00','#00c896','#0088ff',
  '#8844ff','#ff44aa','#884422','#666666','#aaaaaa','#ff5555','#55ff55',
  '#5555ff','#ffff55'
];

function init() {
  const maxW = Math.min(500, window.innerWidth - 48);
  cellSize = Math.floor(maxW / gridSize);
  canvas.width = cellSize * gridSize;
  canvas.height = cellSize * gridSize;
  pixels = Array.from({ length: gridSize }, () => Array(gridSize).fill(null));
  history = []; historyIdx = -1;
  saveState();
  draw();
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // Checkerboard background for transparency
  for (let y = 0; y < gridSize; y++) {
    for (let x = 0; x < gridSize; x++) {
      ctx.fillStyle = (x + y) % 2 === 0 ? '#1a1a1a' : '#222222';
      ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
      if (pixels[y][x]) {
        ctx.fillStyle = pixels[y][x];
        ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
      }
    }
  }
  if (document.getElementById('gridToggle').checked && cellSize > 4) {
    ctx.strokeStyle = 'rgba(255,255,255,0.08)';
    ctx.lineWidth = 0.5;
    for (let i = 0; i <= gridSize; i++) {
      ctx.beginPath(); ctx.moveTo(i * cellSize, 0); ctx.lineTo(i * cellSize, canvas.height); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(0, i * cellSize); ctx.lineTo(canvas.width, i * cellSize); ctx.stroke();
    }
  }
}

function getCell(e) {
  const rect = canvas.getBoundingClientRect();
  const cx = (e.clientX || e.touches[0].clientX) - rect.left;
  const cy = (e.clientY || e.touches[0].clientY) - rect.top;
  return { x: Math.floor(cx / cellSize), y: Math.floor(cy / cellSize) };
}

function applyTool(x, y) {
  if (x < 0 || y < 0 || x >= gridSize || y >= gridSize) return;
  if (currentTool === 'pencil') {
    pixels[y][x] = currentColor;
    addRecent(currentColor);
  } else if (currentTool === 'eraser') {
    pixels[y][x] = null;
  } else if (currentTool === 'fill') {
    floodFill(x, y, pixels[y][x], currentColor);
    addRecent(currentColor);
  } else if (currentTool === 'picker') {
    if (pixels[y][x]) {
      currentColor = pixels[y][x];
      document.getElementById('colorPicker').value = currentColor;
    }
  }
  draw();
}

function floodFill(x, y, target, fill) {
  if (target === fill) return;
  const stack = [[x, y]];
  while (stack.length) {
    const [cx, cy] = stack.pop();
    if (cx < 0 || cy < 0 || cx >= gridSize || cy >= gridSize) continue;
    if (pixels[cy][cx] !== target) continue;
    pixels[cy][cx] = fill;
    stack.push([cx+1,cy],[cx-1,cy],[cx,cy+1],[cx,cy-1]);
  }
}

canvas.addEventListener('mousedown', e => { drawing = true; const c = getCell(e); applyTool(c.x, c.y); });
canvas.addEventListener('mousemove', e => { if (drawing && (currentTool === 'pencil' || currentTool === 'eraser')) { const c = getCell(e); applyTool(c.x, c.y); } });
canvas.addEventListener('mouseup', () => { if (drawing) { drawing = false; saveState(); } });
canvas.addEventListener('mouseleave', () => { if (drawing) { drawing = false; saveState(); } });
canvas.addEventListener('touchstart', e => { e.preventDefault(); drawing = true; const c = getCell(e); applyTool(c.x, c.y); }, { passive: false });
canvas.addEventListener('touchmove', e => { e.preventDefault(); if (drawing && (currentTool === 'pencil' || currentTool === 'eraser')) { const c = getCell(e); applyTool(c.x, c.y); } }, { passive: false });
canvas.addEventListener('touchend', () => { if (drawing) { drawing = false; saveState(); } });

function setTool(t) {
  currentTool = t;
  document.querySelectorAll('.tool-btn').forEach(b => b.classList.toggle('active', b.dataset.tool === t));
}

function saveState() {
  historyIdx++;
  history = history.slice(0, historyIdx);
  history.push(pixels.map(r => [...r]));
  if (history.length > 50) { history.shift(); historyIdx--; }
}

function undo() {
  if (historyIdx > 0) { historyIdx--; pixels = history[historyIdx].map(r => [...r]); draw(); }
}
function redo() {
  if (historyIdx < history.length - 1) { historyIdx++; pixels = history[historyIdx].map(r => [...r]); draw(); }
}

function clearCanvas() {
  pixels = Array.from({ length: gridSize }, () => Array(gridSize).fill(null));
  draw(); saveState();
}

function changeGrid(size) {
  gridSize = size; init();
}

function exportPNG() {
  const scale = Math.max(1, Math.floor(512 / gridSize));
  const c = document.createElement('canvas');
  c.width = gridSize * scale; c.height = gridSize * scale;
  const cx = c.getContext('2d');
  for (let y = 0; y < gridSize; y++) {
    for (let x = 0; x < gridSize; x++) {
      if (pixels[y][x]) {
        cx.fillStyle = pixels[y][x];
        cx.fillRect(x * scale, y * scale, scale, scale);
      }
    }
  }
  const a = document.createElement('a');
  a.download = 'pixelart.png';
  a.href = c.toDataURL('image/png');
  a.click();
}

function addRecent(color) {
  recentColors = [color, ...recentColors.filter(c => c !== color)].slice(0, 10);
  renderRecent();
}

function renderRecent() {
  const wrap = document.getElementById('recentColors');
  wrap.innerHTML = recentColors.map(c =>
    `<div class="recent-swatch" style="background:${c}" onclick="currentColor='${c}';document.getElementById('colorPicker').value='${c}'"></div>`
  ).join('');
}

// Palette
const paletteEl = document.getElementById('palette');
defaultPalette.forEach(c => {
  const s = document.createElement('div');
  s.className = 'palette-swatch';
  s.style.background = c;
  s.onclick = () => { currentColor = c; document.getElementById('colorPicker').value = c; };
  paletteEl.appendChild(s);
});

renderRecent();
init();