Please bookmark this page to avoid losing your image tool!

Image Pincushion 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, pincushionAmount = 0.2) {
    // pincushionAmount: Strength of the pincushion effect.
    //                   0 means no distortion.
    //                   Positive values (e.g., 0.2 to 0.5) create a pincushion effect.
    //                   Higher values create a stronger effect.
    //                   Very high values might lead to extreme warping.

    const width = originalImg.width;
    const height = originalImg.height;

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    // Note: { willReadFrequently: true } is a performance hint for contexts that are frequently read from (e.g. getImageData)
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    if (width === 0 || height === 0) {
        // Handle zero-size image.
        return canvas;
    }
    
    // If pincushionAmount is zero, no distortion is applied.
    // Draw the original image and return early.
    if (pincushionAmount === 0) {
        ctx.drawImage(originalImg, 0, 0, width, height);
        return canvas;
    }

    // Create a temporary canvas to draw the original image and get its pixel data.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
    tempCtx.drawImage(originalImg, 0, 0, width, height);
    const sourceImageData = tempCtx.getImageData(0, 0, width, height);
    const sourceData = sourceImageData.data; // Pixel data array (Uint8ClampedArray)
    
    const destImageData = ctx.createImageData(width, height);
    const destData = destImageData.data;

    // Bilinear interpolation helper function
    // Takes pixel data array, image dimensions, and floating point coordinates (x, y)
    // Returns an object {r, g, b, a} representing the interpolated pixel color.
    function getBilinearPixel(pixelArr, imgWidth, imgHeight, x, y) {
        const x0 = Math.floor(x);
        const y0 = Math.floor(y);
        const x1 = x0 + 1;
        const y1 = y0 + 1;

        const fx = x - x0; // Fractional part of x
        const fy = y - y0; // Fractional part of y
        const invFx = 1 - fx;
        const invFy = 1 - fy;

        // Helper to get pixel component at integer coordinates, clamping to image bounds
        const getPixelComponent = (ix, iy, component) => {
            const cx = Math.max(0, Math.min(ix, imgWidth - 1));
            const cy = Math.max(0, Math.min(iy, imgHeight - 1));
            return pixelArr[(cy * imgWidth + cx) * 4 + component];
        };
        
        let r = 0, g = 0, b = 0, a = 0;
        for (let i = 0; i < 4; i++) { // Iterate over R, G, B, A components
            const p00 = getPixelComponent(x0, y0, i);
            const p10 = getPixelComponent(x1, y0, i);
            const p01 = getPixelComponent(x0, y1, i);
            const p11 = getPixelComponent(x1, y1, i);

            const val = p00 * invFx * invFy + 
                        p10 * fx    * invFy + 
                        p01 * invFx * fy + 
                        p11 * fx    * fy;
            if (i === 0) r = val;
            else if (i === 1) g = val;
            else if (i === 2) b = val;
            else if (i === 3) a = val;
        }
        
        return { r, g, b, a };
    }

    const aspect = width / height;
    
    // For pincushion distortion, the coefficient k (in formulas like r_src = r_dst * (1 + k * r_dst^2))
    // should be negative. We take positive pincushionAmount as input for user-friendliness.
    const k_coefficient = -pincushionAmount;

    for (let dstY = 0; dstY < height; dstY++) {
        for (let dstX = 0; dstX < width; dstX++) {
            // Normalize destination pixel coordinates to range [-0.5, 0.5] (approximately)
            // Using (pixel_coord + 0.5) / dimension maps pixel centers correctly.
            const normDstX = ((dstX + 0.5) / width) - 0.5;
            const normDstY = ((dstY + 0.5) / height) - 0.5;

            // Correct for aspect ratio to make distortion radially symmetric
            // Y-axis is reference; X-axis is scaled by aspect ratio.
            const correctedNormDstX_for_r = normDstX * aspect;
            const correctedNormDstY_for_r = normDstY;

            // Calculate squared radius from center in aspect-corrected normalized space
            const r2 = correctedNormDstX_for_r * correctedNormDstX_for_r + 
                       correctedNormDstY_for_r * correctedNormDstY_for_r;
            
            // Apply distortion formula: src = dst * (1 + k * r_dst^2)
            const distortionFactor = 1.0 + k_coefficient * r2;

            // Calculate source coordinates in aspect-corrected normalized space
            const correctedNormSrcX = correctedNormDstX_for_r * distortionFactor;
            const correctedNormSrcY = correctedNormDstY_for_r * distortionFactor;

            // Revert aspect ratio correction to get normalized UV source coordinates [0,1]
            const normSrcX = (correctedNormSrcX / aspect) + 0.5;
            const normSrcY = correctedNormSrcY + 0.5;

            // Convert normalized UV source coordinates back to pixel coordinates
            // Subtract 0.5 to map from pixel grid centers.
            const srcX = normSrcX * width - 0.5;
            const srcY = normSrcY * height - 0.5;
            
            const destIdx = (dstY * width + dstX) * 4;
            
            // Sample color from source image using bilinear interpolation
            // The getBilinearPixel function handles boundary clamping for srcX, srcY implicitly.
            const { r, g, b, a } = getBilinearPixel(sourceData, width, height, srcX, srcY);
            
            destData[destIdx]     = r;
            destData[destIdx + 1] = g;
            destData[destIdx + 2] = b;
            destData[destIdx + 3] = a;
        }
    }
    
    ctx.putImageData(destImageData, 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 Pincushion Distortion Filter is a web tool that applies a pincushion distortion effect to images. By adjusting the strength of the pincushion effect, users can create a visual effect that warps images inward, which can be used for artistic purposes, creative photography enhancements, or to simulate lens distortion for graphic design. This tool is useful for photographers, designers, and digital artists looking to stylize their images or create unique visual presentations.

Leave a Reply

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