You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, maskType = 'medical', maskColor = '#00a8cc', maskScale = '1.0') {
const scale = parseFloat(maskScale) || 1.0;
const validMaskType = maskType.toLowerCase() === 'masquerade' ? 'masquerade' : 'medical';
const canvas = document.createElement('canvas');
canvas.width = originalImg.width || originalImg.naturalWidth;
canvas.height = originalImg.height || originalImg.naturalHeight;
const ctx = canvas.getContext('2d');
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Dynamically load face-api.js if it isn't already available
if (typeof window.faceapi === 'undefined') {
if (!window.faceapiLoadingPromise) {
window.faceapiLoadingPromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/dist/face-api.min.js';
script.onload = resolve;
script.onerror = () => reject(new Error('Failed to load Face-API.js library'));
document.head.appendChild(script);
});
}
try {
await window.faceapiLoadingPromise;
} catch (err) {
console.error(err);
}
}
let boxes = [];
if (typeof window.faceapi !== 'undefined') {
try {
// Load the face detection model
if (!window.faceapiModelLoaded) {
const modelPath = 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model/';
await window.faceapi.nets.tinyFaceDetector.loadFromUri(modelPath);
window.faceapiModelLoaded = true;
}
// Detect all faces in the canvas
const detections = await window.faceapi.detectAllFaces(
canvas,
new window.faceapi.TinyFaceDetectorOptions()
);
if (detections && detections.length > 0) {
boxes = detections.map(d => d.box);
}
} catch (err) {
console.error('Face detection failed, using fallback placement.', err);
}
}
// If no face found or API failed, place a default mask in the center
if (boxes.length === 0) {
boxes.push({
x: canvas.width * 0.35,
y: canvas.height * 0.35,
width: canvas.width * 0.3,
height: canvas.width * 0.3
});
}
// Helper function to draw rectangles with border radius
const roundRect = (ctx, x, y, w, h, r) => {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
};
// Helper to draw the specified mask based on face dimensions
const drawMask = (box) => {
ctx.save();
const centerX = box.x + box.width / 2;
let w, h, x, y;
if (validMaskType === 'masquerade') {
w = box.width * 1.2 * scale;
h = box.height * 0.45 * scale;
x = centerX - w / 2;
y = box.y + box.height * 0.15;
// Offscreen canvas for hole punching the eyes
const mCanv = document.createElement('canvas');
mCanv.width = w;
mCanv.height = h;
const mCtx = mCanv.getContext('2d');
mCtx.translate(w / 2, h / 2);
// Shape of the domino masquerade mask
mCtx.fillStyle = maskColor || '#000000';
mCtx.beginPath();
mCtx.moveTo(0, -h * 0.3);
mCtx.quadraticCurveTo(w * 0.3, -h * 0.6, w * 0.5, -h * 0.2);
mCtx.quadraticCurveTo(w * 0.5, h * 0.4, w * 0.3, h * 0.5);
mCtx.quadraticCurveTo(w * 0.15, h * 0.4, 0, h * 0.1);
mCtx.quadraticCurveTo(-w * 0.15, h * 0.4, -w * 0.3, h * 0.5);
mCtx.quadraticCurveTo(-w * 0.5, h * 0.4, -w * 0.5, -h * 0.2);
mCtx.quadraticCurveTo(-w * 0.3, -h * 0.6, 0, -h * 0.3);
mCtx.fill();
// Punch the eye holes
mCtx.globalCompositeOperation = 'destination-out';
mCtx.fillStyle = '#ffffff';
mCtx.beginPath(); // Right eye
mCtx.ellipse(w * 0.22, 0, w * 0.08, h * 0.12, 0.1, 0, Math.PI * 2);
mCtx.fill();
mCtx.beginPath(); // Left eye
mCtx.ellipse(-w * 0.22, 0, w * 0.08, h * 0.12, -0.1, 0, Math.PI * 2);
mCtx.fill();
ctx.drawImage(mCanv, x, y);
// Draw masquerade ties
ctx.strokeStyle = 'rgba(0,0,0,0.6)';
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(x, y + h / 2);
ctx.lineTo(box.x - box.width * 0.1, box.y + box.height * 0.25);
ctx.moveTo(x + w, y + h / 2);
ctx.lineTo(box.x + box.width * 1.1, box.y + box.height * 0.25);
ctx.stroke();
} else { // Medical Mask
w = box.width * 1.05 * scale;
h = box.height * 0.55 * scale;
x = centerX - w / 2;
y = box.y + box.height * 0.45;
// Draw elastic ear loops
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2.5 * scale;
ctx.beginPath();
// Left loop
ctx.moveTo(x + w * 0.05, y + h * 0.15);
ctx.quadraticCurveTo(box.x - box.width * 0.2, y + h * 0.5, x + w * 0.05, y + h * 0.85);
// Right loop
ctx.moveTo(x + w * 0.95, y + h * 0.15);
ctx.quadraticCurveTo(box.x + box.width * 1.2, y + h * 0.5, x + w * 0.95, y + h * 0.85);
ctx.stroke();
// Mask body
ctx.fillStyle = maskColor || '#a3e4d7'; // Default medical cyan
roundRect(ctx, x, y, w, h, 10 * scale);
ctx.fill();
// Medical pleats/folds texture
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 1.5 * scale;
ctx.beginPath();
ctx.moveTo(x + w * 0.05, y + h * 0.33);
ctx.lineTo(x + w * 0.95, y + h * 0.33);
ctx.moveTo(x + w * 0.05, y + h * 0.66);
ctx.lineTo(x + w * 0.95, y + h * 0.66);
ctx.stroke();
// Adjustable nose wire
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.lineWidth = 3 * scale;
ctx.beginPath();
ctx.moveTo(x + w * 0.35, y + 2 * scale);
ctx.lineTo(x + w * 0.65, y + 2 * scale);
ctx.stroke();
}
ctx.restore();
};
// Apply masks to all detected faces
for (const box of boxes) {
drawMask(box);
}
return canvas;
}
Apply Changes