Please bookmark this page to avoid losing your image tool!

Image Matte Portrait 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.
function processImage(originalImg, desaturationAmount = 0.3, contrastAdjustment = -25, liftBlacks = 20, vignetteIntensity = 0.4, vignetteSoftness = 0.6) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Ensure originalImg dimensions are valid
    const naturalWidth = originalImg.naturalWidth || originalImg.width;
    const naturalHeight = originalImg.naturalHeight || originalImg.height;

    if (naturalWidth === 0 || naturalHeight === 0) {
        // Return an empty canvas or handle error for zero-dimension images
        canvas.width = 0;
        canvas.height = 0;
        console.warn("Image Matte Portrait Filter: Input image has zero dimensions.");
        return canvas;
    }

    canvas.width = naturalWidth;
    canvas.height = naturalHeight;

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

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;

    const width = canvas.width;
    const height = canvas.height;
    const centerX = width / 2;
    const centerY = height / 2;
    // maxDist for vignette normalization (distance from center to a corner)
    const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);

    // Helper function for smoothstep interpolation (used in vignette)
    function smoothstep(edge0, edge1, value) {
        // Clamp value to edge0-edge1 range before interpolation
        const x = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)));
        // Hermite interpolation
        return x * x * (3 - 2 * x);
    }
    
    // Ensure parameters are numbers and provide fallback if not (though defaults should handle this)
    desaturationAmount = Number(desaturationAmount) || 0;
    contrastAdjustment = Number(contrastAdjustment) || 0;
    liftBlacks = Number(liftBlacks) || 0;
    vignetteIntensity = Number(vignetteIntensity) || 0;
    vignetteSoftness = Number(vignetteSoftness) || 0;


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

        // 1. Desaturation
        if (desaturationAmount > 0) {
            const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminosity method
            // Lerp original color towards gray
            r = r + (gray - r) * desaturationAmount;
            g = g + (gray - g) * desaturationAmount;
            b = b + (gray - b) * desaturationAmount;
        }

        // 2. Contrast Adjustment
        if (contrastAdjustment !== 0) {
            // Clamp contrastAdjustment to prevent extreme values or division by zero.
            // The formula supports -255 to 255, but practically, very high values are extreme.
            const clampedContrast = Math.max(-254, Math.min(254, contrastAdjustment));
            const factor = (259 * (clampedContrast + 255)) / (255 * (259 - clampedContrast));
            r = factor * (r - 128) + 128;
            g = factor * (g - 128) + 128;
            b = factor * (b - 128) + 128;
        }
        
        // Clamp values after contrast adjustment
        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. Lift Blacks (core to "matte" look)
        // This raises the minimum brightness, making shadows less deep.
        if (liftBlacks > 0) {
            r += liftBlacks;
            g += liftBlacks;
            b += liftBlacks;
        }

        // Clamp values again before vignette, so vignette applies to the colors already adjusted
        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. Vignette
        // vignetteIntensity: 0 (no vignette) to 1 (full effect at edges)
        // vignetteSoftness: 0 (sharpest transition at edge) to 1 (softest, transition starts nearer center)
        if (vignetteIntensity > 0 && maxDist > 0) { // maxDist check for 1x1 pixel images or similar
            const currentX = (i / 4) % width;
            const currentY = Math.floor((i / 4) / width);
            
            const dx = currentX - centerX;
            const dy = currentY - centerY;
            const dist = Math.sqrt(dx * dx + dy * dy);
            const normalizedDist = dist / maxDist; // 0 at center, 1 at corners

            // Calculate where the vignette fade begins (edge0) and ends (edge1 for smoothstep)
            // vignetteSoftness = 0: edge0 = 1.0 (fade only at the very edge, sharp)
            // vignetteSoftness = 1: edge0 = 0.25 (fade starts 25% from center, very soft over large area)
            // Default softness = 0.6: edge0 = 1.0 - (0.6 * 0.75) = 1.0 - 0.45 = 0.55 (fade from 55% radius to edge)
            const vignetteEdge0 = Math.max(0, 1.0 - (vignetteSoftness * 0.75));
            const vignetteEdge1 = 1.0; // Fade always reaches full intensity at the very edge/corners

            let fadeAmount = 0;
            if (vignetteEdge0 < vignetteEdge1) { // Normal case for smoothstep
                 fadeAmount = smoothstep(vignetteEdge0, vignetteEdge1, normalizedDist);
            } else if (normalizedDist >= vignetteEdge0) { // Handles case where edge0=edge1 (e.g. softness makes edge0=1)
                 fadeAmount = 1; // Full effect if at or beyond the sharp edge
            }
            
            const reductionFactor = 1.0 - fadeAmount * vignetteIntensity;
            
            r *= reductionFactor;
            g *= reductionFactor;
            b *= reductionFactor;
        }

        // Final clamp and assignment to image data (ensure integer values)
        data[i] = Math.round(Math.max(0, Math.min(255, r)));
        data[i + 1] = Math.round(Math.max(0, Math.min(255, g)));
        data[i + 2] = Math.round(Math.max(0, Math.min(255, b)));
        // Alpha channel (data[i+3]) is preserved
    }

    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 Portrait Filter is a powerful online tool that enhances portrait images by applying a matte effect. Users can adjust various parameters such as desaturation, contrast, lifting blacks, and vignette effects to achieve a desired stylistic look. This tool is particularly useful for photographers, graphic designers, and social media users looking to give their portraits a softer, more artistic appearance while retaining clarity and focus on the subject. Whether for personal projects or professional portfolios, the Image Matte Portrait Filter allows for customizable image adjustments to create stunning visual results.

Leave a Reply

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