Please bookmark this page to avoid losing your image tool!

Dramatic Photo Filter 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, 
    desaturationAmountParam = "0.8", 
    contrastAmountParam = "1.8", 
    brightnessAdjustParam = "-20", 
    vignetteIntensityParam = "0.6", 
    vignetteSoftnessParam = "1.5") {

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;

    if (width === 0 || height === 0) {
        canvas.width = 0;
        canvas.height = 0;
        // console.error("Dramatic Photo Filter: Image has zero dimensions."); // Optional: for debugging
        return canvas; // Return empty canvas for zero-dimension image
    }

    canvas.width = width;
    canvas.height = height;

    try {
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    } catch (e) {
        // This can happen if originalImg is not a valid image source (e.g. tainted canvas, broken img)
        // console.error("Dramatic Photo Filter: Could not draw image on canvas.", e); // Optional: for debugging
        // Return a blank canvas of correct dimensions.
        return canvas;
    }
    
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        // This can happen due to canvas tainting if the image is cross-origin and CORS isn't set up.
        // console.error("Dramatic Photo Filter: Could not get ImageData from canvas. Tainted canvas?", e); // Optional: for debugging
        // Return the canvas with the original image drawn (if drawImage succeeded), but without filter.
        return canvas;
    }
    
    const data = imageData.data;

    // Default values for parsing fallback and validation
    const DEFAULT_DESATURATION = 0.8;
    const DEFAULT_CONTRAST = 1.8;
    const DEFAULT_BRIGHTNESS = -20;
    const DEFAULT_VIGNETTE_INTENSITY = 0.6;
    const DEFAULT_VIGNETTE_SOFTNESS = 1.5; // Must be > 0

    // Parse parameters to numbers
    let desaturationAmount = parseFloat(desaturationAmountParam);
    let contrastAmount = parseFloat(contrastAmountParam);
    let brightnessAdjust = parseFloat(brightnessAdjustParam);
    let vignetteIntensity = parseFloat(vignetteIntensityParam);
    let vignetteSoftness = parseFloat(vignetteSoftnessParam);

    // Validate parsed parameters: if NaN, use default. Then clamp to sensible operational ranges.
    desaturationAmount = isNaN(desaturationAmount) ? DEFAULT_DESATURATION : Math.max(0, Math.min(1, desaturationAmount));
    contrastAmount = isNaN(contrastAmount) ? DEFAULT_CONTRAST : Math.max(0, contrastAmount); // Lower bound 0, no strict upper for flexibility
    brightnessAdjust = isNaN(brightnessAdjust) ? DEFAULT_BRIGHTNESS : Math.max(-255, Math.min(255, brightnessAdjust));
    vignetteIntensity = isNaN(vignetteIntensity) ? DEFAULT_VIGNETTE_INTENSITY : Math.max(0, Math.min(1, vignetteIntensity));
    vignetteSoftness = isNaN(vignetteSoftness) ? DEFAULT_VIGNETTE_SOFTNESS : Math.max(0.1, vignetteSoftness); // Min 0.1 as exponent to avoid issues with pow(0,0)

    const centerX = width / 2;
    const centerY = height / 2;
    // maxDist is distance from center to a corner. Used for vignette falloff.
    // This will be > 0 if width or height > 0.
    const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);

    for (let i = 0; i < data.length; i += 4) {
        let r_proc = data[i];
        let g_proc = data[i + 1];
        let b_proc = data[i + 2];

        // 1. Desaturation
        if (desaturationAmount > 0) {
            const gray = 0.299 * r_proc + 0.587 * g_proc + 0.114 * b_proc;
            // Lerp (Linear Interpolation) towards gray:  (1-amount)*start + amount*end
            r_proc = r_proc * (1 - desaturationAmount) + gray * desaturationAmount;
            g_proc = g_proc * (1 - desaturationAmount) + gray * desaturationAmount;
            b_proc = b_proc * (1 - desaturationAmount) + gray * desaturationAmount;
        }

        // 2. Contrast
        // Formula: NewValue = MidPoint + (OldValue - MidPoint) * Factor
        // MidPoint is 128 for 8-bit color values.
        // Factor = 1 -> no change. Factor > 1 -> increase contrast. 0 <= Factor < 1 -> decrease contrast.
        if (contrastAmount !== 1.0) {
            r_proc = 128 + (r_proc - 128) * contrastAmount;
            g_proc = 128 + (g_proc - 128) * contrastAmount;
            b_proc = 128 + (b_proc - 128) * contrastAmount;
        }

        // 3. Brightness adjustment
        if (brightnessAdjust !== 0) {
            r_proc += brightnessAdjust;
            g_proc += brightnessAdjust;
            b_proc += brightnessAdjust;
        }
        
        // Clamp intermediate values before applying multiplicative vignette
        r_proc = Math.max(0, Math.min(255, r_proc));
        g_proc = Math.max(0, Math.min(255, g_proc));
        b_proc = Math.max(0, Math.min(255, b_proc));

        // 4. Vignette
        // Apply vignette only if intensity is positive and maxDist is positive (to avoid division by zero for 0x0 images, though handled earlier)
        if (vignetteIntensity > 0 && maxDist > 0) {
            const x_coord = (i / 4) % width; // Current pixel's x coordinate
            const y_coord = Math.floor((i / 4) / width); // Current pixel's y coordinate
            
            const dx = x_coord - centerX;
            const dy = y_coord - centerY;
            const dist = Math.sqrt(dx * dx + dy * dy);
            
            // vignetteEffectRatio: 0 at center, up to 1 at maxDist (corner)
            // Clamped to max 1 for safety, though dist should not exceed maxDist if calculated from pixel center to image center.
            const vignetteEffectRatio = Math.min(1, dist / maxDist); 

            // Apply softness (power curve for falloff shape). Higher softness means sharper falloff near edges.
            // vignetteAmount is 0 (center, no effect) to 1 (edge, full effect based on intensity)
            const vignetteAmount = Math.pow(vignetteEffectRatio, vignetteSoftness);
            
            // Darkening multiplier: ranges from 1 (no change) down to (1 - vignetteIntensity) (max darkening)
            const darkeningMultiplier = 1.0 - (vignetteAmount * vignetteIntensity);
            
            r_proc *= darkeningMultiplier;
            g_proc *= darkeningMultiplier;
            b_proc *= darkeningMultiplier;
        }

        // Final clamp and assign to imageData (ensure integer values using Math.round)
        data[i] = Math.round(Math.max(0, Math.min(255, r_proc)));
        data[i + 1] = Math.round(Math.max(0, Math.min(255, g_proc)));
        data[i + 2] = Math.round(Math.max(0, Math.min(255, b_proc)));
        // Alpha channel (data[i+3]) remains unchanged
    }

    ctx.putImageData(imageData, 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 Dramatic Photo Filter Tool allows users to enhance their images by applying a dramatic filter that includes adjustments for desaturation, contrast, brightness, and vignette effects. This tool is ideal for photographers and creatives seeking to give their photos a more stylistic and striking appearance. Common use cases include enhancing portraits, improving landscape photography, or adding artistic flair to any image intended for social media, digital art, or presentations.

Leave a Reply

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