Please bookmark this page to avoid losing your image tool!

Image Grunge 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,
    grayscale = 0.7,      // 0.0 (original color) to 1.0 (fully grayscale)
    contrast = 1.5,       // e.g., 0.0 (gray), 1.0 (original), >1.0 (more contrast)
    sepia = 0.3,          // 0.0 (none) to 1.0 (full sepia)
    noise = 25,           // 0 (none) to e.g., 50 (heavy noise/grain)
    vignette = 0.6,       // 0.0 (none) to 1.0 (strongest vignette, black edges)
    numScratches = 3,     // Number of scratch lines to draw
    numDirtSpots = 50     // Number of dirt spots to draw
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

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

    if (!w || !h) {
        console.error("Image has zero dimensions or is not loaded properly.");
        // Return a small canvas with an error message
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 200;
        errorCanvas.height = 50;
        const errorCtx = errorCanvas.getContext('2d');
        if (errorCtx) {
            errorCtx.fillStyle = "red";
            errorCtx.font = "12px Arial";
            errorCtx.fillText("Error: Invalid image source.", 10, 20);
            errorCtx.fillText("Ensure image is loaded & has dimensions.", 10, 40);
        }
        return errorCanvas;
    }

    canvas.width = w;
    canvas.height = h;

    // 1. Apply core filters: grayscale, contrast, sepia using canvas context filter property
    let filterString = "";
    if (grayscale > 0) { // grayscale(0) is original colors
        filterString += `grayscale(${grayscale}) `; 
    }
    if (sepia > 0) { // sepia(0) is no effect
        filterString += `sepia(${sepia}) `;
    }
    if (contrast !== 1.0) { // contrast(1) is original contrast
        filterString += `contrast(${contrast}) `;
    }
    
    if (filterString) {
        ctx.filter = filterString.trim();
    }
    
    ctx.drawImage(originalImg, 0, 0, w, h);
    ctx.filter = 'none'; // Reset filter for subsequent manual operations

    // 2. Apply noise and vignette through pixel manipulation
    if (noise > 0 || vignette > 0) {
        const imageData = ctx.getImageData(0, 0, w, h);
        const data = imageData.data;
        const centerX = w / 2;
        const centerY = h / 2;
        // Use diagonal distance to a corner as maxDist for vignette normalization
        const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);

        const truncate = (value) => Math.max(0, Math.min(255, Math.round(value)));

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

            // Apply noise
            if (noise > 0) {
                // Generate noise value between -noise and +noise
                const noiseVal = (Math.random() - 0.5) * noise * 2;
                r = truncate(r + noiseVal);
                g = truncate(g + noiseVal);
                b = truncate(b + noiseVal);
            }

            // Apply vignette
            if (vignette > 0 && maxDist > 0) { // maxDist > 0 to avoid division by zero for 1x1px images etc.
                const pixelY = Math.floor((i / 4) / w);
                const pixelX = (i / 4) % w;
                const dx = pixelX - centerX;
                const dy = pixelY - centerY;
                const dist = Math.sqrt(dx * dx + dy * dy);
                
                // Normalized distance from center (0 at center, 1 at maxDist or further)
                const normDist = dist / maxDist; 
                
                // vignette parameter (0 to 1) controls the strength of darkening at edges.
                // Using Math.pow(normDist, 2.0) for a quadratic falloff (common for vignettes)
                const vignetteEffectAmount = vignette * Math.pow(normDist, 2.0);
                const vignetteMultiplier = Math.max(0, 1.0 - vignetteEffectAmount);

                r = truncate(r * vignetteMultiplier);
                g = truncate(g * vignetteMultiplier);
                b = truncate(b * vignetteMultiplier);
            }
            
            data[i] = r;
            data[i+1] = g;
            data[i+2] = b;
        }
        ctx.putImageData(imageData, 0, 0);
    }

    // 3. Draw scratches on top
    if (numScratches > 0) {
        for (let i = 0; i < numScratches; i++) {
            const x1 = Math.random() * w;
            const y1 = Math.random() * h;
            const angle = Math.random() * Math.PI * 2;
            // Scratch length relative to image size, e.g., 10% to 40% of the smaller dimension
            const length = (Math.random() * 0.3 + 0.1) * Math.min(w, h); 
            
            const x2 = x1 + Math.cos(angle) * length;
            const y2 = y1 + Math.sin(angle) * length;

            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            
            // Scratches can be dark or light with varying opacity for subtlety
            if (Math.random() > 0.5) { // Light scratch
                ctx.strokeStyle = `rgba(200, 200, 200, ${Math.random() * 0.15 + 0.05})`; // opacity 0.05 to 0.2
            } else { // Dark scratch
                ctx.strokeStyle = `rgba(50, 50, 50, ${Math.random() * 0.25 + 0.1})`; // opacity 0.1 to 0.35
            }
            ctx.lineWidth = Math.random() * 1.5 + 0.5; // Thickness from 0.5px to 2px
            ctx.stroke();
        }
    }

    // 4. Draw dirt spots on top
    if (numDirtSpots > 0) {
        for (let i = 0; i < numDirtSpots; i++) {
            const x = Math.random() * w;
            const y = Math.random() * h;
            const radius = Math.random() * 2.0 + 1.0; // Radius from 1px to 3px

            ctx.beginPath();
            ctx.arc(x, y, radius, 0, Math.PI * 2);
            
            // Dirt spots are usually darkish and semi-transparent
            ctx.fillStyle = `rgba(30, 30, 30, ${Math.random() * 0.2 + 0.05})`; // opacity 0.05 to 0.25
            ctx.fill();
        }
    }
    
    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 Grunge Filter Application allows users to apply various stylistic effects to their images, creating a vintage or distressed look. This tool offers adjustments for grayscale, contrast, and sepia tones, as well as the ability to add noise, and a vignette effect. Users can customize the number of scratches and dirt spots to further enhance the grunge aesthetic of the image. It is ideal for enhancing photos for artistic projects, retro-style social media posts, or any creative work that requires a worn or aged appearance.

Leave a Reply

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