Please bookmark this page to avoid losing your image tool!

Image Cinemagraph 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.
function processImage(originalImg, saturationStr = "0.8", contrastStr = "1.1", vignetteStr = "0.3") {
    // Parse and validate parameters, providing defaults for invalid inputs
    let saturation = parseFloat(saturationStr);
    if (isNaN(saturation)) {
        saturation = 0.8; // Default saturation: slight desaturation
    }

    let contrast = parseFloat(contrastStr);
    if (isNaN(contrast)) {
        contrast = 1.1; // Default contrast: slight increase
    }
    // For contrast, values >= 0 are typical. contrast = 0 results in a mid-gray image.
    // Negative contrast inverts colors around the 128 midpoint. We allow this for flexibility.

    let vignetteStrength = parseFloat(vignetteStr);
    if (isNaN(vignetteStrength)) {
        vignetteStrength = 0.3; // Default vignette: subtle darkening at edges
    }
    // Vignette strength is conceptually a factor from 0 (no effect) to 1 (max effect).
    vignetteStrength = Math.max(0, Math.min(1, vignetteStrength));


    // Create a new canvas element
    const canvas = document.createElement('canvas');
    // Get the 2D rendering context. { willReadFrequently: true } is a performance hint.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

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

    // Handle cases of invalid image dimensions (e.g., image not loaded yet or 0x0 image)
    if (width === 0 || height === 0) {
        canvas.width = 0;
        canvas.height = 0;
        return canvas; // Return an empty 0x0 canvas
    }

    canvas.width = width;
    canvas.height = height;

    // Draw the original image onto the canvas
    ctx.drawImage(originalImg, 0, 0, width, height);

    let imageData;
    try {
        // Get the ImageData object to manipulate pixels directly
        imageData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        // This can occur if the canvas is tainted (e.g., cross-origin image without CORS)
        console.error("Error getting ImageData (e.g., tainted canvas). Returning canvas with original image drawn:", e);
        // Fallback: return the canvas with just the original image drawn on it.
        return canvas;
    }
    
    const data = imageData.data; // This is a Uint8ClampedArray
    const centerX = width / 2.0;
    const centerY = height / 2.0;
    
    // Calculate the maximum distance from the center to a corner of the image.
    // This is used for normalizing the vignette effect distance.
    const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);

    // Iterate over each pixel in the image data (RGBA order)
    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

        // 1. Apply Saturation adjustment
        // saturation = 1.0 means original color fidelity.
        // saturation = 0.0 means fully desaturated (grayscale).
        // saturation < 1.0 desaturates, > 1.0 oversaturates.
        if (saturation !== 1.0) { 
            const luma = 0.299 * r + 0.587 * g + 0.114 * b; // Standard luminance calculation
            r = luma + saturation * (r - luma);
            g = luma + saturation * (g - luma);
            b = luma + saturation * (b - luma);
        }

        // 2. Apply Contrast adjustment
        // contrast = 1.0 means original contrast.
        // contrast > 1.0 increases contrast.
        // contrast < 1.0 decreases contrast (values towards 128).
        if (contrast !== 1.0) { 
            r = (r - 128) * contrast + 128;
            g = (g - 128) * contrast + 128;
            b = (b - 128) * contrast + 128;
        }
        
        // Clamp intermediate RGB values to the [0, 255] range after saturation and contrast.
        // This is crucial before applying vignette, which expects values in this 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));
        
        // 3. Apply Vignette effect
        // vignetteStrength (clamped to 0-1) determines the maximum darkening at image edges.
        if (vignetteStrength > 0 && maxDist > 0) { // maxDist > 0 check for robustness (e.g. 1x1 image)
            const pixelX = (i / 4) % width; // Current pixel's X coordinate
            const pixelY = Math.floor((i / 4) / width); // Current pixel's Y coordinate
            
            const dx = pixelX - centerX; // Distance from center X
            const dy = pixelY - centerY; // Distance from center Y
            const dist = Math.sqrt(dx * dx + dy * dy); // Euclidean distance from center

            // Normalize distance: 0 at center, 1 at maxDist (corners)
            const normalizedDist = dist / maxDist; 
            
            // VIGNETTE_FALLOFF_POWER controls the smoothness of the vignette's fade.
            // Common values are 1.0 (linear) or 2.0 (quadratic, default here for smoother effect).
            const VIGNETTE_FALLOFF_POWER = 2.0; 
            
            // Calculate how much the vignette darkens this specific pixel.
            // It's stronger (closer to vignetteStrength) further from the center.
            const vignetteEffectAmount = vignetteStrength * Math.pow(normalizedDist, VIGNETTE_FALLOFF_POWER);
            
            // reductionFactor is multiplied by color components (1.0 = no change, 0.0 = black).
            const reductionFactor = 1.0 - vignetteEffectAmount;
            
            r *= reductionFactor;
            g *= reductionFactor;
            b *= reductionFactor;
            // RGB values will remain >= 0 because reductionFactor is >= 0 (as vignetteStrength <=1 and normalizedDist <=1)
            // and r,g,b were positive before this step.
        }

        // Store the modified RGB values back into the ImageData.
        // Uint8ClampedArray automatically clamps values to the 0-255 range on assignment.
        data[i]     = r; 
        data[i + 1] = g;
        data[i + 2] = b;
        // data[i+3] (alpha) 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 Cinemagraph Filter Application allows users to enhance their images by adjusting saturation, contrast, and applying a vignette effect. This tool is ideal for photographers, graphic designers, and social media enthusiasts looking to create visually striking images. Use it to make your images more vibrant, improve their contrast for better depth, and add a subtle darkening effect around the edges to draw focus to the center of the image. Whether you’re preparing images for a portfolio, social media posts, or personal projects, this tool can help elevate the aesthetic quality of your visuals.

Leave a Reply

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