Please bookmark this page to avoid losing your image tool!

Image Silhouette Shadow Filter Effect Tool

(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, threshold = 128, silhouetteColorStr = "black", backgroundColorStr = "transparent") {
    const canvas = document.createElement('canvas');
    // Use { willReadFrequently: true } for potential performance gains when using getImageData/putImageData frequently.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Set canvas dimensions to the intrinsic dimensions of the image.
    // originalImg.naturalWidth/naturalHeight are the true dimensions of the image file.
    // Fallback to originalImg.width/height if natural Dims are not available (e.g., image not fully loaded, or it's not an HTMLImageElement).
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    // Draw the original image onto the canvas. This is necessary to access its pixel data.
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    // Helper function to parse color strings (e.g., "red", "#FF0000", "rgba(255,0,0,0.5)") into RGBA components.
    // isForSilhouette parameter helps determine fallback color if parsing fails.
    function parseColorToRGBA(colorStr, isForSilhouette) {
        const tempElem = document.createElement('div');
        // Set a known default color. If colorStr is invalid, getComputedStyle might return this default.
        tempElem.style.color = 'rgb(0,0,0)'; 
        try {
            // Attempt to set the user-provided color string.
            // Browsers are generally lenient with invalid color strings, but this is a precaution.
            tempElem.style.color = colorStr;
        } catch (e) {
            // Silently ignore errors if the color string is exceptionally malformed, fallback logic will handle it.
            // console.warn(`Error setting color string via style assignment: "${colorStr}"`, e);
        }

        // The element must be part of the document for getComputedStyle to accurately determine the color.
        document.body.appendChild(tempElem);
        const computedColor = window.getComputedStyle(tempElem).color;
        document.body.removeChild(tempElem);
        
        // computedColor is typically in "rgb(r, g, b)" or "rgba(r, g, b, a)" format.
        // Example: "rgb(255, 0, 0)" or "rgba(0, 0, 0, 0.5)"
        const match = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
        if (match) {
            // Extract R, G, B values.
            const r = parseInt(match[1]);
            const g = parseInt(match[2]);
            const b = parseInt(match[3]);
            // Extract Alpha value. If not present (rgb format), defaults to 1.0 (opaque).
            const alpha = match[4] !== undefined ? parseFloat(match[4]) : 1.0; // CSS alpha is 0-1
            
            return {
                r: r,
                g: g,
                b: b,
                a: Math.round(alpha * 255) // Convert CSS alpha (0-1) to ImageData alpha (0-255)
            };
        }
        
        // Fallback if color string could not be parsed by the browser (e.g., "invalid-color-name", system colors like "ButtonText").
        // console.warn(`Failed to parse color string: "${colorStr}". Computed as: "${computedColor}". Using fallback.`);
        if (isForSilhouette) {
             return { r: 0, g: 0, b: 0, a: 255 }; // Default silhouette to opaque black
        } else { // Background context
             return { r: 0, g: 0, b: 0, a: 0 };   // Default background to fully transparent
        }
    }

    // Parse the user-provided color strings into RGBA objects.
    const silhouetteRGBA = parseColorToRGBA(silhouetteColorStr, true);
    const backgroundRGBA = parseColorToRGBA(backgroundColorStr, false);
    
    // Validate and normalize the threshold parameter.
    let numThreshold = Number(threshold);
    if (isNaN(numThreshold)) { // Handle cases where threshold might be a non-numeric string.
        numThreshold = 128; // Default threshold if input is not a valid number.
    }
    // Clamp threshold to the valid 0-255 range (inclusive) for luminance.
    numThreshold = Math.max(0, Math.min(255, numThreshold));

    // Get the pixel data from the canvas.
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data; // This is a Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]

    // Define an alpha cutoff. Pixels with original alpha below this value are
    // considered fully part of the background, regardless of their color.
    // This helps clean up faint edges or nearly transparent areas from the original image.
    const ALPHA_CUTOFF = 20; // Value from 0 (fully transparent) to 255 (fully opaque).

    // Iterate over each pixel in the image data (4 bytes at a time: R, G, B, A).
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        const originalAlpha = data[i + 3];

        // If the original pixel is substantially transparent, it's forced to become background.
        if (originalAlpha < ALPHA_CUTOFF) {
            data[i] = backgroundRGBA.r;
            data[i + 1] = backgroundRGBA.g;
            data[i + 2] = backgroundRGBA.b;
            data[i + 3] = backgroundRGBA.a; // Apply background color and its alpha.
            continue; // Move to the next pixel.
        }

        // Calculate luminance (perceived brightness) using the standard NTSC conversion formula.
        // This gives a grayscale representation of the pixel's color.
        const luminance = 0.299 * r + 0.587 * g + 0.114 * b;

        // If luminance is below the threshold, the pixel becomes part of the silhouette.
        if (luminance < numThreshold) {
            data[i] = silhouetteRGBA.r;
            data[i + 1] = silhouetteRGBA.g;
            data[i + 2] = silhouetteRGBA.b;
            data[i + 3] = silhouetteRGBA.a; // Apply silhouette color and its alpha.
        } else { // Otherwise, the pixel becomes part of the background.
            data[i] = backgroundRGBA.r;
            data[i + 1] = backgroundRGBA.g;
            data[i + 2] = backgroundRGBA.b;
            data[i + 3] = backgroundRGBA.a; // Apply background color and its alpha.
        }
    }

    // Write the modified pixel data back to the canvas.
    ctx.putImageData(imageData, 0, 0);
    
    // Return the canvas element with the processed image.
    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 Silhouette Shadow Filter Effect Tool allows users to transform images by applying a silhouette effect. This tool enables users to define a threshold for luminance, a silhouette color, and a background color, effectively converting parts of the image into a solid silhouette based on brightness. It’s particularly useful for creating artistic representations of images, such as making profile pictures, designing graphics for logos, or enhancing artistic projects by emphasizing shapes and outlines. This tool can benefit graphic designers, artists, and anyone looking to create unique visual content.

Leave a Reply

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