You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, numIdeas = 3, colorTheme = "Dark") {
// Determine canvas dimensions and layout
const maxImgW = 500;
const maxImgH = 600;
const scale = Math.min(maxImgW / originalImg.width, maxImgH / originalImg.height, 1);
const dispW = originalImg.width * scale;
const dispH = originalImg.height * scale;
const panelW = 400;
const padding = 30;
const validIdeaCount = Math.max(1, Math.min(6, Number(numIdeas) || 3));
const rightX = padding * 2 + dispW;
const outW = rightX + panelW + padding;
// Estimate right side height to bound the canvas
const boxHeightsEst = 120; // Approx height per idea box
const estimatedRightHeight = 280 + validIdeaCount * boxHeightsEst;
const outH = Math.max(dispH + padding * 2, estimatedRightHeight);
const padTop = (outH - dispH) / 2; // Center the image vertically
// Create output canvas
const outCanvas = document.createElement("canvas");
outCanvas.width = outW;
outCanvas.height = outH;
const outCtx = outCanvas.getContext("2d");
// Theme definitions
const isLight = colorTheme.toLowerCase() === "light";
const bgGrad = outCtx.createLinearGradient(0, 0, outW, outH);
if (isLight) {
bgGrad.addColorStop(0, "#f8fafc");
bgGrad.addColorStop(1, "#e2e8f0");
} else {
bgGrad.addColorStop(0, "#0f172a");
bgGrad.addColorStop(1, "#1e1b4b");
}
const txtColor = isLight ? "#1e293b" : "#ffffff";
const subColor = isLight ? "#475569" : "#a8b2d1";
const accentColor = isLight ? "#0284c7" : "#00f3ff";
const boxBg = isLight ? "rgba(0, 0, 0, 0.05)" : "rgba(255, 255, 255, 0.05)";
const boxStroke = isLight ? "rgba(0, 0, 0, 0.1)" : "rgba(255, 255, 255, 0.1)";
const gridColor = isLight ? "rgba(0, 0, 0, 0.03)" : "rgba(255, 255, 255, 0.03)";
// Fill Background
outCtx.fillStyle = bgGrad;
outCtx.fillRect(0, 0, outW, outH);
// Draw Subtle Grid Pattern
outCtx.strokeStyle = gridColor;
outCtx.lineWidth = 1;
for (let x = 0; x < outW; x += 40) { outCtx.beginPath(); outCtx.moveTo(x, 0); outCtx.lineTo(x, outH); outCtx.stroke(); }
for (let y = 0; y < outH; y += 40) { outCtx.beginPath(); outCtx.moveTo(0, y); outCtx.lineTo(outW, y); outCtx.stroke(); }
// --- AI Image Analysis (Heuristic based to avoid slow model downloads) ---
let avgLuma = 127;
let avgDiff = 15;
let topColors = [accentColor, txtColor, subColor];
try {
const analyzeCanvas = document.createElement("canvas");
const w = 100, h = 100;
analyzeCanvas.width = w; analyzeCanvas.height = h;
const aCtx = analyzeCanvas.getContext("2d");
aCtx.drawImage(originalImg, 0, 0, w, h);
const imgData = aCtx.getImageData(0, 0, w, h);
const data = imgData.data;
let totalLuma = 0;
let totalDiff = 0;
// 64 bins for dominant color quantization (4*4*4 RGB space)
const bins = new Array(64).fill(0);
const binColors = new Array(64).fill(null).map(() => [0, 0, 0, 0]);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const i = (y * w + x) * 4;
const r = data[i], g = data[i+1], b = data[i+2];
const luma = 0.299 * r + 0.587 * g + 0.114 * b;
totalLuma += luma;
// Edge detection heuristic
if (x < w - 1 && y < h - 1) {
const nextX = data[i + 4]*0.299 + data[i + 5]*0.587 + data[i + 6]*0.114;
const nextY = data[i + w*4]*0.299 + data[i + w*4 + 1]*0.587 + data[i + w*4 + 2]*0.114;
totalDiff += Math.abs(luma - nextX) + Math.abs(luma - nextY);
}
// Color extraction
const rBin = Math.floor(r / 64), gBin = Math.floor(g / 64), bBin = Math.floor(b / 64);
const binIdx = (rBin << 4) | (gBin << 2) | bBin;
bins[binIdx]++;
binColors[binIdx][0] += r;
binColors[binIdx][1] += g;
binColors[binIdx][2] += b;
binColors[binIdx][3]++;
}
}
avgLuma = totalLuma / (w * h);
avgDiff = totalDiff / ((w - 1) * (h - 1) * 2);
const sortedBins = bins.map((count, idx) => ({count, idx})).sort((a,b) => b.count - a.count);
topColors = [];
for (let i = 0; i < 3; i++) {
const bin = sortedBins[i];
if (bin.count > 0) {
const r = Math.round(binColors[bin.idx][0] / bin.count);
const g = Math.round(binColors[bin.idx][1] / bin.count);
const b = Math.round(binColors[bin.idx][2] / bin.count);
topColors.push(`rgb(${r},${g},${b})`);
} else {
topColors.push(subColor);
}
}
} catch (e) {
// Fallback for CORS security issues
console.warn("Cross-origin image detected, using default aesthetic metrics.");
avgLuma = 100 + Math.random() * 50;
avgDiff = 10 + Math.random() * 10;
}
const brightness = avgLuma > 115 ? "Bright" : "Dark";
const lumaPercent = Math.round((avgLuma / 255) * 100);
const complexity = avgDiff > 12 ? "Complex" : "Minimal";
// --- Draw Original Image Side ---
outCtx.shadowColor = isLight ? "rgba(2, 132, 199, 0.3)" : "rgba(0, 243, 255, 0.3)";
outCtx.shadowBlur = 20;
outCtx.shadowOffsetY = 10;
outCtx.drawImage(originalImg, padding, padTop, dispW, dispH);
// Reset shadow
outCtx.shadowBlur = 0;
outCtx.shadowOffsetX = 0;
outCtx.shadowOffsetY = 0;
// Image borders and AI accents
outCtx.strokeStyle = boxStroke;
outCtx.lineWidth = 2;
outCtx.strokeRect(padding, padTop, dispW, dispH);
const cl = 15;
outCtx.strokeStyle = accentColor;
outCtx.lineWidth = 3;
// Top-left
outCtx.beginPath(); outCtx.moveTo(padding, padTop+cl); outCtx.lineTo(padding, padTop); outCtx.lineTo(padding+cl, padTop); outCtx.stroke();
// Top-right
outCtx.beginPath(); outCtx.moveTo(padding+dispW-cl, padTop); outCtx.lineTo(padding+dispW, padTop); outCtx.lineTo(padding+dispW, padTop+cl); outCtx.stroke();
// Bottom-left
outCtx.beginPath(); outCtx.moveTo(padding, padTop+dispH-cl); outCtx.lineTo(padding, padTop+dispH); outCtx.lineTo(padding+cl, padTop+dispH); outCtx.stroke();
// Bottom-right
outCtx.beginPath(); outCtx.moveTo(padding+dispW-cl, padTop+dispH); outCtx.lineTo(padding+dispW, padTop+dispH); outCtx.lineTo(padding+dispW, padTop+dispH-cl); outCtx.stroke();
// Divider Line
const sepX = padding + dispW + padding / 2;
outCtx.beginPath(); outCtx.moveTo(sepX, padding); outCtx.lineTo(sepX, outH - padding);
outCtx.strokeStyle = boxStroke;
outCtx.lineWidth = 1; outCtx.stroke();
// --- Draw Idea Generator Panel ---
const fontPrimary = "'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
outCtx.fillStyle = accentColor;
outCtx.font = `bold 28px ${fontPrimary}`;
outCtx.fillText("🤖 AI Generator", rightX, padding + 25);
// AI Analysis Box
outCtx.fillStyle = boxBg;
outCtx.fillRect(rightX, padding + 55, panelW, 120);
outCtx.strokeStyle = boxStroke;
outCtx.strokeRect(rightX, padding + 55, panelW, 120);
outCtx.fillStyle = subColor;
outCtx.font = `bold 13px ${fontPrimary}`;
outCtx.fillText("IMAGE SIGNATURE", rightX + 15, padding + 75);
outCtx.fillStyle = txtColor;
outCtx.font = `15px ${fontPrimary}`;
outCtx.fillText(`Brightness Profile: ${brightness} (${lumaPercent}%)`, rightX + 15, padding + 105);
outCtx.fillText(`Complexity Profile: ${complexity}`, rightX + 15, padding + 135);
outCtx.fillText(`Extracted Palette:`, rightX + 15, padding + 163);
// Color Swatches
for (let i = 0; i < topColors.length; i++) {
outCtx.fillStyle = topColors[i];
outCtx.beginPath();
outCtx.rect(rightX + 155 + i * 40, padding + 148, 30, 20);
outCtx.fill();
outCtx.strokeStyle = isLight ? "rgba(0,0,0,0.2)" : "rgba(255,255,255,0.2)";
outCtx.lineWidth = 1;
outCtx.stroke();
}
outCtx.fillStyle = accentColor;
outCtx.font = `bold 14px ${fontPrimary}`;
outCtx.fillText(">> GENERATED CONCEPTS", rightX, padding + 225);
// --- Generate Concepts ---
const styles = ["Cyberpunk", "Watercolor", "Minimalist", "Steampunk", "Pop Art", "Surrealist", "Pixel Art", "Retro Wave", "Gothic", "Neo-noir", "Abstract", "Low Poly", "Vaporwave", "Synthwave"];
const moods = ["melancholic", "joyful", "eerie", "peaceful", "energetic", "mysterious", "whimsical", "dramatic", "nostalgic", "tense", "dreamy"];
const mediums = ["video game concept", "movie poster", "book cover", "graphic novel", "board game art", "music album cover", "mural", "digital painting", "3D render"];
const subjects = {
Complex: ["bustling metropolis", "chaotic battlefield", "dense jungle", "intricate machine", "crowded festival", "vibrant reef", "sprawling cityscape", "intergalactic fleet"],
Minimal: ["solitary figure", "vast landscape", "zen garden", "empty room", "floating object", "calm ocean", "single glowing orb", "lonely mountain peak"],
};
function generateIdeaStr() {
const pick = arr => arr[Math.floor(Math.random() * arr.length)];
const style = pick(styles);
const mood = pick(moods);
const medium = pick(mediums);
const subject = pick(subjects[complexity]);
const templates = [
`Convert this scene into a ${style} artwork, using its ${brightness.toLowerCase()} underlying mood to highlight a ${subject}.`,
`Reimagine the composition as a ${medium}, focusing on the extracted color palette to evoke a ${mood} atmosphere.`,
`Apply a ${style} aesthetic to the image, restructuring its layout to firmly establish a ${subject}.`,
`Create a ${mood} ${medium} inspired by this image's unique lighting, emphasizing its ${complexity.toLowerCase() === 'complex' ? 'intricate features' : 'immaculate simplicity'} and ${brightness.toLowerCase()} tones.`,
`What if this was a ${style} ${medium}? Redesign the scenery to orbit tightly around a ${subject}.`,
`Morph the ${brightness.toLowerCase()} components to craft a ${mood} ${style} rendering surrounding a ${subject}.`
];
return pick(templates);
}
function getLines(ctx, text, maxWidth) {
const words = text.split(" ");
const lines = [];
let currentLine = words[0];
for (let i = 1; i < words.length; i++) {
const word = words[i];
const width = ctx.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
}
let curY = padding + 250;
for (let i = 0; i < validIdeaCount; i++) {
const ideaText = generateIdeaStr();
outCtx.font = `15px ${fontPrimary}`;
const lines = getLines(outCtx, ideaText, panelW - 40);
const boxHeight = 40 + lines.length * 22;
outCtx.fillStyle = boxBg;
outCtx.fillRect(rightX, curY, panelW, boxHeight);
outCtx.fillStyle = accentColor;
outCtx.fillRect(rightX, curY, 4, boxHeight);
outCtx.fillStyle = isLight ? "#0f172a" : "#ccd6f6";
outCtx.font = `bold 15px ${fontPrimary}`;
outCtx.fillText(`Prompt ${i+1}`, rightX + 15, curY + 25);
outCtx.fillStyle = subColor;
outCtx.font = `15px ${fontPrimary}`;
lines.forEach((line, j) => {
outCtx.fillText(line, rightX + 15, curY + 48 + j * 22);
});
curY += boxHeight + 15;
}
return outCanvas;
}
Apply Changes