Please bookmark this page to avoid losing your image tool!

Image Moody Cinematic 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,
    desaturation = 0.4,      // 0.0 (no change) to 1.0 (grayscale)
    contrast = 1.3,          // 1.0 is no change. <1 reduces, >1 increases.
    brightnessOffset = -10,  // Pixel value offset. Affects overall brightness. Range typically -255 to 255.
    tint = "rgba(0, 40, 80, 0.2)", // CSS color string for tint layer (e.g., "rgba(0,50,100,0.1)" for a cool blueish tint)
    tintBlendMode = "soft-light", // Blend mode for tint: 'soft-light', 'overlay', 'multiply', 'color', etc.
    vignetteIntensity = 0.6, // 0.0 (none) to 1.0 (strong black vignette)
    vignetteSmoothness = 0.5,// 0.01 (sharp edge) to 1.0 (very soft, fades from center)
    grain = 0.05             // 0.0 (none) to 1.0 (very noisy)
) {
    const canvas = document.createElement('canvas');
    
    // Use naturalWidth/Height for intrinsic image size, fallback to width/height
    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;

    if (width === 0 || height === 0) {
        // Return a minimal (e.g., 1x1 transparent) canvas if image dimensions are invalid
        canvas.width = 1;
        canvas.height = 1;
        // Optional: fill with transparent color
        // const tempCtx = canvas.getContext('2d');
        // if (tempCtx) { tempCtx.fillStyle = 'rgba(0,0,0,0)'; tempCtx.fillRect(0,0,1,1); }
        return canvas;
    }

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

    const ctx = canvas.getContext('2d');
    if (!ctx) {
        // This should ideally not happen in modern browsers for 2D context
        console.error("Failed to get 2D context from canvas.");
        return canvas; // Return the canvas, which will be blank
    }

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

    // 2. Attempt to get image data for pixel manipulation
    // This can fail if the canvas is tainted (e.g., cross-origin image without CORS)
    let imageData = null;
    try {
        imageData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        console.warn("Could not get image data for pixel manipulation. " +
                     "Effects like desaturation, contrast, and grain will be skipped. " +
                     "This is often due to cross-origin image restrictions.", e);
    }

    if (imageData) {
        const data = imageData.data;
        
        // Clamp and prepare parameter values for pixel operations
        const actualDesaturation = Math.max(0, Math.min(1, desaturation));
        const actualContrast = Math.max(0, contrast); // Contrast factor (0 = solid gray, 1 = no change)
        const actualGrain = Math.max(0, Math.min(1, grain));
        const grainAmount = actualGrain * 50; // Max noise amplitude (e.g., +/-50 for grain=1.0)

        // 3. Process pixels: desaturation, contrast, brightness, grain
        for (let i = 0; i < data.length; i += 4) {
            let r = data[i];
            let g = data[i+1];
            let b = data[i+2];

            // Apply Desaturation
            // L = 0.299R + 0.587G + 0.114B
            // R' = R + (L - R) * desaturationAmount
            if (actualDesaturation > 0) {
                const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
                r += (luminance - r) * actualDesaturation;
                g += (luminance - g) * actualDesaturation;
                b += (luminance - b) * actualDesaturation;
            }

            // Apply Contrast and Brightness
            // Contrast formula: NewValue = factor * (OldValue - 128) + 128
            // Then add brightness offset
            if (actualContrast !== 1 || brightnessOffset !== 0) {
                r = actualContrast * (r - 128) + 128 + brightnessOffset;
                g = actualContrast * (g - 128) + 128 + brightnessOffset;
                b = actualContrast * (b - 128) + 128 + brightnessOffset;
            }

            // Apply Grain
            if (grainAmount > 0) {
                // Generate monochromatic noise
                const noise = (Math.random() * 2 - 1) * grainAmount;
                r += noise;
                g += noise;
                b += noise;
            }

            // Clamp values to [0, 255]
            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));
        }
        // 4. Put modified imageData back onto the canvas
        ctx.putImageData(imageData, 0, 0);
    } // End of pixel manipulation block

    // Store original context settings to restore them later
    const originalGCO = ctx.globalCompositeOperation;
    const originalFillStyle = ctx.fillStyle;

    // 5. Apply Tint layer (drawing operation, works even if imageData failed)
    // Ensure tint is a non-empty string. `ctx.fillStyle` handles "transparent" or rgba(...,0) correctly.
    if (typeof tint === 'string' && tint.trim() !== "") {
        try {
            ctx.globalCompositeOperation = tintBlendMode;
            ctx.fillStyle = tint;
            ctx.fillRect(0, 0, width, height);
        } catch (e) {
            console.warn("Failed to apply tint. Invalid tint color or blend mode?", e);
        } finally {
            // Reset to avoid affecting subsequent drawing operations outside this function potentially
            ctx.globalCompositeOperation = originalGCO;
            ctx.fillStyle = originalFillStyle;
        }
    }

    // 6. Apply Vignette (drawing operation, works even if imageData failed)
    const actualVignetteIntensity = Math.max(0, Math.min(1, vignetteIntensity));
    if (actualVignetteIntensity > 0) {
        const centerX = width / 2;
        const centerY = height / 2;
        
        // Calculate outer radius to cover corners
        const outerRadius = Math.hypot(centerX, centerY);
        
        // vignetteSmoothness defines the size of the transparent center relative to outerRadius
        // Clamp to [0.01, 1.0] for sensible results
        const actualVignetteSmoothness = Math.max(0.01, Math.min(1, vignetteSmoothness));
        const innerRadius = outerRadius * (1 - actualVignetteSmoothness);

        try {
            const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
            gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent center
            gradient.addColorStop(1, `rgba(0,0,0,${actualVignetteIntensity})`); // Dark edges

            ctx.fillStyle = gradient;
            // Ensure vignette is drawn using source-over blending on top of existing content
            ctx.globalCompositeOperation = 'source-over'; 
            ctx.fillRect(0, 0, width, height);
        } catch (e) {
            console.warn("Failed to apply vignette.", e);
        } finally {
             // Reset properties
            ctx.fillStyle = originalFillStyle;
            ctx.globalCompositeOperation = originalGCO;
        }
    }

    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 Moody Cinematic Filter Application is a web-based tool designed to enhance images by applying various cinematic effects. Users can adjust parameters such as desaturation, contrast, brightness, tint, and vignette to create a moody atmosphere in their photos. This tool is suitable for photographers and social media enthusiasts looking to stylize their images for artistic projects, personal albums, or online sharing. It allows for fine-tuning visual elements to achieve desired effects, making images appear more dynamic and visually appealing.

Leave a Reply

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