Please bookmark this page to avoid losing your image tool!

Image Pull-Processed Film 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.
async function processImage(
    originalImg, 
    desaturationLevel = 0.2, 
    contrastReduction = 0.15, 
    coolTintStrength = 20, 
    grainAmount = 10
) {
    // Clamp parameters to sensible ranges
    desaturationLevel = Math.max(0, Math.min(1, desaturationLevel));
    // For contrastReduction, a value of 1 makes the image entirely mid-gray.
    contrastReduction = Math.max(0, Math.min(1, contrastReduction)); 
    coolTintStrength = Math.max(0, coolTintStrength); // Must be non-negative
    grainAmount = Math.max(0, grainAmount); // Must be non-negative

    const canvas = document.createElement('canvas');
    // Set willReadFrequently for potential performance improvement on repeated getImageData/putImageData calls
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

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

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

    // Handle cases where image might not be loaded or has zero dimensions
    if (canvas.width === 0 || canvas.height === 0) {
        console.warn("Image has zero dimensions. Ensure it's loaded before processing.");
        // Ensure canvas has at least 1x1 dimension for drawing fallback
        canvas.width = Math.max(1, canvas.width);
        canvas.height = Math.max(1, canvas.height);
        
        // Draw a simple placeholder/error message
        ctx.fillStyle = 'lightgray';
        ctx.fillRect(0,0,canvas.width, canvas.height);
        if (typeof ctx.fillText === 'function') {
            ctx.fillStyle = 'red';
            ctx.font = '10px Arial'; // Small font for tiny canvas
            if (canvas.width > 50 && canvas.height > 20) { // Slightly larger font for larger placeholder
                 ctx.font = `${Math.min(canvas.width/4, canvas.height/2, 20)}px Arial`;
            }
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText('Error', canvas.width/2, canvas.height/2);
        }
        return canvas;
    }

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

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Could not get image data, possibly due to cross-origin restrictions:", e);
        // Fallback: return canvas with original image drawn and an error message overlay.
        // The original image is already drawn on the canvas.
        if (typeof ctx.fillText === 'function') {
            ctx.fillStyle = "rgba(255, 0, 0, 0.7)"; // Semi-transparent red
            ctx.textAlign = "center";
            ctx.textBaseline = 'middle';
            
            const fontSize = Math.max(12, Math.min(canvas.width, canvas.height) / 10); // Responsive font size
            ctx.font = `bold ${fontSize}px Arial`;
            
            // Simple text shadow for better visibility
            ctx.shadowColor = "black";
            ctx.shadowOffsetX = 1;    
            ctx.shadowOffsetY = 1;
            ctx.shadowBlur = 3;
            
            ctx.fillText("Processing Error", canvas.width / 2, canvas.height / 2);
            
            // Reset shadow properties for any subsequent drawing (if any)
            ctx.shadowColor = "transparent";
            ctx.shadowOffsetX = 0;    
            ctx.shadowOffsetY = 0;
            ctx.shadowBlur = 0;
        }
        return canvas; // Return the canvas with the original image and error text
    }
    
    const data = imageData.data;
    // Helper to clamp color values to the 0-255 range
    const clamp = (value) => Math.max(0, Math.min(255, value));

    for (let i = 0; i < data.length; i += 4) {
        let r = data[i];
        let g = data[i + 1];
        let b = data[i + 2];
        // Alpha channel (data[i+3]) is preserved throughout

        // 1. Desaturation
        // Reduces color saturation, moving colors towards grayscale.
        if (desaturationLevel > 0) {
            const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Standard luminance calculation
            r = r * (1 - desaturationLevel) + gray * desaturationLevel;
            g = g * (1 - desaturationLevel) + gray * desaturationLevel;
            b = b * (1 - desaturationLevel) + gray * desaturationLevel;
        }

        // 2. Contrast Reduction
        // Flattens the image by pulling color values towards mid-gray (128).
        if (contrastReduction > 0) {
            r = r * (1 - contrastReduction) + 128 * contrastReduction;
            g = g * (1 - contrastReduction) + 128 * contrastReduction;
            b = b * (1 - contrastReduction) + 128 * contrastReduction;
        }
        
        // 3. Cool Tint (applied more strongly to shadows and midtones)
        // Adds a blue/cyan cast, more pronounced in darker areas.
        if (coolTintStrength > 0) {
            // Calculate luminance from current (potentially modified) r,g,b values
            const currentLuminance = (r + g + b) / 3; 
            // lumFactor: 1 for black (max tint), 0 for white (no tint). Clamped to be non-negative.
            const lumFactor = Math.max(0, (255 - currentLuminance) / 255); 

            b += coolTintStrength * lumFactor;          // Add blue
            g += coolTintStrength * 0.6 * lumFactor;    // Add some green for a cyan/cooler tone
        }

        // Clamp intermediate values after tinting, as tinting might push them out of range.
        // r is not directly changed by tinting logic above, but good practice to include if logic changes.
        r = clamp(r); 
        g = clamp(g);
        b = clamp(b);
        
        // 4. Film Grain
        // Adds random noise to simulate film grain.
        if (grainAmount > 0) {
            // Generates noise values typically in the range [-grainAmount/2, grainAmount/2]
            const noise = (Math.random() - 0.5) * grainAmount; 
            r += noise;
            g += noise;
            b += noise;
        }

        // Final assignment: clamp modified RGB values and round to nearest integer.
        data[i] = Math.round(clamp(r));
        data[i + 1] = Math.round(clamp(g));
        data[i + 2] = Math.round(clamp(b));
    }

    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 Image Pull-Processed Film Filter Effect Tool allows users to apply a creative filter effect to their images by adjusting various parameters. This tool can desaturate colors, reduce contrast, add a cool tint, and simulate film grain, resulting in a nostalgic or artistic look reminiscent of processed film photography. Ideal for photographers, graphic designers, and social media enthusiasts, this tool enhances images for creative projects, personal sharing, or artistic expression.

Leave a Reply

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