Please bookmark this page to avoid losing your image tool!

Image Mustache Distortion Filter

(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, mustacheCenterXRatio = 0.5, mustacheCenterYRatio = 0.6, mustacheWidthRatio = 0.3, mustacheHeightRatio = 0.1, strength = 0.5) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); // willReadFrequently for performance with getImageData/putImageData

    // Ensure originalImg has dimensions. If not, it might not be loaded.
    // The function will produce a 0x0 canvas if img dimensions are 0.
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        // Return an empty canvas if image has no dimensions
        return canvas;
    }

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    
    const imgWidth = canvas.width;
    const imgHeight = canvas.height;

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        // This can happen due to tainted canvas (e.g. cross-origin image without CORS)
        console.error("Error getting ImageData:", e);
        // Return canvas with original image drawn if we can't process pixels
        return canvas; 
    }

    const dstImageData = ctx.createImageData(imgWidth, imgHeight);
    const dstData = dstImageData.data;

    const centerX = imgWidth * mustacheCenterXRatio;
    const centerY = imgHeight * mustacheCenterYRatio;
    
    // Radii are half of the total width/height of the effect area
    const radiusX = imgWidth * mustacheWidthRatio / 2;
    const radiusY = imgHeight * mustacheHeightRatio / 2;

    // If radii are zero or negative, or strength is zero, no distortion occurs.
    // Strength = 0 means scale_factor = r_norm^0 = 1 (for r_norm != 0), resulting in no change.
    // r_norm = 0 (center) is handled separately.
    if (radiusX <= 0 || radiusY <= 0) {
        // No distortion area, return original image on canvas
        ctx.putImageData(imageData, 0, 0);
        return canvas;
    }

    // Bilinear interpolation helper function
    function getPixelBilinear(srcImgData, x, y) {
        const srcData = srcImgData.data;
        const width = srcImgData.width;
        const height = srcImgData.height;

        // Clamp coordinates to be within the source image bounds
        const s_x = Math.max(0, Math.min(x, width - 1));
        const s_y = Math.max(0, Math.min(y, height - 1));

        const x1 = Math.floor(s_x);
        const y1 = Math.floor(s_y);
        
        // Ensure x2, y2 are also within bounds
        const x2 = Math.min(x1 + 1, width - 1);
        const y2 = Math.min(y1 + 1, height - 1);

        const fx = s_x - x1; // Fractional part for x
        const fy = s_y - y1; // Fractional part for y
        const fx1 = 1 - fx;
        const fy1 = 1 - fy;

        const idx11 = (y1 * width + x1) * 4;
        const idx12 = (y2 * width + x1) * 4; // Pixel at (x1, y2)
        const idx21 = (y1 * width + x2) * 4; // Pixel at (x2, y1)
        const idx22 = (y2 * width + x2) * 4; // Pixel at (x2, y2)

        const r = srcData[idx11] * fx1 * fy1 + srcData[idx21] * fx * fy1 + srcData[idx12] * fx1 * fy + srcData[idx22] * fx * fy;
        const g = srcData[idx11+1] * fx1 * fy1 + srcData[idx21+1] * fx * fy1 + srcData[idx12+1] * fx1 * fy + srcData[idx22+1] * fx * fy;
        const b = srcData[idx11+2] * fx1 * fy1 + srcData[idx21+2] * fx * fy1 + srcData[idx12+2] * fx1 * fy + srcData[idx22+2] * fx * fy;
        const a = srcData[idx11+3] * fx1 * fy1 + srcData[idx21+3] * fx * fy1 + srcData[idx12+3] * fx1 * fy + srcData[idx22+3] * fx * fy;
        
        return [r, g, b, a];
    }

    for (let y = 0; y < imgHeight; y++) {
        for (let x = 0; x < imgWidth; x++) {
            const dx = x - centerX;
            const dy = y - centerY;

            // Normalized coordinates relative to ellipse radii
            // (dx / radiusX)^2 + (dy / radiusY)^2 = 1 is the ellipse equation
            const nx = dx / radiusX;
            const ny = dy / radiusY;

            const r_squared_norm = nx * nx + ny * ny;
            
            let sourceX = x;
            let sourceY = y;

            if (r_squared_norm < 1) { // Pixel is inside the distortion ellipse
                const r_norm = Math.sqrt(r_squared_norm); // Normalized distance from center (0 to <1)

                if (r_norm === 0 && strength !== 0) { // At the very center of the ellipse
                    // If strength is positive (bulge), center maps to center.
                    // If strength is negative (pinch), center also maps to center.
                    // (0^strength is 0 for strength>0, Math.pow(0,0)=1, Math.pow(0,negative)=Infinity)
                    // To ensure center pixel always maps to center for simplicity unless strength is exactly 0:
                    sourceX = centerX;
                    sourceY = centerY;
                } else if (strength === 0) { // No distortion if strength is 0
                    const scale_factor = 1; // Correctly declare scale_factor
                    sourceX = centerX + dx * scale_factor; // Effectively x
                    sourceY = centerY + dy * scale_factor; // Effectively y
                }
                 else { // r_norm > 0 and strength !== 0
                    // Apply distortion
                    // strength > 0 for bulge (samples closer to center, scale_factor < 1 for r_norm < 1)
                    // strength < 0 for pinch (samples further from center, scale_factor > 1 for r_norm < 1)
                    const scale_factor = Math.pow(r_norm, strength);
                    
                    sourceX = centerX + dx * scale_factor;
                    sourceY = centerY + dy * scale_factor;
                }
            }
            
            const pixelDestIndex = (y * imgWidth + x) * 4;
            const [r, g, b, a] = getPixelBilinear(imageData, sourceX, sourceY);

            dstData[pixelDestIndex]     = r;
            dstData[pixelDestIndex + 1] = g;
            dstData[pixelDestIndex + 2] = b;
            dstData[pixelDestIndex + 3] = a;
        }
    }

    ctx.putImageData(dstImageData, 0, 0);
    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 Mustache Distortion Filter is a creative image processing tool that allows users to apply a mustache-style distortion effect to their images. By defining the center position, size, and strength of the distortion, users can create unique artistic effects. This tool can be useful for enhancing personal photos, creating fun social media content, or experimenting with new digital art styles.

Leave a Reply

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