You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, prompt = "geometric abstract", seed = 12345) {
/**
* A simple pseudo-random number generator (PRNG) for deterministic results.
* @param {number} a The seed.
* @returns {function(): number} A function that returns a random number between 0 and 1.
*/
const mulberry32 = (a) => {
return function() {
var t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
};
const random = mulberry32(seed);
const canvas = document.createElement('canvas');
// Using willReadFrequently is a performance hint for the browser
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
/**
* Extracts a palette of dominant colors from an image.
* @param {Image} img The source image object.
* @param {number} count The number of colors to extract.
* @returns {string[]} An array of RGB color strings.
*/
const getPalette = (img, count = 5) => {
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
// Scale down for faster processing
const smallSize = 64;
tempCanvas.width = smallSize;
tempCanvas.height = smallSize;
tempCtx.drawImage(img, 0, 0, smallSize, smallSize);
try {
const imageData = tempCtx.getImageData(0, 0, smallSize, smallSize).data;
const colorCounts = {};
const quantization = 32; // Group colors into buckets of 32 (0-255)
for (let i = 0; i < imageData.length; i += 4) {
// Quantize colors to group similar shades
const r = Math.floor(imageData[i] / quantization) * quantization;
const g = Math.floor(imageData[i+1] / quantization) * quantization;
const b = Math.floor(imageData[i+2] / quantization) * quantization;
const key = `${r},${g},${b}`;
colorCounts[key] = (colorCounts[key] || 0) + 1;
}
const sortedColors = Object.entries(colorCounts).sort(([,a],[,b]) => b - a);
const palette = sortedColors.slice(0, count).map(([colorKey]) => `rgb(${colorKey})`);
// Fallback in case no colors are found
if (palette.length === 0) {
return ['rgb(128,128,128)'];
}
return palette;
} catch (e) {
console.error("Could not get image data for palette extraction.", e);
// Return a default palette on error (e.g., CORS issue on a tainted canvas)
return ['#555', '#a1a', '#1aa', '#a11', '#11a'];
}
};
// Yield to the main thread before starting heavy computation
await new Promise(resolve => setTimeout(resolve, 0));
const lowerCasePrompt = prompt.toLowerCase();
// --- AI Algorithm Selection based on prompt ---
// Algorithm 1: Glitch Art Effect
if (lowerCasePrompt.includes("glitch")) {
ctx.drawImage(originalImg, 0, 0);
const numGlitches = Math.floor(random() * 25 + 15);
for (let i = 0; i < numGlitches; i++) {
const startY = Math.floor(random() * height);
const glitchHeight = Math.floor(random() * (height / 20) + 1);
const offsetX = Math.floor((random() - 0.5) * (width / 10));
const chunkWidth = Math.min(width, Math.floor(random() * width));
const chunkX = Math.floor(random() * (width - chunkWidth));
if (chunkWidth > 0 && glitchHeight > 0) {
const rowData = ctx.getImageData(chunkX, startY, chunkWidth, glitchHeight);
ctx.putImageData(rowData, chunkX + offsetX, startY);
}
}
// Algorithm 2: Pixelate/Mosaic Effect
} else if (lowerCasePrompt.includes("pixel") || lowerCasePrompt.includes("mosaic")) {
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = width;
tempCanvas.height = height;
tempCtx.drawImage(originalImg, 0, 0, width, height);
const blockSize = Math.max(4, Math.floor(width / (random() * 60 + 40)));
for (let y = 0; y < height; y += blockSize) {
for (let x = 0; x < width; x += blockSize) {
const w = Math.min(blockSize, width - x);
const h = Math.min(blockSize, height - y);
const imageData = tempCtx.getImageData(x, y, w, h).data;
let r = 0, g = 0, b = 0;
let pixelCount = imageData.length / 4;
for (let i = 0; i < imageData.length; i += 4) {
r += imageData[i];
g += imageData[i+1];
b += imageData[i+2];
}
r = Math.floor(r / pixelCount);
g = Math.floor(g / pixelCount);
b = Math.floor(b / pixelCount);
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.fillRect(x, y, blockSize, blockSize);
}
}
// Algorithm 3: Generative Geometric Art (Default)
} else {
const palette = getPalette(originalImg, 5);
// Set a background from the palette
ctx.fillStyle = palette[Math.floor(random() * palette.length)] || '#111';
ctx.fillRect(0, 0, width, height);
const numShapes = Math.floor((width * height) / (3000 - random() * 2000));
for (let i = 0; i < numShapes; i++) {
const color = palette[Math.floor(random() * palette.length)];
const x = random() * width;
const y = random() * height;
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = 1 + random() * 4;
ctx.globalAlpha = 0.3 + random() * 0.6;
const shapeType = random();
if (shapeType < 0.5) { // Rectangle
const w = random() * width / 4;
const h = random() * height / 4;
if (random() > 0.5) ctx.fillRect(x - w / 2, y - h / 2, w, h);
else ctx.strokeRect(x - w / 2, y - h / 2, w, h);
} else if (shapeType < 0.8) { // Circle
const r = random() * width / 8;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
if (random() > 0.5) ctx.fill();
else ctx.stroke();
} else { // Line
const x2 = x + (random() - 0.5) * (width / 3);
const y2 = y + (random() - 0.5) * (height / 3);
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x2, y2);
ctx.stroke();
}
}
}
// Reset global settings before returning canvas
ctx.globalAlpha = 1.0;
return canvas;
}
Apply Changes