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();
}