Please bookmark this page to avoid losing your image tool!

Image Hurricane View Filter Effect Tool

(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, eyeRadiusRatio = 0.3, swirlMaxAngle = 0.5, outerFadeColorStr = "30,30,40", outerFadeStartRatio = 0.6) {
    // Helper to parse "r,g,b" color string into an object {r, g, b}
    function parseColor(colorStr) {
        const parts = colorStr.split(',').map(s => parseInt(s.trim(), 10));
        return {
            r: Number.isFinite(parts[0]) ? parts[0] : 0,
            g: Number.isFinite(parts[1]) ? parts[1] : 0,
            b: Number.isFinite(parts[2]) ? parts[2] : 0
        };
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Prefer natural dimensions of the image, fallback to width/height
    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;
    
    canvas.width = width;
    canvas.height = height;

    // If image dimensions are invalid, return an empty (but correctly sized) canvas
    if (width === 0 || height === 0) {
        return canvas;
    }

    // Draw the original image to the canvas
    try {
        ctx.drawImage(originalImg, 0, 0, width, height);
    } catch (e) {
        // Handle error if the image cannot be drawn (e.g., invalid image source)
        console.error("Error drawing image to canvas: " + e.message);
        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, width, height);
        ctx.font = "16px Arial";
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("Error: Could not draw the input image.", width / 2, height / 2);
        return canvas;
    }
    
    let originalImageData;
    try {
        // Attempt to get pixel data; this can fail for cross-origin images without CORS
        originalImageData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        console.error("Error getting ImageData (cross-origin issues?): " + e.message);
        // Display an error message on the canvas regarding cross-origin restrictions
        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, width, height);
        ctx.font = "14px Arial"; // Slightly smaller font for potentially longer messages
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        const lineHeight = 18;
        ctx.fillText("Error: Cannot process image pixels.", width / 2, height / 2 - lineHeight);
        ctx.fillText("Image might be from a different origin", width / 2, height / 2);
        ctx.fillText("and lack appropriate CORS headers.", width / 2, height / 2 + lineHeight);
        return canvas;
    }
    
    const processedImageData = ctx.createImageData(width, height);
    const data = originalImageData.data;          // Source pixel data
    const processedData = processedImageData.data; // Target pixel data

    const centerX = width / 2;
    const centerY = height / 2;
    
    // Reference radius for effects: radius of the largest circle inscribed in the image
    const effectReferenceRadius = Math.min(centerX, centerY);

    // Calculate actual radius of the clear "eye"
    const actualEyeRadius = effectReferenceRadius * Math.max(0, eyeRadiusRatio);
    
    // Ensure outerFadeStartRatio is at least eyeRadiusRatio
    const validatedOuterFadeStartRatio = Math.max(outerFadeStartRatio, eyeRadiusRatio);
    let actualOuterFadeStartRadius = effectReferenceRadius * validatedOuterFadeStartRatio;
    // Further ensure that the pixel radius for starting fade is not less than the eye radius
    actualOuterFadeStartRadius = Math.max(actualOuterFadeStartRadius, actualEyeRadius);

    const parsedFadeColor = parseColor(outerFadeColorStr);

    // Iterate over each pixel
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const dx = x - centerX; // Distance from center X
            const dy = y - centerY; // Distance from center Y
            const dist = Math.sqrt(dx * dx + dy * dy); // Pixel's distance from center
            
            const currentPixelIndex = (y * width + x) * 4; // Index for R, G, B, A values

            if (dist <= actualEyeRadius) {
                // Inside the "eye": copy original pixel
                processedData[currentPixelIndex]     = data[currentPixelIndex];
                processedData[currentPixelIndex + 1] = data[currentPixelIndex + 1];
                processedData[currentPixelIndex + 2] = data[currentPixelIndex + 2];
                processedData[currentPixelIndex + 3] = data[currentPixelIndex + 3];
            } else {
                // Outside the "eye": apply swirl and fade effects
                const angle = Math.atan2(dy, dx); // Angle of the pixel relative to center
                
                let swirlZoneProgress;
                const swirlEffectRange = effectReferenceRadius - actualEyeRadius;
                if (swirlEffectRange <= 0) { // Eye is at or beyond reference radius
                    swirlZoneProgress = (dist > actualEyeRadius) ? 1 : 0; // Max swirl if truly outside eye
                } else {
                    swirlZoneProgress = (dist - actualEyeRadius) / swirlEffectRange;
                }
                swirlZoneProgress = Math.min(1, Math.max(0, swirlZoneProgress)); // Clamp progress to [0,1]
                const angleOffset = swirlMaxAngle * swirlZoneProgress; // Calculate swirl angle offset

                // Calculate source pixel coordinates after applying swirl
                let sampleX = Math.round(centerX + dist * Math.cos(angle - angleOffset));
                let sampleY = Math.round(centerY + dist * Math.sin(angle - angleOffset));

                // Clamp sample coordinates to be within image bounds
                sampleX = Math.max(0, Math.min(width - 1, sampleX));
                sampleY = Math.max(0, Math.min(height - 1, sampleY));
                
                const sourcePixelIndex = (sampleY * width + sampleX) * 4;

                // Get color from swirled position
                let r = data[sourcePixelIndex];
                let g = data[sourcePixelIndex + 1];
                let b = data[sourcePixelIndex + 2];
                let a = data[sourcePixelIndex + 3];
                
                // Apply fade effect if pixel is beyond the fade start radius
                if (dist > actualOuterFadeStartRadius) {
                    let fadeZoneProgress;
                    const fadeEffectRange = effectReferenceRadius - actualOuterFadeStartRadius;
                    if (fadeEffectRange <= 0) { // Fade starts at or beyond reference radius
                        fadeZoneProgress = (dist > actualOuterFadeStartRadius) ? 1 : 0; // Max fade if truly outside start
                    } else {
                        fadeZoneProgress = (dist - actualOuterFadeStartRadius) / fadeEffectRange;
                    }
                    fadeZoneProgress = Math.min(1, Math.max(0, fadeZoneProgress)); // Clamp progress to [0,1]
                    
                    // Interpolate towards fade color
                    r = Math.round(r * (1 - fadeZoneProgress) + parsedFadeColor.r * fadeZoneProgress);
                    g = Math.round(g * (1 - fadeZoneProgress) + parsedFadeColor.g * fadeZoneProgress);
                    b = Math.round(b * (1 - fadeZoneProgress) + parsedFadeColor.b * fadeZoneProgress);
                    // Optionally, fade alpha:
                    // a = Math.round(a * (1 - fadeZoneProgress) + 255 * fadeZoneProgress); // Fade to opaque
                }

                // Set the processed pixel data
                processedData[currentPixelIndex]     = r;
                processedData[currentPixelIndex + 1] = g;
                processedData[currentPixelIndex + 2] = b;
                processedData[currentPixelIndex + 3] = a;
            }
        }
    }

    // Put the processed pixel data back onto the canvas
    ctx.putImageData(processedImageData, 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 Hurricane View Filter Effect Tool allows users to apply a unique swirl and fade effect to images. This tool is ideal for enhancing photographs or graphics with a creative twist, making them visually striking and dynamic. It provides customizable parameters such as the size of the ‘eye’ effect, the maximum angle of swirl, and the color and distance at which fading begins, allowing for a tailored artistic expression. Use this tool for personal projects, social media graphics, or any occasion where you want to add a captivating visual effect to your images.

Leave a Reply

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