Please bookmark this page to avoid losing your image tool!

Photo Funny Filter Application

(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.
function processImage(originalImg, filterType = "pixelate", param1 = 10, param2 = 0.1, param3 = 0.4, param4 = 0.15, param5 = -0.1, param6 = 90, param7 = 0.8, param8 = 0.5, param9 = 0.75) {
    // Parameter mapping based on filterType:
    // param1: For "pixelate"       -> blockSize (integer, e.g., 10)
    // param2: For "googlyEyes"     -> eyeScale (number, e.g., 0.1 relative to image width)
    // param3: For "googlyEyes"     -> pupilScale (number, e.g., 0.4 relative to sclera radius)
    // param4: For "googlyEyes"     -> eyeDistFactor (number, e.g., 0.15, each eye's distance from image center X, relative to image width)
    // param5: For "googlyEyes"     -> eyeYOffsetFactor (number, e.g., -0.1, vertical offset from image center Y, relative to image height)
    // param6: For "swirl"          -> swirlAngle (number, degrees, e.g., 90)
    // param7: For "swirl"          -> swirlRadiusFactor (number, e.g., 0.8, active radius as a factor of min(width,height)/2)
    // param8: For "bulge" / "pinch"-> bulgePinchFactor (number, e.g., 0.5 for bulge; 1.0 for no effect; >1.0 for pinch)
    // param9: For "bulge" / "pinch"-> bulgePinchRadiusFactor (number, e.g., 0.75, active radius as a factor of min(width,height)/2)

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimize for frequent getImageData/putImageData

    const w = originalImg.width;
    const h = originalImg.height;

    if (w === 0 || h === 0) {
        console.error("Image has zero width or height. Ensure it's loaded and dimensions are valid.");
        canvas.width = 1; // Return a minimal canvas to indicate an issue
        canvas.height = 1;
        ctx.fillStyle = "red"; // Red pixel
        ctx.fillRect(0,0,1,1);
        return canvas;
    }

    canvas.width = w;
    canvas.height = h;
    ctx.drawImage(originalImg, 0, 0, w, h); // Draw the original image first

    // Helper function for bilinear interpolation.
    // Samples a color from 'pixelDataArray' (which is imageData.data) at floating point (x,y) coordinates.
    function getPixelBilinear(pixelDataArray, imgWidth, imgHeight, x, y) {
        const x_floor = Math.floor(x);
        const y_floor = Math.floor(y);

        // Clamp coordinates to be within image bounds for pixel sampling
        const x1 = Math.max(0, Math.min(imgWidth - 1, x_floor));    // Top-left X
        const y1 = Math.max(0, Math.min(imgHeight - 1, y_floor));   // Top-left Y
        const x2 = Math.max(0, Math.min(imgWidth - 1, x_floor + 1));// Top-right X / Bottom-right X
        const y2 = Math.max(0, Math.min(imgHeight - 1, y_floor + 1));// Bottom-left Y / Bottom-right Y

        const idxTL = (y1 * imgWidth + x1) * 4; // Index for Top-Left pixel
        const idxTR = (y1 * imgWidth + x2) * 4; // Index for Top-Right pixel
        const idxBL = (y2 * imgWidth + x1) * 4; // Index for Bottom-Left pixel
        const idxBR = (y2 * imgWidth + x2) * 4; // Index for Bottom-Right pixel

        const fx = x - x_floor; // Fractional part of x
        const fy = y - y_floor; // Fractional part of y
        const fx1 = 1 - fx;     // 1 - fractional part of x
        const fy1 = 1 - fy;     // 1 - fractional part of y

        // Calculate weights for each of the 4 neighboring pixels
        const wTL = fx1 * fy1;
        const wTR = fx  * fy1;
        const wBL = fx1 * fy;
        const wBR = fx  * fy;

        // Interpolate R, G, B, A channels
        const r = pixelDataArray[idxTL] * wTL + pixelDataArray[idxTR] * wTR + pixelDataArray[idxBL] * wBL + pixelDataArray[idxBR] * wBR;
        const g = pixelDataArray[idxTL+1] * wTL + pixelDataArray[idxTR+1] * wTR + pixelDataArray[idxBL+1] * wBL + pixelDataArray[idxBR+1] * wBR;
        const b = pixelDataArray[idxTL+2] * wTL + pixelDataArray[idxTR+2] * wTR + pixelDataArray[idxBL+2] * wBL + pixelDataArray[idxBR+2] * wBR;
        const a = pixelDataArray[idxTL+3] * wTL + pixelDataArray[idxTR+3] * wTR + pixelDataArray[idxBL+3] * wBL + pixelDataArray[idxBR+3] * wBR;
        
        return [r, g, b, a];
    }

    switch (filterType) {
        case "pixelate": {
            const blockSize = Math.max(1, Math.floor(param1));
            // For pixelation, we sample the original image (already on canvas)
            const originalImageData = ctx.getImageData(0, 0, w, h);
            const origData = originalImageData.data;
            
            for (let yBlock = 0; yBlock < h; yBlock += blockSize) {
                for (let xBlock = 0; xBlock < w; xBlock += blockSize) {
                    let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
                    let count = 0;
                    // Calculate average color for the current block
                    for (let yPx = yBlock; yPx < Math.min(yBlock + blockSize, h); yPx++) {
                        for (let xPx = xBlock; xPx < Math.min(xBlock + blockSize, w); xPx++) {
                            const idx = (yPx * w + xPx) * 4;
                            sumR += origData[idx];
                            sumG += origData[idx + 1];
                            sumB += origData[idx + 2];
                            sumA += origData[idx + 3];
                            count++;
                        }
                    }
                    if (count > 0) {
                        ctx.fillStyle = `rgba(${sumR/count}, ${sumG/count}, ${sumB/count}, ${sumA/count/255})`;
                        // Fill the block on the canvas. Handle edge blocks carefully.
                        ctx.fillRect(xBlock, yBlock, Math.min(blockSize, w - xBlock), Math.min(blockSize, h - yBlock));
                    }
                }
            }
            break;
        }
        case "googlyEyes": {
            const eyeScale = param2;
            const pupilScale = param3;
            const eyeDistFactor = param4; // Distance for *each* eye from center X
            const eyeYOffsetFactor = param5;

            const centerX = w / 2;
            const centerY = h / 2;

            const scleraRadius = Math.max(1, (w * eyeScale) / 2);
            const pupilRadius = Math.max(1, scleraRadius * pupilScale);

            // Calculate positions for left and right eyes
            const finalEyeLX = centerX - w * eyeDistFactor;
            const finalEyeRX = centerX + w * eyeDistFactor;
            const finalEyeY = centerY + h * eyeYOffsetFactor;

            const maxPupilOffset = Math.max(0, scleraRadius - pupilRadius); // Max distance pupil can move within sclera

            function drawEye(eyeCenterX, eyeCenterY, scleraR, pupilR) {
                // Random offset for pupil to make it "googly"
                const pupilOffsetX = (Math.random() * 2 - 1) * maxPupilOffset * 0.7; // 0.7 limits offset slightly
                const pupilOffsetY = (Math.random() * 2 - 1) * maxPupilOffset * 0.7;

                // Draw sclera (white part)
                ctx.fillStyle = 'white';
                ctx.beginPath();
                ctx.arc(eyeCenterX, eyeCenterY, scleraR, 0, Math.PI * 2);
                ctx.fill();
                ctx.strokeStyle = 'black'; // Outline for sclera
                ctx.lineWidth = Math.max(1, scleraR * 0.05); // Line width relative to sclera size
                ctx.stroke();

                // Draw pupil (black part)
                ctx.fillStyle = 'black';
                ctx.beginPath();
                ctx.arc(eyeCenterX + pupilOffsetX, eyeCenterY + pupilOffsetY, pupilR, 0, Math.PI * 2);
                ctx.fill();
            }
            
            // Draw eyes on top of the existing image on canvas
            drawEye(finalEyeLX, finalEyeY, scleraRadius, pupilRadius);
            drawEye(finalEyeRX, finalEyeY, scleraRadius, pupilRadius);
            break;
        }
        case "swirl":
        case "bulge": { // Swirl and Bulge/Pinch filters involve per-pixel remapping
            const srcImageData = ctx.getImageData(0, 0, w, h); // Get current canvas pixels to sample from
            const srcData = srcImageData.data;
            const destImageData = ctx.createImageData(w, h);   // Create a new buffer for destination pixels
            const destData = destImageData.data;

            const centerX = w / 2;
            const centerY = h / 2;

            for (let y = 0; y < h; y++) {
                for (let x = 0; x < w; x++) {
                    const dx = x - centerX; // Distance from center X
                    const dy = y - centerY; // Distance from center Y
                    const distance = Math.sqrt(dx * dx + dy * dy); // Overall distance from center
                    
                    let srcX = x; // Source X to sample from (defaults to current X)
                    let srcY = y; // Source Y to sample from (defaults to current Y)

                    if (filterType === "swirl") {
                        const swirlAngleDeg = param6;
                        const swirlRadiusFactor = param7;
                        const swirlAngleRad = swirlAngleDeg * Math.PI / 180; // Convert degrees to radians
                        const effectRadius = (Math.min(w, h) / 2) * swirlRadiusFactor;

                        if (distance < effectRadius && effectRadius > 0) { // Apply only within effect radius
                            const angle = Math.atan2(dy, dx); // Original angle of pixel relative to center
                            // Swirl strength decreases linearly from center to edge of effectRadius
                            const swirlStrength = 1 - (distance / effectRadius); 
                            const newAngle = angle + swirlAngleRad * swirlStrength; // New angle after swirl
                            
                            srcX = centerX + distance * Math.cos(newAngle);
                            srcY = centerY + distance * Math.sin(newAngle);
                        }
                    } else { // "bulge" (or "pinch")
                        const bulgePinchFactor = param8; // <1.0 for bulge, 1.0 for no effect, >1.0 for pinch
                        const bulgePinchRadiusFactor = param9;
                        const effectRadius = (Math.min(w, h) / 2) * bulgePinchRadiusFactor;

                        if (distance < effectRadius && effectRadius > 0 && bulgePinchFactor > 0.001) { // Factor must be positive
                            const normDist = distance / effectRadius; // Normalized distance [0,1)
                            
                            let transformedNormDist;
                            if (Math.abs(bulgePinchFactor - 1.0) < 0.001) { // Effectively no change if factor is 1.0
                                transformedNormDist = normDist;
                            } else {
                                // Exponent adjusts sampling radius:
                                // bulgePinchFactor 0.5 (bulge) => exponent 2 => samples from smaller radius (magnifies center)
                                // bulgePinchFactor 2.0 (pinch) => exponent 0.5 => samples from larger radius (minifies center)
                                const exponent = 1.0 / bulgePinchFactor;
                                transformedNormDist = Math.pow(normDist, exponent);
                            }
                            
                            const newDist = transformedNormDist * effectRadius;
                            const angle = Math.atan2(dy, dx);
                            
                            if (distance === 0) { // Center pixel remains center
                                srcX = centerX;
                                srcY = centerY;
                            } else {
                                srcX = centerX + newDist * Math.cos(angle);
                                srcY = centerY + newDist * Math.sin(angle);
                            }
                        }
                    }
                    
                    // Sample color from source coordinates (srcX, srcY) using bilinear interpolation
                    const C = getPixelBilinear(srcData, w, h, srcX, srcY);
                    const destIdx = (y * w + x) * 4; // Index in destination buffer
                    destData[destIdx]     = C[0]; // R
                    destData[destIdx + 1] = C[1]; // G
                    destData[destIdx + 2] = C[2]; // B
                    destData[destIdx + 3] = C[3]; // A
                }
            }
            ctx.putImageData(destImageData, 0, 0); // Apply the modified pixels to the canvas
            break;
        }
        default:
            console.warn(`Unknown filterType: "${filterType}". No filter applied.`);
            // Original image is already on the canvas as per the initial drawImage.
            break;
    }

    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 Photo Funny Filter Application allows users to apply various fun filters to their images, transforming ordinary photos into amusing ones. With options such as pixelation, googly eyes, swirling effects, and bulging or pinching distortions, this tool is perfect for personalizing images for social media, creating playful visuals for events, or adding a humorous touch to any digital content. Whether you’re looking to create a quirky avatar, enhance party photos, or simply experiment with creative edits, this application provides an easy and engaging way to modify your images.

Leave a Reply

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