Please bookmark this page to avoid losing your image tool!

Image Cellophane 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.
async function processImage(originalImg, cellophaneColorStr = "255,230,150", opacity = 0.5, shininess = 0.7, detailAmount = 6, shadowIntensity = 0.4) {
    // 0. Parameter sanitization
    const saneOpacity = Math.max(0, Math.min(1, opacity));
    const saneShininess = Math.max(0, Math.min(1, shininess));
    // detailAmount: 1 (max blur, min detail) to 10 (min blur, max detail)
    const saneDetailAmount = Math.max(1, Math.min(10, detailAmount)); 
    const saneShadowIntensity = Math.max(0, Math.min(1, shadowIntensity));

    const C_base = (() => { // IIFE for parsing cellophaneColorStr
        try {
            const parts = cellophaneColorStr.split(',');
            if (parts.length === 3) {
                const r = parseInt(parts[0], 10);
                const g = parseInt(parts[1], 10);
                const b = parseInt(parts[2], 10);
                if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
                    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)) 
                    };
                }
            }
        } catch (e) { /* Fall through to default */ }
        // Default color if parsing fails or invalid format
        return { r: 255, g: 230, b: 150 }; 
    })();

    const w = originalImg.width;
    const h = originalImg.height;

    if (w === 0 || h === 0) {
        // Handle empty image case: return an empty canvas or a specific error indicator
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = w;
        emptyCanvas.height = h;
        return emptyCanvas;
    }

    // 1. Main canvas for final output
    const canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext('2d');
    // Draw the original image onto the main canvas to get its pixel data
    ctx.drawImage(originalImg, 0, 0, w, h);
    const originalImageData = ctx.getImageData(0, 0, w, h);
    const P_data = originalImageData.data; // Original Pixel data

    // 2. Create Light Map (blurred grayscale version of the image)
    const lightMapCanvas = document.createElement('canvas');
    lightMapCanvas.width = w;
    lightMapCanvas.height = h;
    const lightMapCtx = lightMapCanvas.getContext('2d', { willReadFrequently: true });


    // Draw original image to lightMapCanvas for grayscale conversion
    lightMapCtx.drawImage(originalImg, 0, 0, w, h);

    // Convert lightMapCanvas to grayscale
    const lmImageData = lightMapCtx.getImageData(0, 0, w, h);
    const lmData = lmImageData.data;
    for (let i = 0; i < lmData.length; i += 4) {
        const avg = (lmData[i] + lmData[i+1] + lmData[i+2]) / 3;
        lmData[i] = lmData[i+1] = lmData[i+2] = avg;
    }
    lightMapCtx.putImageData(lmImageData, 0, 0);

    // Apply blur to the grayscaled lightMapCanvas
    // blurRadius: 0.5px (for saneDetailAmount=10, max detail) up to 5px (for saneDetailAmount=1, min detail)
    const blurRadius = (11 - saneDetailAmount) * 0.5; 
    
    if (blurRadius > 0) { 
        // To blur lightMapCanvas "in place", draw to a temporary canvas, then copy back
        // This is because canvas filters apply to drawing operations, not existing content directly.
        const tempBlurCanvas = document.createElement('canvas');
        tempBlurCanvas.width = w;
        tempBlurCanvas.height = h;
        const tempBlurCtx = tempBlurCanvas.getContext('2d');
        
        tempBlurCtx.filter = `blur(${blurRadius}px)`;
        tempBlurCtx.drawImage(lightMapCanvas, 0, 0, w, h); // Draw blurred version onto temp canvas
        tempBlurCtx.filter = 'none'; // Reset filter for this context

        lightMapCtx.clearRect(0, 0, w, h); // Clear lightMapCanvas
        lightMapCtx.drawImage(tempBlurCanvas, 0, 0, w, h); // Copy blurred image back
    }
    
    const lightMapFinalImageData = lightMapCtx.getImageData(0, 0, w, h);
    const L_data = lightMapFinalImageData.data; // Luminance data (R channel of grayscale image)

    // 3. Pixel processing loop
    const outputImageData = ctx.createImageData(w, h); // Use main canvas's context to create ImageData
    const outData = outputImageData.data;

    // Define zones for highlights and shadows based on luminance
    // saneShininess: 0 (less shiny, highlights start later) to 1 (very shiny, highlights start sooner)
    // saneShadowIntensity: 0 (less intense shadows, shadows start later) to 1 (very intense shadows, shadows start sooner)
    const highlightZoneStart = 0.5 + (1 - saneShininess) * 0.4; // Ranges from 0.9 down to 0.5
    const shadowZoneEnd = 0.5 - (1 - saneShadowIntensity) * 0.4;  // Ranges from 0.1 up to 0.5
    
    const epsilon = 1e-6; // Small number to prevent division by zero

    for (let i = 0; i < P_data.length; i += 4) {
        const P_r = P_data[i];
        const P_g = P_data[i+1];
        const P_b = P_data[i+2];
        const P_a = P_data[i+3];

        const L_raw = L_data[i]; // Luminance value from blurred grayscale map (0-255)
        const L = L_raw / 255.0;   // Normalized luminance (0-1)

        let reflectR, reflectG, reflectB; // These will be the cellophane's color modulated by light

        if (L >= highlightZoneStart && highlightZoneStart < 1.0) { // Highlight zone
            // Factor approaches 1 as L approaches 1.0
            const factor = (L - highlightZoneStart) / (1.0 - highlightZoneStart + epsilon);
            const intensity = saneShininess; // Use shininess to control highlight brightness
            reflectR = C_base.r + (255 - C_base.r) * factor * intensity;
            reflectG = C_base.g + (255 - C_base.g) * factor * intensity;
            reflectB = C_base.b + (255 - C_base.b) * factor * intensity;
        } else if (L <= shadowZoneEnd && shadowZoneEnd > 0.0) { // Shadow zone
            // Factor approaches 1 as L approaches 0.0
            const factor = (shadowZoneEnd - L) / (shadowZoneEnd + epsilon);
            const intensity = saneShadowIntensity; // Use shadowIntensity to control shadow darkness
            reflectR = C_base.r * (1 - factor * intensity);
            reflectG = C_base.g * (1 - factor * intensity);
            reflectB = C_base.b * (1 - factor * intensity);
        } else { // Midtone zone
            reflectR = C_base.r;
            reflectG = C_base.g;
            reflectB = C_base.b;
        }

        // Clamp reflected color components to 0-255 range
        reflectR = Math.max(0, Math.min(255, reflectR));
        reflectG = Math.max(0, Math.min(255, reflectG));
        reflectB = Math.max(0, Math.min(255, reflectB));

        // Blend the calculated cellophane color (reflectR,G,B) with the original pixel color (P_r,g,b)
        outData[i]   = (reflectR * saneOpacity) + (P_r * (1 - saneOpacity));
        outData[i+1] = (reflectG * saneOpacity) + (P_g * (1 - saneOpacity));
        outData[i+2] = (reflectB * saneOpacity) + (P_b * (1 - saneOpacity));
        outData[i+3] = P_a; // Preserve original alpha
    }

    // Place the processed pixel data back onto the main canvas
    ctx.putImageData(outputImageData, 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 Cellophane Filter Application allows users to apply a custom cellophane filter effect to their images. Users can choose a specific cellophane color, adjust the opacity, shininess, detail amount, and shadow intensity to achieve the desired look. This tool is particularly useful for enhancing artwork, photographs, and design projects by adding a unique visual style that resembles the translucent and reflective properties of cellophane. Ideal for graphic designers, photographers, and digital artists seeking to create distinctive effects or mood in their imagery.

Leave a Reply

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