Please bookmark this page to avoid losing your image tool!

Image Matte 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.
async function processImage(originalImg,
    liftBlacksParam = 20,
    reduceWhitesParam = 20,
    saturationParam = 0.7,
    tintColorStr = "40,30,25", // Default: A subtle warm, slightly desaturated brownish tone
    tintAlphaParam = 0.1) {

    // Clamp and validate numeric parameters
    const liftBlacks = Math.max(0, Math.min(255, Number(liftBlacksParam)));
    const reduceWhites = Math.max(0, Math.min(255, Number(reduceWhitesParam)));
    const saturation = Math.max(0, Math.min(1, Number(saturationParam)));
    const tintAlpha = Math.max(0, Math.min(1, Number(tintAlphaParam)));

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

    // Ensure the image is loaded if it's an HTMLImageElement
    // The problem states originalImg is an "Image object", usually meaning HTMLImageElement.
    if (originalImg instanceof HTMLImageElement && !originalImg.complete) {
        try {
            await new Promise((resolve, reject) => {
                originalImg.onload = resolve;
                originalImg.onerror = (err) => reject(new Error("Image failed to load for processing: " + (err.message || err)));
                // Check if src is set, otherwise onload might never fire
                if (!originalImg.src) {
                    reject(new Error("Image source is not set."));
                }
            });
        } catch (e) {
            console.error("Error loading image:", e);
            // Return an empty (or error-indicating) canvas or re-throw
            ctx.canvas.width = 100; // Example error canvas
            ctx.canvas.height = 30;
            ctx.font = "12px Arial";
            ctx.fillText("Error loading image", 5, 20);
            return canvas;
        }
    }
    
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        console.warn("Original image has zero width or height. Returning an empty canvas.");
        // Potentially draw a message on the canvas if preferred
        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 ImageData (e.g., tainted canvas):", e);
        // Potentially return the original drawn canvas without filter or an error canvas
        ctx.canvas.width = 200; // Example error canvas
        ctx.canvas.height = 30;
        ctx.font = "12px Arial";
        ctx.fillText("Error: Could not process image", 5, 20);
        return canvas; // Return canvas with original image / error
    }
    const data = imageData.data;

    // Parse tint color string
    let parsedTintR, parsedTintG, parsedTintB;
    // These are the components of the default tintColorStr "40,30,25"
    const defaultTintValues = { r: 40, g: 30, b: 25 }; 

    const tintParts = tintColorStr.split(',').map(s => parseFloat(s.trim()));
    if (tintParts.length === 3 && tintParts.every(n => !isNaN(n) && isFinite(n))) {
        parsedTintR = Math.max(0, Math.min(255, tintParts[0]));
        parsedTintG = Math.max(0, Math.min(255, tintParts[1]));
        parsedTintB = Math.max(0, Math.min(255, tintParts[2]));
    } else {
        const defaultTintStrForComparison = `${defaultTintValues.r},${defaultTintValues.g},${defaultTintValues.b}`;
        if (tintColorStr !== defaultTintStrForComparison) {
           console.warn(`Invalid tintColorStr: "${tintColorStr}". Using default tint color ("${defaultTintStrForComparison}"). Format should be 'R,G,B'.`);
        }
        // Fallback to hardcoded default tint values
        parsedTintR = defaultTintValues.r;
        parsedTintG = defaultTintValues.g;
        parsedTintB = defaultTintValues.b;
    }

    // Calculate new black and white points for contrast adjustment
    const newBlack = liftBlacks; 
    let newWhite = 255 - reduceWhites; 

    if (newWhite < newBlack) {
        // This ensures that the mapped range is not inverted (e.g. black becoming white).
        // If newWhite becomes equal to newBlack, contrastRange will be 0, 
        // leading to a flat color (newBlack,newBlack,newBlack) before tinting.
        newWhite = newBlack; 
    }
    const contrastRange = newWhite - newBlack;

    // Pre-calculate factors for performance optimization within the loop
    const sFactor = 1 - saturation; // Used for desaturation
    const taFactor = 1 - tintAlpha; // Used for tint blending

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

        // 1. Saturation adjustment
        if (saturation < 1.0) { // saturation = 1.0 means original saturation, no change needed
            const lum = 0.299 * r + 0.587 * g + 0.114 * b; // Standard luminance calculation
            r = r * saturation + lum * sFactor;
            g = g * saturation + lum * sFactor;
            b = b * saturation + lum * sFactor;
        }

        // 2. Contrast adjustment (Lifting blacks, crushing whites)
        // This linearly maps the current pixel component's value (assumed to be in [0, 255] after saturation)
        // to a new range [newBlack, newWhite].
        // If contrastRange is 0 (i.e., newBlack == newWhite), all components will map to newBlack.
        r = newBlack + (r / 255) * contrastRange;
        g = newBlack + (g / 255) * contrastRange;
        b = newBlack + (b / 255) * contrastRange;
        
        // 3. Tinting
        if (tintAlpha > 0) { // tintAlpha = 0 means no tint, no change needed
            r = r * taFactor + parsedTintR * tintAlpha;
            g = g * taFactor + parsedTintG * tintAlpha;
            b = b * taFactor + parsedTintB * tintAlpha;
        }

        // 4. Clamping results to [0, 255] and rounding
        // Pixel data must be integers.
        data[i]   = Math.max(0, Math.min(255, Math.round(r)));
        data[i+1] = Math.max(0, Math.min(255, Math.round(g)));
        data[i+2] = Math.max(0, Math.min(255, Math.round(b)));
        // Alpha channel (data[i+3]) is preserved untouched
    }

    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 Matte Filter Application allows users to enhance their images by applying a matte filter with adjustable parameters. Users can modify the contrast by lifting blacks and reducing whites, adjust saturation levels, and add a custom tint color to achieve the desired aesthetic effect. This tool is suitable for photographers and graphic designers who want to create a vintage or softened look in their images, as well as for social media enthusiasts looking to enhance their visual content with unique color treatments.

Leave a Reply

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