const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const defaultGridWidth = 16; const defaultGridHeight = 16; let gridWidth = defaultGridWidth; let gridHeight = defaultGridHeight; let cellSize = 16; let colorHistory = []; let currentColor = '#000000'; let grid = Array(gridWidth).fill().map(() => Array(gridHeight).fill(null)); let offsetX = 0; let offsetY = 0; const paletteToggle = document.getElementById('palette-toggle'); const palette = document.getElementById('palette'); let isPaletteVisible = true; let isDrawing = false; let lastX = null; let lastY = null; let lastCell = null; const MIN_CELL_SIZE = 4; const MAX_CELL_SIZE = 64; // Event Listeners canvas.addEventListener('mousedown', handleInputStart); canvas.addEventListener('mousemove', handleInputMove); canvas.addEventListener('mouseup', handleInputEnd); canvas.addEventListener('touchstart', handleInputStart); canvas.addEventListener('touchmove', handleInputMove); canvas.addEventListener('touchend', handleInputEnd); document.getElementById('colorPicker').addEventListener('input', handleColorChange); document.getElementById('gridWidth').addEventListener('change', updateGridSize); document.getElementById('gridHeight').addEventListener('change', updateGridSize); document.getElementById('resetBtn').addEventListener('click', handleReset); document.getElementById('exportBtn').addEventListener('click', exportToPNG); window.addEventListener('keydown', handlePan); paletteToggle.addEventListener('click', togglePalette); document.getElementById('zoomInBtn').addEventListener('click', () => handleZoom(1.5)); document.getElementById('zoomOutBtn').addEventListener('click', () => handleZoom(0.666)); document.getElementById('panUpBtn').addEventListener('click', () => handlePanButton('up')); document.getElementById('panDownBtn').addEventListener('click', () => handlePanButton('down')); document.getElementById('panLeftBtn').addEventListener('click', () => handlePanButton('left')); document.getElementById('panRightBtn').addEventListener('click', () => handlePanButton('right')); document.getElementById('centerViewBtn').addEventListener('click', resetView); resizeCanvas(); loadFromLocalStorage(); function initializeGrid() { grid = Array(gridWidth).fill().map(() => Array(gridHeight).fill(null)); } function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; centerGrid(); drawGrid(); } function centerGrid() { offsetX = Math.max((canvas.width - (gridWidth * cellSize)) / 2, 0); offsetY = Math.max((canvas.height - (gridHeight * cellSize)) / 2, 0); } function drawGrid() { ctx.fillStyle = 'teal'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.strokeStyle = '#888888'; for (let x = 0; x < gridWidth; x++) { for (let y = 0; y < gridHeight; y++) { ctx.fillStyle = grid[x][y] || '#f7f7f7'; ctx.fillRect(x * cellSize + offsetX, y * cellSize + offsetY, cellSize, cellSize); ctx.strokeRect(x * cellSize + offsetX, y * cellSize + offsetY, cellSize, cellSize); } } } function addToColorHistory(color) { if (colorHistory.includes(color)) return; if (colorHistory.length >= 10) colorHistory.shift(); colorHistory.push(color); renderColorHistory(); } function renderColorHistory() { const historyDiv = document.getElementById('colorHistory'); historyDiv.innerHTML = ''; colorHistory.forEach(color => { const colorDiv = document.createElement('div'); colorDiv.style.backgroundColor = color; colorDiv.addEventListener('click', () => { currentColor = color; document.getElementById('colorPicker').value = color; }); historyDiv.appendChild(colorDiv); }); } function handleColorChange() { currentColor = document.getElementById('colorPicker').value; addToColorHistory(currentColor); saveToLocalStorage(); } function handleReset() { const confirmReset = confirm("Are you sure you want to reset? This will clear your drawing and settings."); if (confirmReset) { gridWidth = defaultGridWidth; gridHeight = defaultGridHeight; initializeGrid(); centerGrid(); drawGrid(); localStorage.removeItem('pixelArtConfig'); colorHistory = []; renderColorHistory(); document.getElementById('gridWidth').value = gridWidth; document.getElementById('gridHeight').value = gridHeight; alert("Grid reset, color history cleared, and local storage cleared."); } } function handlePan(e) { const step = cellSize; if (e.key === 'ArrowUp') offsetY += step; if (e.key === 'ArrowDown') offsetY -= step; if (e.key === 'ArrowLeft') offsetX += step; if (e.key === 'ArrowRight') offsetX -= step; drawGrid(); } function updateGridSize() { gridWidth = parseInt(document.getElementById('gridWidth').value); gridHeight = parseInt(document.getElementById('gridHeight').value); initializeGrid(); centerGrid(); drawGrid(); saveToLocalStorage(); } function saveToLocalStorage() { const gridData = { gridWidth: gridWidth, gridHeight: gridHeight, cellSize: cellSize, colorHistory: colorHistory, currentColor: currentColor, grid: grid, isPaletteVisible: isPaletteVisible }; localStorage.setItem('pixelArtConfig', JSON.stringify(gridData)); } function loadFromLocalStorage() { const savedData = localStorage.getItem('pixelArtConfig'); if (savedData) { const gridData = JSON.parse(savedData); gridWidth = gridData.gridWidth || 10; gridHeight = gridData.gridHeight || 10; cellSize = gridData.cellSize || 16; colorHistory = gridData.colorHistory || []; currentColor = gridData.currentColor || '#000000'; grid = gridData.grid || Array(gridWidth).fill().map(() => Array(gridHeight).fill(null)); document.getElementById('gridWidth').value = gridWidth; document.getElementById('gridHeight').value = gridHeight; document.getElementById('colorPicker').value = currentColor; centerGrid(); drawGrid(); isPaletteVisible = gridData.isPaletteVisible ?? true; if (!isPaletteVisible) { palette.classList.add('hidden'); paletteToggle.classList.add('hidden'); paletteToggle.innerHTML = '🎨'; } } else { initializeGrid(); centerGrid(); drawGrid(); } } function exportToPNG() { const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); tempCanvas.width = gridWidth * cellSize; tempCanvas.height = gridHeight * cellSize; for (let x = 0; x < gridWidth; x++) { for (let y = 0; y < gridHeight; y++) { tempCtx.fillStyle = grid[x][y] || 'transparent'; tempCtx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); } } tempCanvas.toBlob(blob => { const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'pixel-art.png'; link.click(); }); } function handleInputStart(e) { e.preventDefault(); isDrawing = true; const coords = getInputCoordinates(e); const cell = getCellFromCoords(coords); if (cell) { lastCell = cell; // If cell already has a color, remove it. Otherwise, add color if (grid[cell.x][cell.y]) { grid[cell.x][cell.y] = null; } else { grid[cell.x][cell.y] = currentColor; } drawGrid(); saveToLocalStorage(); } } function handleInputMove(e) { e.preventDefault(); if (!isDrawing) return; const coords = getInputCoordinates(e); const cell = getCellFromCoords(coords); if (cell && (!lastCell || cell.x !== lastCell.x || cell.y !== lastCell.y)) { lastCell = cell; // When dragging, always draw (don't erase) grid[cell.x][cell.y] = currentColor; drawGrid(); saveToLocalStorage(); } } function handleInputEnd(e) { e.preventDefault(); isDrawing = false; lastCell = null; } function getInputCoordinates(e) { const rect = canvas.getBoundingClientRect(); if (e.touches) { // Touch event return { x: e.touches[0].clientX - rect.left, y: e.touches[0].clientY - rect.top }; } else { // Mouse event return { x: e.clientX - rect.left, y: e.clientY - rect.top }; } } function getCellFromCoords(coords) { const x = Math.floor((coords.x - offsetX) / cellSize); const y = Math.floor((coords.y - offsetY) / cellSize); if (x >= 0 && x < gridWidth && y >= 0 && y < gridHeight) { return { x, y }; } return null; } function togglePalette() { isPaletteVisible = !isPaletteVisible; if (isPaletteVisible) { palette.classList.remove('hidden'); paletteToggle.classList.remove('hidden'); paletteToggle.innerHTML = '☰'; } else { palette.classList.add('hidden'); paletteToggle.classList.add('hidden'); paletteToggle.innerHTML = '🎨'; } } function handleZoom(factor) { const newCellSize = Math.max(MIN_CELL_SIZE, Math.min(MAX_CELL_SIZE, cellSize * factor)); if (newCellSize === cellSize) return; // const centerX = (canvas.width / 2 - offsetX) / cellSize; // const centerY = (canvas.height / 2 - offsetY) / cellSize; cellSize = newCellSize; centerGrid(); drawGrid(); saveToLocalStorage(); } function handlePanButton(direction) { const step = cellSize; switch(direction) { case 'up': offsetY += step; break; case 'down': offsetY -= step; break; case 'left': offsetX += step; break; case 'right': offsetX -= step; break; } drawGrid(); } function resetView() { cellSize = 16; // Reset to default zoom centerGrid(); drawGrid(); saveToLocalStorage(); }