You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, cellSize = 30, borderWidth = 2, borderColor = "black", seedRandomness = 0.8) {
const imgWidth = originalImg.width;
const imgHeight = originalImg.height;
// Ensure parameters are valid
cellSize = Math.max(1, parseFloat(cellSize));
borderWidth = Math.max(0, parseFloat(borderWidth));
seedRandomness = Math.max(0, Math.min(1, parseFloat(seedRandomness))); // Clamp seedRandomness to [0,1]
// Handle zero-dimension images
if (imgWidth === 0 || imgHeight === 0) {
const canvas = document.createElement('canvas');
canvas.width = imgWidth;
canvas.height = imgHeight;
return canvas;
}
// Create a temporary canvas to get pixel data from originalImg
const tempCanvas = document.createElement('canvas');
tempCanvas.width = imgWidth;
tempCanvas.height = imgHeight;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.drawImage(originalImg, 0, 0);
// Generate seed points
const seeds = [];
const gridSeedMap = new Map();
let seedIdCounter = 0;
// Loop to generate seeds, ensuring at least one row/column of seeds if image dimensions are > 0
for (let gy = 0; (gy * cellSize < imgHeight) || (gy === 0 && imgHeight > 0) ; gy++) {
const cellYStart = gy * cellSize;
for (let gx = 0; (gx * cellSize < imgWidth) || (gx === 0 && imgWidth > 0) ; gx++) {
const cellXStart = gx * cellSize;
const cellCenterX = cellXStart + cellSize / 2;
const cellCenterY = cellYStart + cellSize / 2;
const randomOffsetX = (Math.random() - 0.5) * cellSize * seedRandomness;
const randomOffsetY = (Math.random() - 0.5) * cellSize * seedRandomness;
let seedX = cellCenterX + randomOffsetX;
let seedY = cellCenterY + randomOffsetY;
seedX = Math.max(0, Math.min(imgWidth - 1, seedX));
seedY = Math.max(0, Math.min(imgHeight - 1, seedY));
const pixelData = tempCtx.getImageData(Math.floor(seedX), Math.floor(seedY), 1, 1).data;
const color = { r: pixelData[0], g: pixelData[1], b: pixelData[2] };
const seed = { x: seedX, y: seedY, color: color, id: seedIdCounter++, gx: gx, gy: gy };
seeds.push(seed);
const gridKey = `${gx}_${gy}`;
gridSeedMap.set(gridKey, seed);
if (gx * cellSize >= imgWidth && gx > 0) break; // Optimization: break if past image width
if (gx === 0 && imgWidth === 0 && imgHeight > 0) break; // Edge case 0-width image
if (gx === 0 && !(imgWidth > 0)) break; // Ensure loop runs once for gx=0 if imgWidth > 0
}
if (gy * cellSize >= imgHeight && gy > 0) break; // Optimization: break if past image height
if (gy === 0 && imgHeight === 0 && imgWidth > 0) break; // Edge case 0-height image
if (gy === 0 && !(imgHeight > 0)) break; // Ensure loop runs once for gy=0 if imgHeight > 0
}
// Create output canvas
const outputCanvas = document.createElement('canvas');
outputCanvas.width = imgWidth;
outputCanvas.height = imgHeight;
const outputCtx = outputCanvas.getContext('2d');
const outputImageData = outputCtx.createImageData(imgWidth, imgHeight);
const data = outputImageData.data;
const pixelSeedIdMap = new Array(imgWidth * imgHeight);
// This check is crucial if image dimensions are smaller than cellSize, seed loops might not run as expected by simple W/cellSize formula
// However, the loop conditions `|| (gX ===0 && imgWidth > 0)` should ensure seed generation.
// If somehow no seeds were generated for a valid image, this would be an issue.
if (seeds.length === 0 && (imgWidth > 0 && imgHeight > 0)) {
// This indicates an issue with seed generation logic for small images. Add a central seed.
console.warn("No seeds generated, adding a central fallback seed.");
const fallbackSeedX = Math.floor(imgWidth / 2);
const fallbackSeedY = Math.floor(imgHeight / 2);
const pixelData = tempCtx.getImageData(fallbackSeedX, fallbackSeedY, 1, 1).data;
const color = { r: pixelData[0], g: pixelData[1], b: pixelData[2] };
const seed = { x: fallbackSeedX, y: fallbackSeedY, color: color, id: seedIdCounter++, gx: 0, gy: 0 };
seeds.push(seed);
gridSeedMap.set(`0_0`, seed);
}
for (let y = 0; y < imgHeight; y++) {
for (let x = 0; x < imgWidth; x++) {
const currentPixelGx = Math.floor(x / cellSize);
const currentPixelGy = Math.floor(y / cellSize);
let minDistSq = Infinity;
let bestSeed = null;
for (let dgy = -1; dgy <= 1; dgy++) {
for (let dgx = -1; dgx <= 1; dgx++) {
const checkGx = currentPixelGx + dgx;
const checkGy = currentPixelGy + dgy;
const key = `${checkGx}_${checkGy}`;
const seed = gridSeedMap.get(key);
if (seed) {
const dxSeed = seed.x - x;
const dySeed = seed.y - y;
const distSq = dxSeed * dxSeed + dySeed * dySeed;
if (distSq < minDistSq) {
minDistSq = distSq;
bestSeed = seed;
}
}
}
}
if (!bestSeed && seeds.length > 0) {
// Fallback: if the 3x3 search fails (theoretically shouldn't if seeds are within their cells)
// or if a pixel is outside all known seed grid cells (e.g. cellSize computation issue)
// search all seeds (slower, but robust for edge cases or high randomness beyond cell bounds)
for (const seed of seeds) {
const dxSeed = seed.x - x;
const dySeed = seed.y - y;
const distSq = dxSeed * dxSeed + dySeed * dySeed;
if (distSq < minDistSq) {
minDistSq = distSq;
bestSeed = seed;
}
}
}
if (bestSeed) {
const color = bestSeed.color;
const index = (y * imgWidth + x) * 4;
data[index] = color.r;
data[index + 1] = color.g;
data[index + 2] = color.b;
data[index + 3] = 255;
pixelSeedIdMap[y * imgWidth + x] = bestSeed.id;
} else {
// If no seed could be assigned (e.g., seeds array is empty, which shouldn't happen for valid images now)
const index = (y * imgWidth + x) * 4;
data[index] = data[index+1] = data[index+2] = 0; // Black
data[index+3] = 0; // Transparent
}
}
}
outputCtx.putImageData(outputImageData, 0, 0);
if (borderWidth > 0 && seeds.length > 0) {
outputCtx.strokeStyle = borderColor;
outputCtx.lineWidth = borderWidth;
outputCtx.lineCap = "round";
outputCtx.lineJoin = "round";
outputCtx.beginPath();
for (let y = 0; y < imgHeight; y++) {
for (let x = 0; x < imgWidth; x++) {
const currentPixelIndex = y * imgWidth + x;
const currentSeedId = pixelSeedIdMap[currentPixelIndex];
if (currentSeedId === undefined) continue; // Skip if pixel wasn't assigned a seed
// Check right neighbor
if (x < imgWidth - 1) {
const rightPixelIndex = y * imgWidth + (x + 1);
const rightSeedId = pixelSeedIdMap[rightPixelIndex];
if (rightSeedId !== undefined && currentSeedId !== rightSeedId) {
outputCtx.moveTo(x + 1, y); // Line is at junction of pixels
outputCtx.lineTo(x + 1, y + 1);
}
}
// Check bottom neighbor
if (y < imgHeight - 1) {
const bottomPixelIndex = (y + 1) * imgWidth + x;
const bottomSeedId = pixelSeedIdMap[bottomPixelIndex];
if (bottomSeedId !== undefined && currentSeedId !== bottomSeedId) {
outputCtx.moveTo(x, y + 1);
outputCtx.lineTo(x + 1, y + 1);
}
}
}
}
outputCtx.stroke();
}
return outputCanvas;
}
Apply Changes