Please bookmark this page to avoid losing your image tool!

Image Twirl 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, angle = 90, centerX = 0.5, centerY = 0.5, radius = 0.5) {
    const width = originalImg.width;
    const height = originalImg.height;

    const resultCanvas = document.createElement('canvas');
    resultCanvas.width = width;
    resultCanvas.height = height;
    const resultCtx = resultCanvas.getContext('2d');

    if (width === 0 || height === 0) {
        return resultCanvas; // Return empty canvas if image is 0x0
    }

    // Parse parameters, providing defaults if parsing fails or invalid values are given
    let fAngle = parseFloat(angle);
    if (isNaN(fAngle)) fAngle = 90;

    let fCenterX = parseFloat(centerX);
    if (isNaN(fCenterX)) fCenterX = 0.5;

    let fCenterY = parseFloat(centerY);
    if (isNaN(fCenterY)) fCenterY = 0.5;

    let fRadius = parseFloat(radius);
    if (isNaN(fRadius) || fRadius < 0) fRadius = 0.5; // Radius cannot be negative

    const radAngle = fAngle * Math.PI / 180;
    const iRadius = fRadius * Math.min(width, height); // Effective radius in pixels

    // If no actual twirl will happen (zero angle or zero radius), draw original image and return
    if (radAngle === 0 || iRadius <= 0) {
        resultCtx.drawImage(originalImg, 0, 0);
        return resultCanvas;
    }

    // Draw original image to a temporary canvas to get 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);
    
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, width, height);
    } catch (e) {
        // This can happen due to tainted canvas if image is cross-origin
        console.error("Error getting image data for twirl filter: ", e);
        resultCtx.drawImage(originalImg, 0, 0); // Draw original image as fallback
        // Optionally, draw an error message on the canvas
        resultCtx.font = "14px Arial";
        resultCtx.fillStyle = "red";
        resultCtx.textAlign = "center";
        resultCtx.fillText("Error: Could not process cross-origin image.", width / 2, height / 2);
        return resultCanvas;
    }
    const pixels = imageData.data;
    
    const newImageData = resultCtx.createImageData(width, height);
    const newPixels = newImageData.data;

    const actualCenterX = fCenterX * width;
    const actualCenterY = fCenterY * height;

    // Bilinear interpolation helper function
    // Gets the color of a floating-point coordinate (x_src, y_src)
    const getPixelBilinear = (x_src, y_src) => {
        const x1 = Math.floor(x_src);
        const y1 = Math.floor(y_src);

        const fx = x_src - x1; // Fractional part of x
        const fy = y_src - y1; // Fractional part of y
        const invFx = 1 - fx;
        const invFy = 1 - fy;

        const resultColor = [0, 0, 0, 0]; // R, G, B, A

        for (let ch = 0; ch < 4; ++ch) { // Iterate over R, G, B, A channels
            // Helper to get a component value from original pixel data, clamping coordinates
            const getClampedComponent = (currX, currY) => {
                const sX = Math.max(0, Math.min(width - 1, currX));
                const sY = Math.max(0, Math.min(height - 1, currY));
                return pixels[(sY * width + sX) * 4 + ch];
            };

            const C00 = getClampedComponent(x1, y1);         // Color component at (x1, y1)
            const C10 = getClampedComponent(x1 + 1, y1);     // Color component at (x1+1, y1)
            const C01 = getClampedComponent(x1, y1 + 1);     // Color component at (x1, y1+1)
            const C11 = getClampedComponent(x1 + 1, y1 + 1); // Color component at (x1+1, y1+1)

            // Interpolate along x-axis for y1 and y2
            const R_x_y1 = C00 * invFx + C10 * fx;
            const R_x_y2 = C01 * invFx + C11 * fx;
            
            // Interpolate along y-axis
            resultColor[ch] = R_x_y1 * invFy + R_x_y2 * fy;
        }
        return resultColor;
    };

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const dx = x - actualCenterX; // Distance from center x
            const dy = y - actualCenterY; // Distance from center y
            const distance = Math.sqrt(dx * dx + dy * dy);
            
            let srcX = x;
            let srcY = y;

            if (distance < iRadius) { // Only twirl pixels within the specified radius
                // Standard twirl: rotation angle increases with distance from center, up to radAngle at iRadius
                const rotation = radAngle * (distance / iRadius);
                
                // To find the source pixel (srcX, srcY) for the destination pixel (x,y),
                // we effectively rotate (dx, dy) by -rotation.
                // src_offset_X = dx * cos(-rotation) - dy * sin(-rotation)
                //              = dx * cos(rotation) + dy * sin(rotation)
                // src_offset_Y = dx * sin(-rotation) + dy * cos(-rotation)
                //              = -dx * sin(rotation) + dy * cos(rotation)
                const cosRot = Math.cos(rotation);
                const sinRot = Math.sin(rotation);

                const rotatedOffsetX = dx * cosRot + dy * sinRot;
                const rotatedOffsetY = -dx * sinRot + dy * cosRot;
                
                srcX = actualCenterX + rotatedOffsetX;
                srcY = actualCenterY + rotatedOffsetY;
            }

            const [r_val, g_val, b_val, a_val] = getPixelBilinear(srcX, srcY);
            
            const destIdx = (y * width + x) * 4;
            newPixels[destIdx]     = r_val;
            newPixels[destIdx + 1] = g_val;
            newPixels[destIdx + 2] = b_val;
            newPixels[destIdx + 3] = a_val;
        }
    }
    
    resultCtx.putImageData(newImageData, 0, 0);
    return resultCanvas;
}

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 Twirl Filter Application allows users to apply a twirl effect to images, creating a visual distortion that spirals around a designated center point. Users can customize the angle and radius of the twirl effect, making it suitable for artistic edits or creative designs. This tool can be used in various scenarios, including enhancing graphic designs, creating engaging social media posts, or experimenting with image manipulation techniques for artistic projects.

Leave a Reply

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