xxxxxxxxxx
285
var grid = 4; // Each cell will be 4px to fit 128x64 grid in the canvas
var show = false;
var pixelCoords = new Set(); // Store coordinates as "x,y" strings for uniqueness
var animationIndex = 0;
var lastAnimationTime = 0;
var animationSpeed = 100; // Time between highlights in milliseconds
var lastX = 0;
var lastY = 0;
var exportCount = {}; // Track export counts for each name
let refImg;
let imgLoaded = false;
let showRef = false;
let colorPicker, nameInput, clearButton, exportButton, refButton, fileInput;
function setup() {
createCanvas(512, 256); // 128*4 x 64*4 pixels
background(10);
// Create color picker
colorPicker = createColorPicker("#FFFFFF");
colorPicker.position(0, height + 10);
colorPicker.size(colorPicker.width, 28);
// Create name input field
nameInput = createInput('mySprite');
nameInput.position(colorPicker.x + colorPicker.width + 8, height + 10);
nameInput.size(150, 28);
clearButton = createButton("CLEAR");
clearButton.position(nameInput.x + nameInput.width + 8, height + 10);
clearButton.size(clearButton.width, 32);
clearButton.mousePressed(clean);
// Add array export button
exportButton = createButton("EXPORT COORDS");
exportButton.position(clearButton.x + clearButton.width + 8, height + 10);
exportButton.size(116, 32);
exportButton.mousePressed(exportCoords);
// Add reference picture toggle button
refButton = createButton("REF PICTURE");
refButton.position(exportButton.x + exportButton.width + 8, height + 10);
refButton.size(100, 32);
refButton.mousePressed(() => showRef = !showRef);
// Create file input for reference image
fileInput = createFileInput(handleFile);
fileInput.position(refButton.x + refButton.width + 8, height + 10);
// Prevent context menu on right click
let canvasElement = document.querySelector('canvas');
canvasElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
}
function draw() {
// Always start with a clean background
background(10);
// Draw reference image if enabled
if (showRef && imgLoaded) {
push();
tint(255, 127); // 50% opacity
image(refImg, 0, 0, width, height);
pop();
}
// Draw all existing pixels
for (let coordStr of pixelCoords) {
let coord = coordStr.split(',').map(Number);
noStroke();
fill(colorPicker.color());
square(coord[0] * grid, coord[1] * grid, grid);
}
// Draw the animated highlight if we have pixels
if (pixelCoords.size > 0) {
let nowTime = millis();
if (nowTime - lastAnimationTime > animationSpeed) {
let coordArray = Array.from(pixelCoords);
let currentCoord = coordArray[animationIndex].split(',').map(Number);
noStroke();
fill(255, 0, 0);
square(currentCoord[0] * grid, currentCoord[1] * grid, grid);
// Update animation index
animationIndex = (animationIndex + 1) % coordArray.length;
lastAnimationTime = nowTime;
} else {
// Continue showing the current pixel until it's time to change
let coordArray = Array.from(pixelCoords);
let currentCoord = coordArray[animationIndex].split(',').map(Number);
noStroke();
fill(255, 0, 0);
square(currentCoord[0] * grid, currentCoord[1] * grid, grid);
}
}
// Always draw crosshairs last
stroke(150);
line(width/2, 0, width/2, height);
line(0, height/2, width, height/2);
// Handle animation
if (pixelCoords.size > 0) {
let nowTime = millis();
if (nowTime - lastAnimationTime > animationSpeed) {
// Only clear and redraw if reference is shown
if (showRef && imgLoaded) {
background(10);
push();
tint(255, 127);
image(refImg, 0, 0, width, height);
pop();
}
redrawAllPixels();
// Draw the current animation pixel in red
let coordArray = Array.from(pixelCoords);
let currentCoord = coordArray[animationIndex].split(',').map(Number);
noStroke();
fill(255, 0, 0);
square(currentCoord[0] * grid, currentCoord[1] * grid, grid);
// Update animation index
animationIndex = (animationIndex + 1) % coordArray.length;
lastAnimationTime = nowTime;
}
}
// Handle drawing and erasing
if (mouseIsPressed) {
var currentX = snap(mouseX);
var currentY = snap(mouseY);
// If this is the first point of the stroke, initialize last position
if (lastX === 0 && lastY === 0) {
lastX = currentX;
lastY = currentY;
}
// Calculate number of steps needed based on distance
var distance = dist(lastX, lastY, currentX, currentY);
var steps = Math.max(1, Math.ceil(distance / grid));
// Interpolate points between last and current position
for (var i = 0; i <= steps; i++) {
var t = i / steps;
var x = lerp(lastX, currentX, t);
var y = lerp(lastY, currentY, t);
// Snap interpolated points to grid
x = snap(x);
y = snap(y);
var gridX = Math.floor(x / grid);
var gridY = Math.floor(y / grid);
// Check if coordinates are within bounds
if (gridX >= 0 && gridX < 128 && gridY >= 0 && gridY < 64) {
if (mouseButton === RIGHT) {
// Erase pixel
pixelCoords.delete(`${gridX},${gridY}`);
} else if (mouseButton === LEFT) {
// Draw pixel
noStroke();
fill(colorPicker.color());
square(x, y, grid);
pixelCoords.add(`${gridX},${gridY}`);
}
}
}
// Update last position for next frame
lastX = currentX;
lastY = currentY;
} else {
// Reset last position when mouse is released
lastX = 0;
lastY = 0;
}
}
function handleFile(file) {
imgLoaded = false;
if (file.type === 'image') {
// Create the image as an img element
refImg = createImg(file.data, 'Reference image', 'anonymous', imgCreated);
refImg.hide();
} else {
refImg = null;
}
}
function imgCreated() {
refImg.hide();
// Create a temporary p5.Graphics object to draw the image
let g = createGraphics(refImg.elt.width, refImg.elt.height);
g.image(refImg, 0, 0);
// Remove the original element from the DOM
refImg.remove();
// g.get will return image data as a p5.Image object
refImg = g.get(0, 0, g.width, g.height);
// Resize it to fit our canvas
if (refImg.width/refImg.height > width/height) {
refImg.resize(width, 0);
} else {
refImg.resize(0, height);
}
// Record that we have finished creating the image object
imgLoaded = true;
showRef = true; // Automatically show the reference when loaded
}
function snap(p) {
var cell = Math.round((p - grid / 2) / grid);
return cell * grid;
}
function redrawAllPixels() {
// Redraw all pixels in their normal color
for (let coordStr of pixelCoords) {
let coord = coordStr.split(',').map(Number);
noStroke();
fill(colorPicker.color());
square(coord[0] * grid, coord[1] * grid, grid);
}
}
function clean() {
// Just clear the pixels, the draw loop will handle the rest
pixelCoords.clear();
animationIndex = 0;
lastAnimationTime = 0;
}
function exportCoords() {
let baseName = nameInput.value().trim() || 'mySprite';
// Initialize counter for this base name if it doesn't exist
if (!(baseName in exportCount)) {
exportCount[baseName] = 0;
}
// Increment counter
exportCount[baseName]++;
// Create variable name with suffix if needed
let varName = baseName;
if (exportCount[baseName] > 1) {
varName = `${baseName}_${exportCount[baseName]}`;
}
let output = `const uint8_t ${varName}[][2] = {\n`;
// Convert Set of strings back to coordinates and sort them
let coordArray = Array.from(pixelCoords)
.map(coord => coord.split(',').map(Number))
.sort((a, b) => {
if (a[1] === b[1]) return a[0] - b[0];
return a[1] - b[1];
});
// Generate the coordinate pairs
coordArray.forEach((coord, index) => {
output += ` {${coord[0]}, ${coord[1]}}`; // x, y coordinates
if (index < coordArray.length - 1) output += ",";
output += "\n";
});
output += "};\n";
output += `const uint16_t ${varName}_length = ${coordArray.length};\n`;
// Create and download the text file
let blob = new Blob([output], {type: 'text/plain'});
let url = window.URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = `${varName}.txt`;
a.click();
window.URL.revokeObjectURL(url);
}