You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, skinColorParam = "AAD0AA", eyeScleraColorParam = "FF0000", brainColorParam = "F5C6C6", domeOpacityParam = 0.2) {
// --- Helper Functions ---
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 }; // Default to black if invalid
}
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function componentToHex(c) {
const hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
function adjustColorLightness(hexColor, percent) {
const rgb = hexToRgb(hexColor);
let hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
hsl[2] = Math.max(0, Math.min(1, hsl[2] + (percent / 100)));
const newRgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
return rgbToHex(newRgb[0], newRgb[1], newRgb[2]);
}
// --- End Helper Functions ---
const skinMainRgb = hexToRgb(skinColorParam);
const brainMainRgb = hexToRgb(brainColorParam);
const CW = 500; // Canvas Width
const CH = 650; // Canvas Height
const canvas = document.createElement('canvas');
canvas.width = CW;
canvas.height = CH;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, CW, CH);
ctx.fillStyle = "white"; // Optional: background for testing
ctx.fillRect(0,0,CW,CH);
// 1. Draw Alien Head Base (Brain and Face Area Shapes)
ctx.fillStyle = `#${brainColorParam}`;
ctx.beginPath();
ctx.ellipse(CW / 2, CH * 0.35, CW * 0.45, CH * 0.30, 0, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = `#${skinColorParam}`;
ctx.beginPath();
// Start from top-left of face area, under the brain
ctx.moveTo(CW * 0.28, CH * 0.55);
// Left cheek/jaw slope down to chin
ctx.bezierCurveTo(CW * 0.25, CH * 0.75, CW * 0.4, CH * 0.86, CW / 2, CH * 0.87);
// Chin to right cheek/jaw slope up
ctx.bezierCurveTo(CW * 0.6, CH * 0.86, CW * 0.75, CH * 0.75, CW * 0.72, CH * 0.55);
// Connect to bottom of brain smoothly (create a slight overlap area)
ctx.lineTo(CW * 0.75, CH * 0.40); // Upper right of face area
ctx.arcTo(CW / 2, CH * 0.35, CW * 0.25, CH * 0.40, CW * 0.3); // Curve along bottom of brain
ctx.lineTo(CW * 0.25, CH * 0.40); // Upper left of face area
ctx.closePath();
ctx.fill();
// 2. Draw Brain Texture
const brainHsl = rgbToHsl(brainMainRgb.r, brainMainRgb.g, brainMainRgb.b);
const textureLightnessAdjust = brainHsl[2] < 0.5 ? 20 : -20;
const brainTextureColor = adjustColorLightness(brainColorParam, textureLightnessAdjust);
ctx.strokeStyle = brainTextureColor;
ctx.lineWidth = Math.max(1.5, CW / 250);
// More structured convolutions for brain texture
const numConvolutions = 6;
const brainTopY = CH * 0.05; // Top of brain ellipse
const brainBottomY = CH * 0.60; // Bottom of brain ellipse
const brainCenterX = CW / 2;
const brainRadiusX = CW * 0.45;
for (let i = 0; i < numConvolutions; i++) {
ctx.beginPath();
// Start on left side of brain
let startY = brainTopY + (brainBottomY - brainTopY) * (Math.random() * 0.4 + i * 0.1);
let startX = brainCenterX - Math.sqrt(1 - Math.pow((startY - CH * 0.35) / (CH * 0.30), 2)) * brainRadiusX * (0.8 + Math.random()*0.2) ;
if (isNaN(startX)) startX = brainCenterX - brainRadiusX * (0.8 + Math.random()*0.2);
ctx.moveTo(startX, startY);
// Random curves across the brain
ctx.bezierCurveTo(
brainCenterX - brainRadiusX * (Math.random() * 0.5), startY + (Math.random() - 0.5) * CH * 0.1, // CP1
brainCenterX + brainRadiusX * (Math.random() * 0.5), startY + (Math.random() - 0.5) * CH * 0.1, // CP2
startX + brainRadiusX * 2 * (0.8 + Math.random()*0.2), startY + (Math.random() - 0.5) * CH * 0.05 // End point (roughly other side)
);
ctx.stroke();
}
// 3. Process and Place Original Face
const faceCanvas = document.createElement('canvas');
const faceCtx = faceCanvas.getContext('2d');
const faceProcessSize = 200; // Process face at this resolution
faceCanvas.width = faceProcessSize;
faceCanvas.height = faceProcessSize;
let cropInputX, cropInputY, cropInputSize;
if (originalImg.width / originalImg.height > 1) { // Landscape or square
cropInputSize = originalImg.height;
cropInputX = (originalImg.width - cropInputSize) / 2;
cropInputY = 0;
} else { // Portrait
cropInputSize = originalImg.width;
cropInputX = 0;
cropInputY = (originalImg.height - cropInputSize) / 3; // Try to get upper part
}
cropInputX = Math.max(0, cropInputX);
cropInputY = Math.max(0, cropInputY);
faceCtx.drawImage(originalImg, cropInputX, cropInputY, cropInputSize, cropInputSize, 0, 0, faceProcessSize, faceProcessSize);
const imgData = faceCtx.getImageData(0, 0, faceProcessSize, faceProcessSize);
const pixels = imgData.data;
const targetSkinHsl = rgbToHsl(skinMainRgb.r, skinMainRgb.g, skinMainRgb.b);
for (let i = 0; i < pixels.length; i += 4) {
let r = pixels[i], g = pixels[i+1], b = pixels[i+2];
let hsl = rgbToHsl(r, g, b);
hsl[0] = targetSkinHsl[0];
hsl[1] = Math.max(0.05, hsl[1] * 0.6); // Desaturate
hsl[2] = hsl[2] * 0.95; // Slightly darken
let newRgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
pixels[i] = newRgb[0];
pixels[i+1] = newRgb[1];
pixels[i+2] = newRgb[2];
}
faceCtx.putImageData(imgData, 0, 0);
const facePlaceWidth = CW * 0.40;
const facePlaceHeight = facePlaceWidth;
const facePlaceX = CW / 2 - facePlaceWidth / 2;
const facePlaceY = CH * 0.60 - facePlaceHeight / 2;
ctx.save();
ctx.beginPath();
ctx.ellipse(facePlaceX + facePlaceWidth/2, facePlaceY + facePlaceHeight/2, facePlaceWidth/2 * 0.90, facePlaceHeight/2 * 0.85, 0, 0, 2 * Math.PI);
ctx.clip();
ctx.drawImage(faceCanvas, facePlaceX, facePlaceY, facePlaceWidth, facePlaceHeight);
ctx.restore();
// 4. Add Alien Eyes (on top of the tinted face)
const eyeRadiusX = facePlaceWidth * 0.22;
const eyeRadiusY = facePlaceHeight * 0.25;
const eyeVerticalPos = facePlaceY + facePlaceHeight * 0.40;
// Left Eye
const leftEyeX = facePlaceX + facePlaceWidth * 0.28;
ctx.fillStyle = `#${eyeScleraColorParam}`;
ctx.beginPath();
ctx.ellipse(leftEyeX, eyeVerticalPos, eyeRadiusX, eyeRadiusY, -0.1, 0, 2 * Math.PI); // Slight tilt
ctx.fill();
ctx.fillStyle = "black"; // Pupil
ctx.beginPath();
ctx.ellipse(leftEyeX, eyeVerticalPos, eyeRadiusX * 0.5, eyeRadiusY * 0.5, -0.1, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "rgba(255,255,255,0.7)"; // Highlight
ctx.beginPath();
ctx.ellipse(leftEyeX + eyeRadiusX * 0.25, eyeVerticalPos - eyeRadiusY * 0.25, eyeRadiusX * 0.2, eyeRadiusY * 0.2, -0.1, 0, Math.PI * 2);
ctx.fill();
// Right Eye
const rightEyeX = facePlaceX + facePlaceWidth * 0.72;
ctx.fillStyle = `#${eyeScleraColorParam}`;
ctx.beginPath();
ctx.ellipse(rightEyeX, eyeVerticalPos, eyeRadiusX, eyeRadiusY, 0.1, 0, 2 * Math.PI); // Slight tilt
ctx.fill();
ctx.fillStyle = "black"; // Pupil
ctx.beginPath();
ctx.ellipse(rightEyeX, eyeVerticalPos, eyeRadiusX * 0.5, eyeRadiusY * 0.5, 0.1, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "rgba(255,255,255,0.7)"; // Highlight
ctx.beginPath();
ctx.ellipse(rightEyeX + eyeRadiusX * 0.25, eyeVerticalPos - eyeRadiusY * 0.25, eyeRadiusX * 0.2, eyeRadiusY * 0.2, 0.1, 0, Math.PI * 2);
ctx.fill();
// 5. Optional: Transparent Dome
if (domeOpacityParam > 0 && domeOpacityParam <=1) {
ctx.fillStyle = `rgba(190, 210, 255, ${domeOpacityParam})`;
ctx.beginPath();
// Path for dome starting from bottom-left, over the top, to bottom-right
const domeBaseY = CH * 0.50;
const domePeakY = CH * -0.05; // Control point for peak
ctx.moveTo(CW * 0.03, domeBaseY);
ctx.bezierCurveTo(
CW * 0.1, domePeakY, // CP1 (high left)
CW * 0.9, domePeakY, // CP2 (high right)
CW * 0.97, domeBaseY // End point (bottom-right)
);
ctx.fill();
// Dome highlights
ctx.strokeStyle = `rgba(255, 255, 255, ${Math.min(1.0, domeOpacityParam * 2.5)})`;
ctx.lineWidth = Math.max(2, CW / 150);
ctx.beginPath();
ctx.arc(CW * 0.4, CH * 0.15, CW * 0.15, Math.PI * 1.35, Math.PI * 1.65);
ctx.stroke();
ctx.beginPath();
ctx.arc(CW * 0.65, CH * 0.1, CW * 0.08, Math.PI * 1.25, Math.PI * 1.75);
ctx.stroke();
}
return canvas;
}
Apply Changes