Please bookmark this page to avoid losing your image tool!

Photo Halloween 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.
function processImage(originalImg, orangeIntensity = 0.5, desaturation = 0.2, brightness = -30, contrast = 25, vignetteStrength = 0.6, vignetteSoftness = 0.7) {
    // Ensure parameters are numbers
    orangeIntensity = Number(orangeIntensity);
    desaturation = Number(desaturation);
    brightness = Number(brightness);
    contrast = Number(contrast);
    vignetteStrength = Number(vignetteStrength);
    vignetteSoftness = Number(vignetteSoftness);

    const canvas = document.createElement('canvas');
    // Use naturalWidth/Height for accuracy, fallback to width/height if natural are 0
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        // For an empty image, return an empty canvas.
        return canvas;
    }

    const ctx = canvas.getContext('2d');
    if (!ctx) {
        // This should ideally not happen in a standard browser environment
        console.error("Could not get 2D context");
        // Fallback: return an empty canvas or throw an error
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = canvas.width;
        emptyCanvas.height = canvas.height;
        return emptyCanvas;
    }

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        // This can happen due to CORS issues if the image is tainted (cross-origin).
        console.error("Could not get ImageData due to security policies or other error:", e);
        // In this case, pixel manipulation is not possible.
        // We return the canvas with the original image drawn. 
        // A more advanced tool might notify the user or attempt alternative methods.
        return canvas;
    }
    
    const pixels = imageData.data;
    
    // Contrast factor calculation:
    // contrast is typically -100 to 100. The formula expects a range like -255 to 255.
    // We use the user's contrast value directly; moderate values are fine.
    const contrastFactor = (259 * (contrast + 255)) / (255 * (259 - contrast));
    
    // Target color for orange tint
    const tintR_target = 255; // Strong red component
    const tintG_target = 130; // Moderate green for orangey feel
    const tintB_target = 30;  // Low blue to emphasize warmth

    for (let i = 0; i < pixels.length; i += 4) {
        let r = pixels[i];
        let g = pixels[i + 1];
        let b = pixels[i + 2];

        // 1. Apply Brightness
        r += brightness;
        g += brightness;
        b += brightness;

        // 2. Apply Contrast
        // This formula works by adjusting values relative to a midpoint (128)
        r = contrastFactor * (r - 128) + 128;
        g = contrastFactor * (g - 128) + 128;
        b = contrastFactor * (b - 128) + 128;
        
        // Clamp values after brightness/contrast, before color mixing
        r = Math.max(0, Math.min(255, r));
        g = Math.max(0, Math.min(255, g));
        b = Math.max(0, Math.min(255, b));

        // 3. Apply Desaturation
        if (desaturation > 0 && desaturation <= 1) {
            const gray = r * 0.299 + g * 0.587 + b * 0.114; // Standard luminance calculation
            r = r * (1 - desaturation) + gray * desaturation;
            g = g * (1 - desaturation) + gray * desaturation;
            b = b * (1 - desaturation) + gray * desaturation;
        }

        // 4. Apply Orange Tint (Halloween style)
        // This blends the current pixel color with the target tint color
        if (orangeIntensity > 0 && orangeIntensity <= 1) {
            r = r * (1 - orangeIntensity) + tintR_target * orangeIntensity;
            g = g * (1 - orangeIntensity) + tintG_target * orangeIntensity;
            b = b * (1 - orangeIntensity) + tintB_target * orangeIntensity;
        }
        
        // Final assignment: clamp values to [0, 255] and round to integers
        pixels[i]     = Math.round(Math.max(0, Math.min(255, r)));
        pixels[i + 1] = Math.round(Math.max(0, Math.min(255, g)));
        pixels[i + 2] = Math.round(Math.max(0, Math.min(255, b)));
        // Alpha channel (pixels[i+3]) is preserved
    }

    ctx.putImageData(imageData, 0, 0);

    // 5. Apply Vignette effect
    if (vignetteStrength > 0 && vignetteStrength <= 1) {
        const centerX = canvas.width / 2;
        const centerY = canvas.height / 2;
        // rOuter is the radius to the furthest corner, ensuring vignette covers the whole canvas.
        const rOuter = Math.sqrt(centerX * centerX + centerY * centerY); 

        if (rOuter > 0) { // Only apply if canvas has a discernible size
            const gradient = ctx.createRadialGradient(
                centerX, centerY, 0,      // Inner circle for gradient starts at radius 0 from center
                centerX, centerY, rOuter  // Outer circle for gradient extends to rOuter
            );

            // vignetteSoftness (0-1): 0 = sharp, 1 = very soft/wide transition
            // stopA: where transparency ends and darkening begins (relative to rOuter)
            // stopB: where darkening reaches full vignetteStrength (relative to rOuter)
            // A softness of 0 leads to stopA and stopB being equal (sharp edge).
            // A softness of 1 leads to a wide transition (e.g. 0.1 to 0.9 of rOuter).
            let stopA = 0.5 - (0.4 * vignetteSoftness); 
            let stopB = 0.5 + (0.4 * vignetteSoftness); 

            stopA = Math.max(0.0, Math.min(1.0, stopA)); 
            stopB = Math.max(0.0, Math.min(1.0, stopB)); 
            
            // Ensure stopA is not greater than stopB for a valid gradient.
            // If they are equal, it creates a sharp transition which is valid.
            if (stopA > stopB) {
                 // Swap them if softness calculation inverted them due to extreme values
                 [stopA, stopB] = [stopB, stopA];
            }
            // If stopA and stopB are still identical and at extremes, provide minimal separation
            // This is for robustness, though generally not hit with Math.max/min and (0.4 * vignetteSoftness)
            if (stopA === stopB) {
                if (stopA === 0.0) stopB = 0.001; // Tiny gradient at the start
                else if (stopA === 1.0) stopA = 0.999; // Tiny gradient at the end
                // else, they are equal mid-gradient, creating a hard line (intended for softness=0)
            }


            gradient.addColorStop(0, `rgba(0,0,0,0)`); // Center is fully transparent
            gradient.addColorStop(stopA, `rgba(0,0,0,0)`); // Edge of the clear area
            gradient.addColorStop(stopB, `rgba(0,0,0,${vignetteStrength})`); // Start of full vignette darkness
            gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`);   // Full darkness to the outer edge

            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }
    }
    
    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 Halloween Filter tool allows users to apply a festive Halloween-themed filter to their images. This tool enhances photos by applying an orange tint, adjusting brightness, contrast, and desaturation, and adding a vignette effect for a spooky atmosphere. It is ideal for creating themed images for Halloween parties, social media posts, and seasonal events. Users can customize the intensity of orange, level of desaturation, and the severity of the vignette, making it a versatile option for enhancing photos with a fun and festive touch.

Leave a Reply

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