You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg) {
const FACEAPI_SCRIPT_URL = 'https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js';
const MODEL_URL = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js/weights';
// Validate originalImg: must be a loaded HTMLImageElement
if (!(originalImg instanceof HTMLImageElement) || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
console.error("processImage: Provided originalImg is not a valid, loaded HTMLImageElement.", originalImg);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 300; // Default error canvas size
errorCanvas.height = 150;
const ctx = errorCanvas.getContext('2d');
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
ctx.fillStyle = 'red';
ctx.font = `bold ${Math.min(18, errorCanvas.height * 0.12)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Invalid or Unloaded Image', errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
// Helper to ensure face-api.js is loaded
const ensureFaceApiLoaded = () => {
return new Promise((resolve, reject) => {
// Check if faceapi is already loaded and seems initialized
if (typeof faceapi !== 'undefined' && faceapi.nets && faceapi.nets.ssdMobilenetv1) {
resolve();
return;
}
// If loading is already in Pprogress by another call, wait for its promise
if (window.faceApiLoadingPromise instanceof Promise) {
// This ensures concurrent calls don't try to load the script multiple times
return window.faceApiLoadingPromise.then(resolve).catch(reject);
}
// This is the first call, or a call after a previous load attempt failed and cleared the promise
let script = document.querySelector(`script[src="${FACEAPI_SCRIPT_URL}"]`);
if (!script) {
script = document.createElement('script');
script.src = FACEAPI_SCRIPT_URL;
script.async = true; // Load asynchronously
document.head.appendChild(script);
}
// Create a global promise to track the loading status of face-api.js
window.faceApiLoadingPromise = new Promise((res, rej) => {
script.onload = () => {
if (typeof faceapi !== 'undefined' && faceapi.nets && faceapi.nets.ssdMobilenetv1) {
res(); // Successfully loaded and initialized
} else {
// Script loaded, but faceapi global is not as expected
window.faceApiLoadingPromise = null; // Clear promise to allow a retry on next processImage call
rej(new Error('face-api.js loaded but `faceapi` global is incomplete or invalid.'));
}
};
script.onerror = (errEvent) => {
console.error("Script load error for face-api.js:", errEvent);
window.faceApiLoadingPromise = null; // Clear promise for retry
if(script && script.parentNode) { // Optional: remove failed script tag
script.parentNode.removeChild(script);
}
rej(new Error(`Failed to load script: ${FACEAPI_SCRIPT_URL}. Check network or CDN status.`));
};
});
return window.faceApiLoadingPromise.then(resolve).catch(reject);
});
};
// Helper to display errors on a canvas
const displayErrorOnCanvas = (targetCanvas, message) => {
const ctx = targetCanvas.getContext('2d');
let barHeight = Math.min(60, targetCanvas.height * 0.25, targetCanvas.width * 0.8);
barHeight = Math.max(30, barHeight); // Ensure minimum height
ctx.fillStyle = 'rgba(200, 0, 0, 0.85)'; // Semi-transparent red bar
ctx.fillRect(0, targetCanvas.height / 2 - barHeight / 2, targetCanvas.width, barHeight);
ctx.fillStyle = 'white';
// Responsive font size, ensuring it fits and is readable
ctx.font = `bold ${Math.max(12, Math.min(18, barHeight * 0.35))}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(message, targetCanvas.width / 2, targetCanvas.height / 2);
};
// Try to load library and models
try {
await ensureFaceApiLoaded();
// Load the SSD Mobilenet V1 model for face detection if not already loaded
if (!faceapi.nets.ssdMobilenetv1.isLoaded) {
await faceapi.nets.ssdMobilenetv1.loadFromUri(MODEL_URL);
}
// For other features like landmarks, expressions, age/gender, load other models:
// if (!faceapi.nets.faceLandmark68Net.isLoaded) await faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL);
// if (!faceapi.nets.faceExpressionNet.isLoaded) await faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL);
// if (!faceapi.nets.ageGenderNet.isLoaded) await faceapi.nets.ageGenderNet.loadFromUri(MODEL_URL);
} catch (error) {
console.error("Initialization Error (library/models):", error.message);
// Create a new canvas, draw the original image, and overlay the error message
const initErrorCanvas = document.createElement('canvas');
initErrorCanvas.width = originalImg.naturalWidth;
initErrorCanvas.height = originalImg.naturalHeight;
const initCtx = initErrorCanvas.getContext('2d');
initCtx.drawImage(originalImg, 0, 0, initErrorCanvas.width, initErrorCanvas.height);
const friendlyMessage = error.message.includes('load script') || error.message.includes('faceapi global')
? 'Error: AI library failed to load.'
: 'Error: AI models failed to load.';
displayErrorOnCanvas(initErrorCanvas, friendlyMessage);
return initErrorCanvas;
}
// Create canvas for output
const canvas = document.createElement('canvas');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
const ctx = canvas.getContext('2d');
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
try {
// Perform face detection
// SsdMobilenetv1Options can set minConfidence (default 0.5), maxResults etc.
const detections = await faceapi.detectAllFaces(originalImg, new faceapi.SsdMobilenetv1Options());
if (detections && detections.length > 0) {
// Draw bounding boxes for detected faces
detections.forEach(detection => {
const box = detection.box; // { x, y, width, height }
ctx.strokeStyle = 'lime'; // Bright green for visibility
// Dynamic line width based on image size, with min/max
ctx.lineWidth = Math.max(2, Math.round(Math.min(canvas.width, canvas.height) * 0.007));
ctx.beginPath();
ctx.rect(box.x, box.y, box.width, box.height);
ctx.stroke();
});
} else {
// Display a message if no faces are detected
let barHeight = Math.min(40, canvas.height * 0.1);
barHeight = Math.max(20, barHeight); // Min height for the message bar
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; // Semi-transparent dark bar
ctx.fillRect(0, canvas.height - barHeight, canvas.width, barHeight); // At the bottom
ctx.fillStyle = 'white';
// Dynamic font size for the message
ctx.font = `bold ${Math.max(12, Math.min(16, barHeight * 0.5))}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('No faces detected.', canvas.width / 2, canvas.height - barHeight / 2);
}
} catch (detectError) {
console.error("Face Detection Error:", detectError);
// Display detection error on the main canvas (which already has the image)
displayErrorOnCanvas(canvas, 'Error during face detection.');
}
return canvas;
}
Apply Changes