Please bookmark this page to avoid losing your image tool!

Image Face Swapper

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
async function processImage(originalImg, faceImgDataURL = "") {
    // If no face image is provided, return the original image on a canvas.
    if (!faceImgDataURL) {
        console.warn("No face image data URL provided. Returning original image.");
        const canvas = document.createElement('canvas');
        canvas.width = originalImg.width;
        canvas.height = originalImg.height;
        canvas.getContext('2d').drawImage(originalImg, 0, 0);
        return canvas;
    }

    // Helper function to dynamically load the face-api.js library and models.
    const loadFaceApi = async () => {
        // If the library is already loaded, do nothing.
        if (window.faceapi && window.faceapi.nets.tinyFaceDetector.params) {
            return;
        }

        const loadScript = (src) => new Promise((resolve, reject) => {
            if (document.querySelector(`script[src="${src}"]`)) {
                return resolve();
            }
            const script = document.createElement('script');
            script.src = src;
            script.onload = resolve;
            script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
            document.head.appendChild(script);
        });
        
        // Load the face-api.js library.
        await loadScript("https://cdn.jsdelivr.net/npm/@vladmandic/face-api/dist/face-api.min.js");

        // Load the required models.
        const MODEL_URL = 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model/';
        await Promise.all([
            faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
            faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL)
        ]);
    };

    try {
        await loadFaceApi();
    } catch (error) {
        console.error(error);
        // Fallback to returning the original image on error.
        const canvas = document.createElement('canvas');
        canvas.width = originalImg.width;
        canvas.height = originalImg.height;
        canvas.getContext('2d').drawImage(originalImg, 0, 0);
        return canvas;
    }

    // Load the provided face image from its data URL.
    const faceImg = new Image();
    faceImg.crossOrigin = "anonymous";
    const faceImgPromise = new Promise((resolve, reject) => {
        faceImg.onload = resolve;
        faceImg.onerror = reject;
        faceImg.src = faceImgDataURL;
    });
    await faceImgPromise;
   
    // Set up the main canvas and draw the original image on it.
    const canvas = document.createElement('canvas');
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    ctx.drawImage(originalImg, 0, 0);

    // Detect faces and landmarks in both images.
    const detectionOptions = new faceapi.TinyFaceDetectorOptions({ inputSize: 416 });
    const targetFaces = await faceapi.detectAllFaces(originalImg, detectionOptions).withFaceLandmarks();
    const sourceFaceResult = await faceapi.detectSingleFace(faceImg, detectionOptions).withFaceLandmarks();

    if (!sourceFaceResult || targetFaces.length === 0) {
        console.warn("Could not detect a face in the source or target image.");
        return canvas; // Return original if no faces are found.
    }

    const sourceLandmarks = sourceFaceResult.landmarks;
    
    // Create a feathered mask of the source face to ensure soft blending.
    // 1. Create a smaller temporary canvas for efficiency.
    const PADDING = 30;
    const sourceBox = sourceFaceResult.detection.box;
    const isolatedFaceCanvas = document.createElement('canvas');
    isolatedFaceCanvas.width = sourceBox.width + PADDING * 2;
    isolatedFaceCanvas.height = sourceBox.height + PADDING * 2;
    const isolatedFaceCtx = isolatedFaceCanvas.getContext('2d');
    
    // 2. Center the drawing operations on this smaller canvas.
    const offsetX = -sourceBox.x + PADDING;
    const offsetY = -sourceBox.y + PADDING;
    isolatedFaceCtx.translate(offsetX, offsetY);

    // 3. Draw a feathered polygon based on the jaw and eyebrow landmarks.
    isolatedFaceCtx.shadowColor = 'black';
    isolatedFaceCtx.shadowBlur = 20; // Controls the feathering size.
    isolatedFaceCtx.fillStyle = 'black';
    isolatedFaceCtx.beginPath();
    const jaw = sourceLandmarks.getJawOutline();
    const eyebrowLeft = sourceLandmarks.getLeftEyeBrow();
    const eyebrowRight = sourceLandmarks.getRightEyeBrow();
    isolatedFaceCtx.moveTo(jaw[0].x, jaw[0].y);
    jaw.slice(1).forEach(pt => isolatedFaceCtx.lineTo(pt.x, pt.y));
    eyebrowLeft.slice().reverse().forEach(pt => isolatedFaceCtx.lineTo(pt.x, pt.y));
    eyebrowRight.forEach(pt => isolatedFaceCtx.lineTo(pt.x, pt.y));
    isolatedFaceCtx.closePath();
    isolatedFaceCtx.fill();
    
    // 4. Use the mask to clip out the face from the source image.
    isolatedFaceCtx.shadowBlur = 0;
    isolatedFaceCtx.globalCompositeOperation = 'source-in';
    isolatedFaceCtx.drawImage(faceImg, 0, 0);


    // For each face detected in the main image, perform the swap.
    for (const targetFace of targetFaces) {
        const targetLandmarks = targetFace.landmarks;

        // Calculate transformation parameters based on landmarks.
        // Anchor points (using the nose for stable positioning).
        const sourceAnchor = sourceLandmarks.getNose()[3];
        const targetAnchor = targetLandmarks.getNose()[3];

        // Scale based on the distance between the eyes.
        const sourceEyeDist = sourceLandmarks.getRightEye()[0].sub(sourceLandmarks.getLeftEye()[3]).magnitude();
        const targetEyeDist = targetLandmarks.getRightEye()[0].sub(targetLandmarks.getLeftEye()[3]).magnitude();
        const scale = targetEyeDist / sourceEyeDist;

        // Rotation based on the angle of the eyes.
        const sourceEyeVec = sourceLandmarks.getRightEye()[0].sub(sourceLandmarks.getLeftEye()[3]);
        const targetEyeVec = targetLandmarks.getRightEye()[0].sub(targetLandmarks.getLeftEye()[3]);
        const angle = Math.atan2(targetEyeVec.y, targetEyeVec.x) - Math.atan2(sourceEyeVec.y, sourceEyeVec.x);
        
        // Calculate the drawing position for the isolated face, accounting for its own canvas offsets.
        const drawX = -(sourceAnchor.x - sourceBox.x + PADDING);
        const drawY = -(sourceAnchor.y - sourceBox.y + PADDING);

        // Apply transformations and draw the new face onto the main canvas.
        ctx.save();
        ctx.translate(targetAnchor.x, targetAnchor.y);
        ctx.rotate(angle);
        ctx.scale(scale, scale);
        ctx.drawImage(isolatedFaceCanvas, drawX, drawY);
        ctx.restore();
    }

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Face Swapper is an online tool that allows users to seamlessly swap faces between images. By providing an original image and a face image, users can effortlessly replace the faces in the original image with the selected face. This tool is ideal for fun and creative applications, such as creating humorous images, memes, or art. It employs advanced face detection and landmark recognition to ensure that the swapped faces are accurately positioned and blended into the original environment.

Leave a Reply

Your email address will not be published. Required fields are marked *