Please bookmark this page to avoid losing your image tool!

Image Patina Finish 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.
function processImage(originalImg, patinaColorStr = "70,140,120", intensity = 1.0) {
    // Helper function to parse color string (e.g., "R,G,B" or "#RRGGBB" or "#RGB")
    // Returns an object {r, g, b} with values clamped 0-255.
    function _parsePatinaColor(colorStrInput, defaultR, defaultG, defaultB) {
        let r = defaultR, g = defaultG, b = defaultB;
        
        // Use defaults if input is not a non-empty string
        if (typeof colorStrInput !== 'string' || colorStrInput.trim() === "") {
            return { 
                r: Math.max(0, Math.min(255, r)), 
                g: Math.max(0, Math.min(255, g)), 
                b: Math.max(0, Math.min(255, b))
            };
        }

        const trimmedColorStr = colorStrInput.trim();

        if (trimmedColorStr.startsWith('#')) {
            let hex = trimmedColorStr.substring(1);
            if (hex.length === 3) { // Expand shorthand hex #RGB to #RRGGBB
                hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
            }
            if (hex.length === 6) {
                const parsedR = parseInt(hex.substring(0, 2), 16);
                const parsedG = parseInt(hex.substring(2, 4), 16);
                const parsedB = parseInt(hex.substring(4, 6), 16);
                // Update r,g,b only if all parsing is successful
                if (!isNaN(parsedR) && !isNaN(parsedG) && !isNaN(parsedB)) {
                    r = parsedR; g = parsedG; b = parsedB;
                }
            }
        } else {
            const parts = trimmedColorStr.split(',');
            if (parts.length === 3) {
                const parsedR = parseInt(parts[0].trim(), 10);
                const parsedG = parseInt(parts[1].trim(), 10);
                const parsedB = parseInt(parts[2].trim(), 10);
                // Update r,g,b only if all parsing is successful
                if (!isNaN(parsedR) && !isNaN(parsedG) && !isNaN(parsedB)) {
                    r = parsedR; g = parsedG; b = parsedB;
                }
            }
        }
        
        // Clamp values to 0-255 range
        r = Math.max(0, Math.min(255, r));
        g = Math.max(0, Math.min(255, g));
        b = Math.max(0, Math.min(255, b));
        return { r, g, b };
    }

    const canvas = document.createElement('canvas');
    // Using { willReadFrequently: true } can optimize frequent 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;

    // If image has no dimensions (e.g., not loaded or invalid), return an empty canvas
    if (imgWidth === 0 || imgHeight === 0) {
        console.warn("Image has zero dimensions.");
        return canvas;
    }

    try {
        ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    } catch (e) {
        console.error("Error drawing image to canvas:", e);
        // Return canvas as is (it might be empty or partially drawn)
        return canvas;
    }

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        console.error("Error getting image data from canvas. This can be due to CORS restrictions if the image is loaded from a different origin.", e);
        // If getImageData fails, pixel manipulation is not possible.
        // Return the canvas with the original image drawn (if drawImage succeeded).
        return canvas; 
    }
    
    const pixels = imageData.data; // Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]

    // Parse the patinaColorStr parameter using the helper
    const { r: r_patina_target, g: g_patina_target, b: b_patina_target } = 
        _parsePatinaColor(patinaColorStr, 70, 140, 120); // Default patina color: R=70,G=140,B=120 (a greenish-blue)

    // Normalize patina color components to factors between 0.0 and 1.0
    const r_patina_norm_factor = r_patina_target / 255;
    const g_patina_norm_factor = g_patina_target / 255;
    const b_patina_norm_factor = b_patina_target / 255;

    // Parse and clamp the intensity parameter (must be between 0.0 and 1.0)
    let parsedIntensity = typeof intensity === 'string' ? parseFloat(intensity) : intensity;
    if (typeof parsedIntensity !== 'number' || isNaN(parsedIntensity)) {
        parsedIntensity = 1.0; // Default to full intensity if parsing fails or type is incorrect
    }
    const clampedIntensity = Math.max(0, Math.min(1, parsedIntensity));

    // Iterate over each pixel (4 array elements per pixel: R, G, B, A)
    for (let i = 0; i < pixels.length; i += 4) {
        const r_orig = pixels[i];     // Original Red
        const g_orig = pixels[i+1];   // Original Green
        const b_orig = pixels[i+2];   // Original Blue
        // pixels[i+3] is Alpha, which we'll leave unchanged

        // Calculate luminance (perceived brightness) of the original pixel
        // Standard NTSC/BT.709 formula for converting RGB to Luminance (grayscale)
        const luminance = 0.299 * r_orig + 0.587 * g_orig + 0.114 * b_orig;

        // Calculate the "patina" color version for this pixel.
        // This step effectively colorizes the luminance value with the target patina hue.
        // A brighter original pixel (higher luminance) will result in a brighter shade of the patina color.
        const patinaR = luminance * r_patina_norm_factor;
        const patinaG = luminance * g_patina_norm_factor;
        const patinaB = luminance * b_patina_norm_factor;

        // Blend the original pixel color with the calculated patina color based on intensity.
        // If intensity is 1.0, the pixel becomes fully patinated.
        // If intensity is 0.0, the pixel remains its original color.
        // Values between 0 and 1 create a partial patina effect.
        pixels[i]   = (1 - clampedIntensity) * r_orig + clampedIntensity * patinaR;
        pixels[i+1] = (1 - clampedIntensity) * g_orig + clampedIntensity * patinaG;
        pixels[i+2] = (1 - clampedIntensity) * b_orig + clampedIntensity * patinaB;
        // Alpha (pixels[i+3]) remains unchanged.
    }

    // Write the modified pixel data back to the canvas
    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 Patina Finish Filter Effect Tool allows users to apply a patinated color effect to images, enhancing them with a unique aesthetic reminiscent of antique or weathered looks. Users can customize the color of the patina effect and adjust the intensity to achieve desired results. This tool is particularly useful for photographers, graphic designers, and artists looking to add vintage or artistic feels to their images, making it suitable for creative projects, digital artwork, and social media content.

Leave a Reply

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