Please bookmark this page to avoid losing your image tool!

Photo Grunge Mood 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, 
    desaturation = 0.6, 
    contrast = 1.4, 
    grain = 25, 
    sepia = 0.3, 
    brightness = -15,
    tintOverlayColor = "rgba(40, 25, 10, 0.1)", // Subtle dark brownish overlay
    vignette = 0.6
    ) {

    // Helper function for clamping values
    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    // Ensure parameters are of correct type and apply defaults if necessary.
    // Store processed parameters in new variables to avoid modifying originals if they are used later in a wider scope.
    let pDesaturation = Number(desaturation);
    let pContrast = Number(contrast);
    let pGrain = Number(grain);
    let pSepia = Number(sepia);
    let pBrightness = Number(brightness);
    let pVignette = Number(vignette);
    let pTintOverlayColor = (typeof tintOverlayColor === 'string') ? tintOverlayColor : "rgba(40, 25, 10, 0.1)";

    // Use default values if conversion to Number resulted in NaN
    pDesaturation     = isNaN(pDesaturation) ? 0.6 : pDesaturation;
    pContrast       = isNaN(pContrast)     ? 1.4 : pContrast;
    pGrain          = isNaN(pGrain)        ? 25  : pGrain;
    pSepia          = isNaN(pSepia)        ? 0.3 : pSepia;
    pBrightness     = isNaN(pBrightness)   ? -15 : pBrightness;
    pVignette       = isNaN(pVignette)     ? 0.6 : pVignette;


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

    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;
    
    if (imgWidth === 0 || imgHeight === 0) {
        // Handle case where image might not be loaded or has no dimensions
        // Return a 1x1 transparent canvas to avoid errors downsteam.
        canvas.width = 1; 
        canvas.height = 1;
        return canvas; 
    }

    canvas.width = imgWidth;
    canvas.height = imgHeight;

    // Initial draw of the image
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    const len = data.length;

    // Clamp numeric parameters to their operational ranges
    pDesaturation = clamp(pDesaturation, 0, 1);
    pContrast     = clamp(pContrast, 0, 5); // 0-1 reduces contrast, 1 no change, >1 increases
    pGrain        = clamp(pGrain, 0, 100);
    pSepia        = clamp(pSepia, 0, 1);
    pBrightness   = clamp(pBrightness, -255, 255);
    pVignette     = clamp(pVignette, 0, 1);

    for (let i = 0; i < len; i += 4) {
        let r = data[i];
        let g = data[i + 1];
        let b = data[i + 2];

        // 1. Brightness adjustment
        r += pBrightness;
        g += pBrightness;
        b += pBrightness;

        // 2. Desaturation
        // Mix current color with its grayscale equivalent
        if (pDesaturation > 0) {
            const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminosity method
            r = gray * pDesaturation + r * (1 - pDesaturation);
            g = gray * pDesaturation + g * (1 - pDesaturation);
            b = gray * pDesaturation + b * (1 - pDesaturation);
        }

        // 3. Sepia
        // Mix current color with its sepia equivalent
        if (pSepia > 0) {
            const R_ = r; // Use current r,g,b (possibly desaturated and brightened)
            const G_ = g;
            const B_ = b;
            
            // Standard sepia weights
            const sr = (R_ * 0.393) + (G_ * 0.769) + (B_ * 0.189);
            const sg = (R_ * 0.349) + (G_ * 0.686) + (B_ * 0.168);
            const sb = (R_ * 0.272) + (G_ * 0.534) + (B_ * 0.131);
            
            r = (1 - pSepia) * R_ + pSepia * sr;
            g = (1 - pSepia) * G_ + pSepia * sg;
            b = (1 - pSepia) * B_ + pSepia * sb;
        }
        
        // 4. Contrast
        // Adjust contrast by stretching/compressing values relative to midpoint (128)
        if (pContrast !== 1.0) { 
            r = (((r / 255.0 - 0.5) * pContrast) + 0.5) * 255.0;
            g = (((g / 255.0 - 0.5) * pContrast) + 0.5) * 255.0;
            b = (((b / 255.0 - 0.5) * pContrast) + 0.5) * 255.0;
        }

        // 5. Grain
        // Add random noise
        if (pGrain > 0) {
            const noise = (Math.random() - 0.5) * pGrain;
            r += noise;
            g += noise;
            b += noise;
        }

        // Clamp final RGB values to [0, 255]
        data[i]     = clamp(r, 0, 255);
        data[i + 1] = clamp(g, 0, 255);
        data[i + 2] = clamp(b, 0, 255);
        // Alpha (data[i+3]) remains unchanged
    }
    ctx.putImageData(imageData, 0, 0); // Apply pixel manipulations

    // 6. Tint Overlay (applied on top of pixel-processed image)
    if (pTintOverlayColor && pTintOverlayColor.trim() !== "") {
        try {
            ctx.fillStyle = pTintOverlayColor; // Browser parses color string
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        } catch (e) {
            // Silently ignore if tint color string is invalid to prevent breaking
        }
    }

    // 7. Vignette (applied on top of everything else)
    if (pVignette > 0) {
        const w = canvas.width;
        const h = canvas.height;
        const gradCenterX = w / 2;
        const gradCenterY = h / 2;
        
        // Outer radius should reach the furthest corner from the center to cover image
        const gradOuterRadius = Math.sqrt(Math.pow(w / 2, 2) + Math.pow(h / 2, 2));
        // Inner radius becomes smaller as vignette strength increases, creating a larger dark area
        // The 0.95 factor makes the vignette effect start a bit more sharply/closer to the center.
        const gradInnerRadius = gradOuterRadius * (1.0 - pVignette * 0.95); 
        
        const gradient = ctx.createRadialGradient(
            gradCenterX, gradCenterY, gradInnerRadius, 
            gradCenterX, gradCenterY, gradOuterRadius
        );
        
        gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Center is transparent
        gradient.addColorStop(1, `rgba(0,0,0,${pVignette})`); // Edges are dark with opacity pVignette

        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, w, h);
    }
    
    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 Grunge Mood Filter is an online image processing tool that allows users to apply a vintage, grunge effect to their photos. With customizable settings, users can adjust desaturation, contrast, grain, sepia tone, brightness, and add a subtle tint overlay and vignette effect. This tool is ideal for enhancing photographs with a retro aesthetic suitable for social media posts, artistic projects, or any creative work that requires a stylish, textured look.

Leave a Reply

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