Suggestion for enhancing keyOut in BITMAP

Hi,

I recently needed to use the keyOut function in BITMAP, but I ran into an issue because the images I was working with weren’t the best quality. The green backgrounds often contained multiple shades, so using a single color for keying didn’t produce good results.

I tried passing an array of greenish colors to handle the variations, but that didn’t work as expected either.

Eventually, I wrote a custom function (with help from Claude) that handles multiple colors and allows for more flexibility. I think it could be a great enhancement if keyOut supported something like this natively.

Here’s the function we came up with:

function removeGreenScreen(bitmap) {
    // Default values for green screen detection
    const hueMin = 80;    // Minimum hue value (0-360) for green detection - sets the lower bound of green hues
    const hueMax = 160;   // Maximum hue value (0-360) for green detection - sets the upper bound of green hues 
    const satMin = 30;    // Minimum saturation value (0-100) - avoids detecting desaturated/grayish pixels as green
    const lightMin = 15;  // Minimum lightness value (0-100) - avoids detecting very dark pixels as green
    const smoothing = 0;  // Edge smoothing factor (0-10) - higher values create smoother edges around subjects
    
    // Create temporary canvas for image processing
    const canvas = document.createElement('canvas');
    const width = bitmap.width;
    const height = bitmap.height;
    canvas.width = width;
    canvas.height = height;
    
    // Draw the bitmap onto the canvas
    const ctx = canvas.getContext('2d');
    ctx.drawImage(bitmap.image, 0, 0);
    
    // Get pixel data
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;
    
    // Function to convert RGB to HSL
    function rgbToHsl(r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        
        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        
        if (max === min) {
            h = s = 0; // gray
        } else {
            const d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            
            h /= 6;
        }
        
        return { 
            h: h * 360, // convert to degrees
            s: s * 100, // percentage
            l: l * 100  // percentage
        };
    }
    
    // Process each pixel
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        
        // Convert to HSL for easier green detection
        const hsl = rgbToHsl(r, g, b);
        
        // Check if pixel is in the defined green range
        let isGreen = (hsl.h >= hueMin && hsl.h <= hueMax && 
                       hsl.s >= satMin && 
                       hsl.l >= lightMin);
                       
        // Set transparency with edge smoothing
        let alpha = 255; // opaque by default
        
        if (isGreen) {
            // Completely transparent pixel
            alpha = 0;
        } else if (smoothing > 0) {
            // Check if pixel is close to green range - for edge smoothing
            const hueDiff = Math.min(
                Math.abs(hsl.h - hueMin),
                Math.abs(hsl.h - hueMax)
            );
            
            if (hueDiff < smoothing * 5) {
                // Reduce opacity gradually based on proximity to green
                const factor = hueDiff / (smoothing * 5);
                alpha = Math.min(255, Math.max(0, Math.round(factor * 255)));
            }
        }
        
        // Save pixel with updated transparency
        data[i + 3] = alpha;
    }
    
    // Update image data
    ctx.putImageData(imageData, 0, 0);
    
    // Create new ZIM Bitmap from canvas
    const resultBitmap = new Bitmap(canvas);
    
    return resultBitmap;
}

Thanks @racheli - so the tolerance parameter did not work for you? I think it is trying to do what the code you provided is doing. Maybe you just missed that it was there - let us know.

I tried playing with the tolerance parameter but it didn't work for me. In this code I define the minimum shade of the color and its maximum shade.
And all the colors in between are included in the range

1 Like

Okay - thanks - then it sounds like you have a better way - we should be able to use your code. Will test it out and if it works fix it up for ZIM 018. Thanks!!

1 Like