You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, character = "Baymax", effectIntensity = "100") {
const canvas = document.createElement('canvas');
const w = originalImg.width || originalImg.naturalWidth;
const h = originalImg.height || originalImg.naturalHeight;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Draw original image
ctx.drawImage(originalImg, 0, 0, w, h);
let faces = [];
// Attempt to use modern native FaceDetector API (available in some browsers)
try {
if ('FaceDetector' in window) {
const faceDetector = new FaceDetector({ fastMode: false });
faces = await faceDetector.detect(canvas);
}
} catch (e) {
console.warn("Native FaceDetector error, falling back to heuristic detection");
}
// Fallback "AI": Skin-color segmentation heuristic if no native detector
if (!faces || faces.length === 0) {
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
let skinXSum = 0, skinYSum = 0, skinCount = 0;
const skinPixels = [];
for (let y = 0; y < h; y += 4) {
for (let x = 0; x < w; x += 4) {
const i = (y * w + x) * 4;
const r = data[i], g = data[i+1], b = data[i+2];
// basic skin color bounds
if (r > 95 && g > 40 && b > 20 &&
(Math.max(r, g, b) - Math.min(r, g, b)) > 15 &&
Math.abs(r - g) > 15 && r > g && r > b) {
skinXSum += x;
skinYSum += y;
skinCount++;
skinPixels.push({x, y});
}
}
}
// Only use fallback if decent amount of skin pixels found
if (skinCount > ( (w/4) * (h/4) * 0.01 )) {
const cx = skinXSum / skinCount;
const cy = skinYSum / skinCount;
// Calculate standard deviation to approximate face size
let varX = 0, varY = 0;
for (let i = 0; i < skinCount; i++) {
varX += Math.pow(skinPixels[i].x - cx, 2);
varY += Math.pow(skinPixels[i].y - cy, 2);
}
const stdX = Math.sqrt(varX / skinCount);
const stdY = Math.sqrt(varY / skinCount);
let radius = Math.max(stdX, stdY) * 1.5;
radius = Math.min(radius, Math.min(w, h) * 0.4); // Clamp size
faces.push({
boundingBox: {
x: cx - radius,
y: cy - radius,
width: radius * 2,
height: radius * 2
}
});
} else {
// Ultimate fallback: Just put the character directly in upper center
const radius = Math.min(w, h) * 0.25;
faces.push({
boundingBox: {
x: w/2 - radius,
y: h/3 - radius,
width: radius * 2,
height: radius * 2
}
});
}
}
// Function to draw localized character AI replacement over detecting area
function drawCharacterReplacement(cx, cy, radius, charName) {
ctx.save();
ctx.translate(cx, cy);
if (charName.toLowerCase() === 'hiro') {
// Draw Hiro Hamada stylized helmet/face
ctx.fillStyle = '#3a2b5e'; // Dark purplish blue helmet
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowBlur = radius * 0.3;
// Helmet dome
ctx.beginPath();
ctx.arc(0, -radius * 0.1, radius * 1.1, Math.PI, 0);
ctx.lineTo(radius * 1.1, radius * 0.8);
ctx.lineTo(-radius * 1.1, radius * 0.8);
ctx.fill();
ctx.shadowColor = "transparent";
// Face opening
ctx.fillStyle = '#ffdfc4';
ctx.beginPath();
ctx.ellipse(0, radius * 0.3, radius * 0.65, radius * 0.5, 0, 0, Math.PI*2);
ctx.fill();
// Expressions / Eyes
ctx.fillStyle = '#111';
ctx.lineWidth = radius * 0.05;
// Left eye
ctx.beginPath();
ctx.ellipse(-radius*0.25, radius*0.2, radius*0.12, radius*0.06, 0.1, 0, Math.PI*2);
ctx.fill();
// Right eye
ctx.beginPath();
ctx.ellipse(radius*0.25, radius*0.2, radius*0.12, radius*0.06, -0.1, 0, Math.PI*2);
ctx.fill();
// Cheek mark
ctx.fillStyle = '#d96c80';
ctx.beginPath(); ctx.arc(-radius*0.35, radius*0.4, radius*0.08, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(radius*0.35, radius*0.4, radius*0.08, 0, Math.PI*2); ctx.fill();
} else {
// Default: Baymax logic
const headW = radius * 1.35;
const headH = radius * 0.95;
// Ambient shadow
ctx.shadowColor = "rgba(0,0,0,0.3)";
ctx.shadowBlur = radius * 0.2;
ctx.shadowOffsetY = radius * 0.1;
// Baymax white head
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.ellipse(0, 0, headW, headH, 0, 0, Math.PI * 2);
ctx.fill();
// Light grey stroke for definition
ctx.lineWidth = radius * 0.03;
ctx.strokeStyle = '#e0e0e0';
ctx.stroke();
ctx.shadowColor = "transparent";
// Baymax Eyes
const eyeOffsetX = headW * 0.35;
const eyeRadius = headH * 0.14;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#000000';
ctx.lineWidth = eyeRadius * 0.35;
// Connecting line
ctx.beginPath();
ctx.moveTo(-eyeOffsetX, 0);
ctx.lineTo(eyeOffsetX, 0);
ctx.stroke();
// Left eye
ctx.beginPath();
ctx.arc(-eyeOffsetX, 0, eyeRadius, 0, Math.PI * 2);
ctx.fill();
// Right eye
ctx.beginPath();
ctx.arc(eyeOffsetX, 0, eyeRadius, 0, Math.PI * 2);
ctx.fill();
// Highlight reflection in eyes
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-eyeOffsetX + eyeRadius*0.2, -eyeRadius*0.2, eyeRadius*0.25, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(eyeOffsetX + eyeRadius*0.2, -eyeRadius*0.2, eyeRadius*0.25, 0, Math.PI*2);
ctx.fill();
}
ctx.restore();
}
// Apply the replacements
faces.forEach(face => {
const cx = face.boundingBox.x + face.boundingBox.width / 2;
const cy = face.boundingBox.y + face.boundingBox.height / 2;
const radius = Math.max(face.boundingBox.width, face.boundingBox.height) / 2;
drawCharacterReplacement(cx, cy, radius, character);
});
// Apply AU (Alternate Universe) AI comic tone map/filter
const intensity = parseFloat(effectIntensity);
if (!isNaN(intensity) && intensity > 0) {
// Sci-Fi San Fransokyo vibe (slight magenta/cyan overlay)
ctx.fillStyle = `rgba(120, 50, 180, ${intensity * 0.0015})`;
ctx.globalCompositeOperation = 'overlay';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = `rgba(50, 200, 255, ${intensity * 0.001})`;
ctx.globalCompositeOperation = 'soft-light';
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
}
return canvas;
}
Apply Changes