Please bookmark this page to avoid losing your image tool!

Image Leather Texture 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.
async function processImage(originalImg, noiseStrength = 30, tintColor = "#654321", tintStrength = 0.6, brightness = -20, contrast = 20) {

    // Helper function to parse hex color string to RGB object
    function hexToRgb(hex) {
        if (!hex || typeof hex !== 'string') return null;
        const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }

    // Validate and ensure the image is loaded
    try {
        if (!originalImg || typeof originalImg.naturalWidth === 'undefined') {
             throw new Error("Invalid Image object provided.");
        }

        // Check if image needs loading
        if (!originalImg.complete || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
            // If src is not set and it's not already loaded, it cannot load.
            if (!originalImg.src && !originalImg.currentSrc) { // currentSrc for potential dynamic changes
                if (!(originalImg.complete && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0)) {
                     throw new Error("Image has no src and is not loaded.");
                }
            }
            
            if (!(originalImg.complete && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0)) {
                 // Only await if not already loaded with valid dimensions
                await new Promise((resolve, reject) => {
                    originalImg.onload = () => {
                        if (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
                            reject(new Error("Image loaded with zero dimensions."));
                        } else {
                            resolve();
                        }
                    };
                    originalImg.onerror = () => reject(new Error("Image failed to load (onerror triggered)."));
                });
            }
        }
        
        // Final check after potential load
        if (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
            throw new Error("Image has zero dimensions after load attempt.");
        }

    } catch (error) {
        console.error("Image loading failed:", error.message);
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 300; 
        errorCanvas.height = 80;
        const ctx = errorCanvas.getContext('2d');
        if (ctx) {
            ctx.fillStyle = 'rgb(240,240,240)';
            ctx.fillRect(0,0,errorCanvas.width, errorCanvas.height);
            ctx.fillStyle = 'black';
            ctx.font = "12px Arial";
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            const message = error.message || "Failed to load image.";
            // Basic text wrapping
            const words = message.split(' ');
            let line = '';
            let y = errorCanvas.height / 2 - ( (Math.ceil(ctx.measureText(message).width / (errorCanvas.width - 20)) -1) * 14 / 2); // Adjust y for multi-line
            
            for(let n = 0; n < words.length; n++) {
                const testLine = line + words[n] + ' ';
                const metrics = ctx.measureText(testLine);
                const testWidth = metrics.width;
                if (testWidth > errorCanvas.width - 20 && n > 0) {
                    ctx.fillText(line, errorCanvas.width / 2, y);
                    line = words[n] + ' ';
                    y += 14; // Line height
                } else {
                    line = testLine;
                }
            }
            ctx.fillText(line, errorCanvas.width/2, y);
        }
        return errorCanvas;
    }

    const canvas = document.createElement('canvas');
    // Use { willReadFrequently: true } for potential performance boost with getImageData
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;
    
    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 from cross-origin image):", e.message);
        // Clear canvas and draw an error message
        ctx.clearRect(0,0,canvas.width, canvas.height); 
        ctx.fillStyle = 'rgb(240,240,240)';
        ctx.fillRect(0,0,canvas.width,canvas.height);
        ctx.fillStyle = 'red';
        // Responsive font size, capped
        const fontSize = Math.min(24, Math.max(12, Math.floor(canvas.width/25)));
        ctx.font = `bold ${fontSize}px Arial`; 
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        const lines = ["Error: Could not process image pixels.", "(Possibly a cross-origin issue)"];
        const lineHeight = fontSize * 1.2;
        let yPos = canvas.height/2 - (lines.length-1) * lineHeight / 2;
        for(const line of lines){
            ctx.fillText(line, canvas.width/2, yPos);
            yPos += lineHeight;
        }
        return canvas; // Return the canvas with the error message
    }
    
    const data = imageData.data;

    let parsedTintColor = hexToRgb(tintColor);
    if (!parsedTintColor) {
        console.warn(`Invalid tintColor: "${tintColor}". Defaulting to #654321 (dark brown).`);
        parsedTintColor = { r: 101, g: 67, b: 33 }; 
    }
    const { r: tintR, g: tintG, b: tintB } = parsedTintColor;

    // Ensure parameters are numeric and appropriately scaled/clamped
    const contrastVal = Math.max(-255, Math.min(255, parseFloat(contrast))); // Clamp contrast to avoid extreme factors / division by zero
    const contrastFactor = (259 * (contrastVal + 255)) / (255 * (259 - contrastVal));
    const brightnessVal = parseFloat(brightness);
    const tintStrengthVal = Math.max(0, Math.min(1, parseFloat(tintStrength)));
    const noiseStrengthVal = parseFloat(noiseStrength);

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

        // 1. Apply Tint
        r = r * (1 - tintStrengthVal) + tintR * tintStrengthVal;
        g = g * (1 - tintStrengthVal) + tintG * tintStrengthVal;
        b = b * (1 - tintStrengthVal) + tintB * tintStrengthVal;

        // 2. Apply Brightness
        r += brightnessVal;
        g += brightnessVal;
        b += brightnessVal;
        
        // Clamp after brightness before contrast, as contrast pivot is 128
        r = Math.max(0, Math.min(255, r));
        g = Math.max(0, Math.min(255, g));
        b = Math.max(0, Math.min(255, b));

        // 3. Apply Contrast
        r = contrastFactor * (r - 128) + 128;
        g = contrastFactor * (g - 128) + 128;
        b = contrastFactor * (b - 128) + 128;

        // Clamp after contrast
        r = Math.max(0, Math.min(255, r));
        g = Math.max(0, Math.min(255, g));
        b = Math.max(0, Math.min(255, b));
        
        // 4. Apply Noise (Affects R, G, B similarly to simulate grain)
        // noiseStrength parameter is used as the amplitude of noise, e.g. if 30, noise is +/- 15.
        const noise = (Math.random() - 0.5) * noiseStrengthVal; 
        r += noise;
        g += noise;
        b += noise;

        // Final clamp for all channels
        data[i] = Math.max(0, Math.min(255, r));
        data[i + 1] = Math.max(0, Math.min(255, g));
        data[i + 2] = Math.max(0, Math.min(255, b));
        // Alpha channel (data[i+3]) remains unchanged
    }

    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 Leather Texture Filter is a web-based tool that allows users to apply a leather-like texture effect to their images. By adjusting parameters such as noise strength, tint color, tint strength, brightness, and contrast, users can personalize the appearance of their images to achieve a unique, stylized look reminiscent of leather. This tool can be useful for graphic designers, artists, and anyone looking to enhance the texture of their images for digital artwork, marketing materials, or social media posts.

Leave a Reply

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