You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, languagePref = "Auto", genderPref = "Any") {
// 1. Analyze the image to derive deterministic traits for our "suggestion engine"
const analysisCanvas = document.createElement('canvas');
const actx = analysisCanvas.getContext('2d');
analysisCanvas.width = originalImg.width || 100;
analysisCanvas.height = originalImg.height || 100;
let r = 100, g = 100, b = 100; // Default colors
try {
actx.drawImage(originalImg, 0, 0, analysisCanvas.width, analysisCanvas.height);
const imgData = actx.getImageData(0, 0, analysisCanvas.width, analysisCanvas.height).data;
let step = Math.ceil(imgData.length / 4000) * 4; // Sample max ~1000 pixels
let count = 0;
let rSum = 0, gSum = 0, bSum = 0;
for (let i = 0; i < imgData.length; i += step) {
rSum += imgData[i];
gSum += imgData[i + 1];
bSum += imgData[i + 2];
count++;
}
if (count > 0) {
r = Math.round(rSum / count);
g = Math.round(gSum / count);
b = Math.round(bSum / count);
}
} catch (e) {
// Fallback if image is cross-origin tainted and pixels cannot be read
r = (originalImg.width * 7) % 255;
g = (originalImg.height * 11) % 255;
b = ((originalImg.width + originalImg.height) * 13) % 255;
}
const brightness = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
const seed = (originalImg.width * originalImg.height + r * 7 + g * 13 + b * 17) % 100;
// 2. Determine Character Vibe based on average colors
let vibe = "Balanced & Grounded";
let vibeKey = "Balanced";
if (brightness < 60) {
vibe = "Dark, Mysterious, or Edgy";
vibeKey = "Dark";
} else if (brightness > 190) {
vibe = "Bright, Energetic, or Innocent";
vibeKey = "Bright";
} else if (r > g + 20 && r > b + 20) {
vibe = "Passionate, Hot-Blooded, or Aggressive";
vibeKey = "Passionate";
} else if (b > r + 20 && b > g + 20) {
vibe = "Calm, Cool, Collected, or Intellectual";
vibeKey = "Calm";
} else if (g > r + 20 && g > b + 20) {
vibe = "Natural, Peaceful, or Mysterious";
vibeKey = "Natural";
}
// 3. Voice Actor Database (Famous Industry Staples)
const vaDB = [
// Male JP
{ name: "Yuki Kaji", lang: "Japanese", gender: "Male", traits: ["Bright", "Passionate"], roles: "Eren Yeager (AOT), Shoto Todoroki (MHA)" },
{ name: "Kenjiro Tsuda", lang: "Japanese", gender: "Male", traits: ["Dark", "Calm"], roles: "Kento Nanami (JJK), Seto Kaiba (Yu-Gi-Oh!)" },
{ name: "Hiroshi Kamiya", lang: "Japanese", gender: "Male", traits: ["Calm", "Balanced"], roles: "Levi Ackerman (AOT), Trafalgar Law (One Piece)" },
{ name: "Natsuki Hanae", lang: "Japanese", gender: "Male", traits: ["Bright", "Natural"], roles: "Tanjiro Kamado (Demon Slayer), Ken Kaneki (TG)" },
{ name: "Takehito Koyasu", lang: "Japanese", gender: "Male", traits: ["Dark", "Passionate"], roles: "Dio Brando (JoJo), Zeke Yeager (AOT)" },
// Female JP
{ name: "Miyuki Sawashiro", lang: "Japanese", gender: "Female", traits: ["Dark", "Cool"], roles: "Kurapika (HxH), Raiden Shogun (Genshin)" },
{ name: "Kana Hanazawa", lang: "Japanese", gender: "Female", traits: ["Bright", "Natural"], roles: "Mitsuri Kanroji (Demon Slayer), Mayuri (Steins;Gate)" },
{ name: "Saori Hayami", lang: "Japanese", gender: "Female", traits: ["Calm", "Balanced", "Natural"], roles: "Yor Forger (Spy x Fam), Shinobu Kocho (DS)" },
{ name: "Rie Takahashi", lang: "Japanese", gender: "Female", traits: ["Bright", "Passionate"], roles: "Megumin (Konosuba), Ai Hoshino (Oshi no Ko)" },
{ name: "Aoi Yuki", lang: "Japanese", gender: "Female", traits: ["Dark", "Bright", "Passionate"], roles: "Madoka (PMMM), Tanya Degurechaff (Youjo Senki)" },
// Male EN
{ name: "Matthew Mercer", lang: "English", gender: "Male", traits: ["Calm", "Balanced"], roles: "Levi (AOT EN), Jotaro Kujo (JoJo EN)" },
{ name: "Troy Baker", lang: "English", gender: "Male", traits: ["Dark", "Passionate"], roles: "Joel (TLOU), Booker DeWitt (BioShock)" },
{ name: "Johnny Yong Bosch", lang: "English", gender: "Male", traits: ["Bright", "Passionate"], roles: "Ichigo (Bleach EN), Lelouch (Code Geass EN)" },
{ name: "Steve Blum", lang: "English", gender: "Male", traits: ["Dark", "Calm"], roles: "Spike Spiegel (Bebop EN), Orochimaru (Naruto EN)" },
{ name: "Yuri Lowenthal", lang: "English", gender: "Male", traits: ["Bright", "Balanced"], roles: "Sasuke Uchiha (Naruto EN), Spider-Man" },
// Female EN
{ name: "Laura Bailey", lang: "English", gender: "Female", traits: ["Bright", "Passionate"], roles: "Tohru Honda (Fruits Basket EN), Abby (TLOU)" },
{ name: "Erica Lindbeck", lang: "English", gender: "Female", traits: ["Dark", "Calm", "Passionate"], roles: "Futaba Sakura (Persona 5), Black Cat" },
{ name: "Cherami Leigh", lang: "English", gender: "Female", traits: ["Bright", "Balanced"], roles: "Asuna (SAO EN), Lucy Heartfilia (Fairy Tail EN)" },
{ name: "Cristina Vee", lang: "English", gender: "Female", traits: ["Dark", "Passionate"], roles: "Killua (HxH EN), Velvet Crowe (Tales)" },
{ name: "Tara Strong", lang: "English", gender: "Female", traits: ["Bright", "Natural"], roles: "Raven (Teen Titans), Harley Quinn" }
];
// Filter actors based on input preferences
let filtered = vaDB.filter(va => {
let matchLang = (languagePref === "Auto") || (va.lang.toLowerCase() === languagePref.toLowerCase());
let matchGen = (genderPref === "Any") || (va.gender.toLowerCase() === genderPref.toLowerCase());
return matchLang && matchGen;
});
if (filtered.length === 0) {
filtered = vaDB; // Fallback to all if parameters are overly restrictive or mistyped
}
// Try to match vibe trait
let bestMatches = filtered.filter(va => va.traits.includes(vibeKey));
if (bestMatches.length === 0) {
bestMatches = filtered;
}
// Select deteriministically from acceptable candidates
const chosenVA = bestMatches[seed % bestMatches.length];
// 4. Create the stylized presentation Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 420;
// Base background styling
ctx.fillStyle = "#121212";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Decorative gradient background
const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
grad.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.2)`);
grad.addColorStop(1, "rgba(0, 0, 0, 0.8)");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Header Glow Box & Text
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, 0.6)`;
ctx.fillRect(0, 0, canvas.width, 10);
// Draw the image on the left with cover positioning and clipping
const imgX = 40;
const imgY = 40;
const imgW = 260;
const imgH = 340;
const cornerRadius = 12;
ctx.save();
ctx.beginPath();
ctx.moveTo(imgX + cornerRadius, imgY);
ctx.lineTo(imgX + imgW - cornerRadius, imgY);
ctx.quadraticCurveTo(imgX + imgW, imgY, imgX + imgW, imgY + cornerRadius);
ctx.lineTo(imgX + imgW, imgY + imgH - cornerRadius);
ctx.quadraticCurveTo(imgX + imgW, imgY + imgH, imgX + imgW - cornerRadius, imgY + imgH);
ctx.lineTo(imgX + cornerRadius, imgY + imgH);
ctx.quadraticCurveTo(imgX, imgY + imgH, imgX, imgY + imgH - cornerRadius);
ctx.lineTo(imgX, imgY + cornerRadius);
ctx.quadraticCurveTo(imgX, imgY, imgX + cornerRadius, imgY);
ctx.closePath();
ctx.clip();
// Aspect ratio covering calculations
const aspectCanvas = imgW / imgH;
const aspectImg = originalImg.width / originalImg.height;
let sWidth = originalImg.width;
let sHeight = originalImg.height;
let sx = 0, sy = 0;
if (aspectImg > aspectCanvas) {
sWidth = originalImg.height * aspectCanvas;
sx = (originalImg.width - sWidth) / 2;
} else {
sHeight = originalImg.width / aspectCanvas;
sy = (originalImg.height - sHeight) / 2;
}
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, imgX, imgY, imgW, imgH);
ctx.restore();
// Add border to image
ctx.strokeStyle = `rgba(255,255,255, 0.3)`;
ctx.lineWidth = 2;
ctx.stroke();
// Apply Typography and Info
const textStartX = 340;
const fontPrimary = "'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif";
ctx.fillStyle = "#ffffff";
ctx.font = `bold 24px ${fontPrimary}`;
ctx.fillText("CHARACTER V.A. MATCH", textStartX, 80);
// Aura / Vibe section
ctx.font = `600 14px ${fontPrimary}`;
ctx.fillStyle = "#888888";
ctx.fillText("DETECTED CHARACTER VIBE:", textStartX, 130);
// Adjust derived color brightness to ensure legibility on dark background
let textR = r + 80, textG = g + 80, textB = b + 80;
textR = Math.min(255, textR); textG = Math.min(255, textG); textB = Math.min(255, textB);
ctx.fillStyle = `rgb(${textR}, ${textG}, ${textB})`;
ctx.font = `bold 18px ${fontPrimary}`;
ctx.fillText(vibe.toUpperCase(), textStartX, 155);
// VA Subtitle
ctx.font = `600 14px ${fontPrimary}`;
ctx.fillStyle = "#888888";
ctx.fillText("SUGGESTED VOICE ACTOR:", textStartX, 215);
// Chosen VA Name
ctx.fillStyle = "#ffffff";
ctx.font = `bold 42px ${fontPrimary}`;
ctx.fillText(chosenVA.name, textStartX, 260);
// Tags (Language / Gender)
const tagText = `${chosenVA.lang} Dub • ${chosenVA.gender} Voice`;
ctx.fillStyle = "#e5b642";
ctx.font = `bold 16px ${fontPrimary}`;
ctx.fillText(tagText, textStartX, 285);
// Roles Subtitle
ctx.font = `600 14px ${fontPrimary}`;
ctx.fillStyle = "#888888";
ctx.fillText("NOTABLE ROLES INCL.", textStartX, 335);
// Roles Text
ctx.fillStyle = "#dedede";
ctx.font = `italic 18px ${fontPrimary}`;
ctx.fillText(chosenVA.roles, textStartX, 360);
return canvas;
}
Apply Changes