Please bookmark this page to avoid losing your image tool!

Image Monochrome Selective Color Filter

(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, targetColorStr = "#FF0000", tolerance = 50) {

    // Helper function to parse color string to RGB object {r, g, b}
    function _parseColorToRgb(colorString) {
        // Ensure input is a string and normalize it (lowercase, trim whitespace)
        const s = String(colorString).trim().toLowerCase();

        // Standard named colors (CSS Level 2 and some common ones)
        const namedColors = {
            "transparent": { r: 0, g: 0, b: 0, a: 0 }, // Special case, treat as black for target
            "aliceblue": { r: 240, g: 248, b: 255 }, "antiquewhite": { r: 250, g: 235, b: 215 },
            "aqua": { r: 0, g: 255, b: 255 }, "aquamarine": { r: 127, g: 255, b: 212 },
            "azure": { r: 240, g: 255, b: 255 }, "beige": { r: 245, g: 245, b: 220 },
            "bisque": { r: 255, g: 228, b: 196 }, "black": { r: 0, g: 0, b: 0 },
            "blanchedalmond": { r: 255, g: 235, b: 205 }, "blue": { r: 0, g: 0, b: 255 },
            "blueviolet": { r: 138, g: 43, b: 226 }, "brown": { r: 165, g: 42, b: 42 },
            "burlywood": { r: 222, g: 184, b: 135 }, "cadetblue": { r: 95, g: 158, b: 160 },
            "chartreuse": { r: 127, g: 255, b: 0 }, "chocolate": { r: 210, g: 105, b: 30 },
            "coral": { r: 255, g: 127, b: 80 }, "cornflowerblue": { r: 100, g: 149, b: 237 },
            "cornsilk": { r: 255, g: 248, b: 220 }, "crimson": { r: 220, g: 20, b: 60 },
            "cyan": { r: 0, g: 255, b: 255 }, "darkblue": { r: 0, g: 0, b: 139 },
            "darkcyan": { r: 0, g: 139, b: 139 }, "darkgoldenrod": { r: 184, g: 134, b: 11 },
            "darkgray": { r: 169, g: 169, b: 169 }, "darkgreen": { r: 0, g: 100, b: 0 },
            "darkgrey": { r: 169, g: 169, b: 169 }, "darkkhaki": { r: 189, g: 183, b: 107 },
            "darkmagenta": { r: 139, g: 0, b: 139 }, "darkolivegreen": { r: 85, g: 107, b: 47 },
            "darkorange": { r: 255, g: 140, b: 0 }, "darkorchid": { r: 153, g: 50, b: 204 },
            "darkred": { r: 139, g: 0, b: 0 }, "darksalmon": { r: 233, g: 150, b: 122 },
            "darkseagreen": { r: 143, g: 188, b: 143 }, "darkslateblue": { r: 72, g: 61, b: 139 },
            "darkslategray": { r: 47, g: 79, b: 79 }, "darkslategrey": { r: 47, g: 79, b: 79 },
            "darkturquoise": { r: 0, g: 206, b: 209 }, "darkviolet": { r: 148, g: 0, b: 211 },
            "deeppink": { r: 255, g: 20, b: 147 }, "deepskyblue": { r: 0, g: 191, b: 255 },
            "dimgray": { r: 105, g: 105, b: 105 }, "dimgrey": { r: 105, g: 105, b: 105 },
            "dodgerblue": { r: 30, g: 144, b: 255 }, "firebrick": { r: 178, g: 34, b: 34 },
            "floralwhite": { r: 255, g: 250, b: 240 }, "forestgreen": { r: 34, g: 139, b: 34 },
            "fuchsia": { r: 255, g: 0, b: 255 }, "gainsboro": { r: 220, g: 220, b: 220 },
            "ghostwhite": { r: 248, g: 248, b: 255 }, "gold": { r: 255, g: 215, b: 0 },
            "goldenrod": { r: 218, g: 165, b: 32 }, "gray": { r: 128, g: 128, b: 128 },
            "green": { r: 0, g: 128, b: 0 }, "greenyellow": { r: 173, g: 255, b: 47 },
            "grey": { r: 128, g: 128, b: 128 }, "honeydew": { r: 240, g: 255, b: 240 },
            "hotpink": { r: 255, g: 105, b: 180 }, "indianred": { r: 205, g: 92, b: 92 },
            "indigo": { r: 75, g: 0, b: 130 }, "ivory": { r: 255, g: 255, b: 240 },
            "khaki": { r: 240, g: 230, b: 140 }, "lavender": { r: 230, g: 230, b: 250 },
            "lavenderblush": { r: 255, g: 240, b: 245 }, "lawngreen": { r: 124, g: 252, b: 0 },
            "lemonchiffon": { r: 255, g: 250, b: 205 }, "lightblue": { r: 173, g: 216, b: 230 },
            "lightcoral": { r: 240, g: 128, b: 128 }, "lightcyan": { r: 224, g: 255, b: 255 },
            "lightgoldenrodyellow": { r: 250, g: 250, b: 210 }, "lightgray": { r: 211, g: 211, b: 211 },
            "lightgreen": { r: 144, g: 238, b: 144 }, "lightgrey": { r: 211, g: 211, b: 211 },
            "lightpink": { r: 255, g: 182, b: 193 }, "lightsalmon": { r: 255, g: 160, b: 122 },
            "lightseagreen": { r: 32, g: 178, b: 170 }, "lightskyblue": { r: 135, g: 206, b: 250 },
            "lightslategray": { r: 119, g: 136, b: 153 }, "lightslategrey": { r: 119, g: 136, b: 153 },
            "lightsteelblue": { r: 176, g: 196, b: 222 }, "lightyellow": { r: 255, g: 255, b: 224 },
            "lime": { r: 0, g: 255, b: 0 }, "limegreen": { r: 50, g: 205, b: 50 },
            "linen": { r: 250, g: 240, b: 230 }, "magenta": { r: 255, g: 0, b: 255 },
            "maroon": { r: 128, g: 0, b: 0 }, "mediumaquamarine": { r: 102, g: 205, b: 170 },
            "mediumblue": { r: 0, g: 0, b: 205 }, "mediumorchid": { r: 186, g: 85, b: 211 },
            "mediumpurple": { r: 147, g: 112, b: 219 }, "mediumseagreen": { r: 60, g: 179, b: 113 },
            "mediumslateblue": { r: 123, g: 104, b: 238 }, "mediumspringgreen": { r: 0, g: 250, b: 154 },
            "mediumturquoise": { r: 72, g: 209, b: 204 }, "mediumvioletred": { r: 199, g: 21, b: 133 },
            "midnightblue": { r: 25, g: 25, b: 112 }, "mintcream": { r: 245, g: 255, b: 250 },
            "mistyrose": { r: 255, g: 228, b: 225 }, "moccasin": { r: 255, g: 228, b: 181 },
            "navajowhite": { r: 255, g: 222, b: 173 }, "navy": { r: 0, g: 0, b: 128 },
            "oldlace": { r: 253, g: 245, b: 230 }, "olive": { r: 128, g: 128, b: 0 },
            "olivedrab": { r: 107, g: 142, b: 35 }, "orange": { r: 255, g: 165, b: 0 },
            "orangered": { r: 255, g: 69, b: 0 }, "orchid": { r: 218, g: 112, b: 214 },
            "palegoldenrod": { r: 238, g: 232, b: 170 }, "palegreen": { r: 152, g: 251, b: 152 },
            "paleturquoise": { r: 175, g: 238, b: 238 }, "palevioletred": { r: 219, g: 112, b: 147 },
            "papayawhip": { r: 255, g: 239, b: 213 }, "peachpuff": { r: 255, g: 218, b: 185 },
            "peru": { r: 205, g: 133, b: 63 }, "pink": { r: 255, g: 192, b: 203 },
            "plum": { r: 221, g: 160, b: 221 }, "powderblue": { r: 176, g: 224, b: 230 },
            "purple": { r: 128, g: 0, b: 128 }, "rebeccapurple": { r: 102, g: 51, b: 153 },
            "red": { r: 255, g: 0, b: 0 }, "rosybrown": { r: 188, g: 143, b: 143 },
            "royalblue": { r: 65, g: 105, b: 225 }, "saddlebrown": { r: 139, g: 69, b: 19 },
            "salmon": { r: 250, g: 128, b: 114 }, "sandybrown": { r: 244, g: 164, b: 96 },
            "seagreen": { r: 46, g: 139, b: 87 }, "seashell": { r: 255, g: 245, b: 238 },
            "sienna": { r: 160, g: 82, b: 45 }, "silver": { r: 192, g: 192, b: 192 },
            "skyblue": { r: 135, g: 206, b: 235 }, "slateblue": { r: 106, g: 90, b: 205 },
            "slategray": { r: 112, g: 128, b: 144 }, "slategrey": { r: 112, g: 128, b: 144 },
            "snow": { r: 255, g: 250, b: 250 }, "springgreen": { r: 0, g: 255, b: 127 },
            "steelblue": { r: 70, g: 130, b: 180 }, "tan": { r: 210, g: 180, b: 140 },
            "teal": { r: 0, g: 128, b: 128 }, "thistle": { r: 216, g: 191, b: 216 },
            "tomato": { r: 255, g: 99, b: 71 }, "turquoise": { r: 64, g: 224, b: 208 },
            "violet": { r: 238, g: 130, b: 238 }, "wheat": { r: 245, g: 222, b: 179 },
            "white": { r: 255, g: 255, b: 255 }, "whitesmoke": { r: 245, g: 245, b: 245 },
            "yellow": { r: 255, g: 255, b: 0 }, "yellowgreen": { r: 154, g: 205, b: 50 }
        };
        if (namedColors[s]) {
            // Alpha is ignored for target color logic, so we don't return it here.
            const {r,g,b} = namedColors[s];
            return {r,g,b};
        }

        // Hex color: #RGB, #RRGGBB, #RGBA, #RRGGBBAA (Alpha component is ignored for target color)
        if (s.startsWith('#')) {
            let hex = s.slice(1);
            if (hex.length === 3 || hex.length === 4) { // #RGB or #RGBA
                hex = hex[0]+hex[0] + hex[1]+hex[1] + hex[2]+hex[2]; // Extract RGB, ignore A
            }
            // Now hex should be 6 chars (from #RGB, #RRGGBB) or 8 chars (from #RGBA, #RRGGBBAA)
            if (hex.length === 6 || hex.length === 8) { // #RRGGBB or #RRGGBBAA
                 const num = parseInt(hex.substring(0,6), 16); // Parse only the RGB part
                 if (!isNaN(num)) {
                    return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
                 }
            }
        }

        // rgb(r,g,b) or rgba(r,g,b,a) (alpha in original string ignored for target color)
        const rgbMatch = s.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)$/);
        if (rgbMatch) {
            return { 
                r: parseInt(rgbMatch[1]), 
                g: parseInt(rgbMatch[2]), 
                b: parseInt(rgbMatch[3]) 
            };
        }
        
        // Fallback for other CSS color names using a temporary element in DOM
        if (typeof document !== 'undefined' && document.createElement) {
            try {
                const tempEl = document.createElement('div');
                tempEl.style.color = colorString; // Use original, unnormalized string
                tempEl.style.display = 'none'; 

                let parentToAppend = document.body;
                if (!parentToAppend) parentToAppend = document.documentElement; // Fallback if body not ready

                if (parentToAppend && typeof parentToAppend.appendChild === 'function' && typeof parentToAppend.removeChild === 'function') {
                    parentToAppend.appendChild(tempEl);
                    const computedColor = window.getComputedStyle(tempEl).color;
                    parentToAppend.removeChild(tempEl);

                    const computedMatch = computedColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)$/);
                    if (computedMatch) {
                        return {
                            r: parseInt(computedMatch[1]),
                            g: parseInt(computedMatch[2]),
                            b: parseInt(computedMatch[3])
                        };
                    }
                }
            } catch (e) {
                console.warn(`Error parsing color "${colorString}" with DOM method: ${e.message}. This may happen if DOM is not fully available or in restricted environments.`);
            }
        }
        
        console.warn(`Could not parse color string: "${colorString}". Defaulting to red (#FF0000).`);
        return { r: 255, g: 0, b: 0 }; // Fallback default color
    }

    const imgWidth = originalImg.naturalWidth;
    const imgHeight = originalImg.naturalHeight;

    const canvas = document.createElement('canvas');
    canvas.width = imgWidth;
    canvas.height = imgHeight;
    
    if (imgWidth === 0 || imgHeight === 0) {
        // The canvas is 0x0, which is a valid element to return.
        // console.warn("Image has zero dimensions. Returning an empty 0x0 canvas.");
        return canvas;
    }

    // Optimization hint for frequent readbacks (getImageData)
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); 

    if (!ctx) {
        console.error("Could not get 2D context from canvas. Canvas support may be missing or disabled.");
        // Return the empty canvas as per requirements (single element)
        return canvas; 
    }

    try {
        ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    } catch (e) {
        console.error("Error drawing image onto canvas:", e);
        // Attempt to draw an error message on the canvas
        ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear previous attempts
        ctx.font = "bold 14px Arial";
        ctx.fillStyle = "red";
        ctx.textAlign = "center";
        const messages = ["Error drawing image.", e.message.length > 50 ? e.message.substring(0,50)+"..." : e.message];
        messages.forEach((line, index) => {
             ctx.fillText(line, canvas.width / 2, canvas.height / 2 - (messages.length-1)*9 + (index * 18));
        });
        return canvas; 
    }

    const targetRgb = _parseColorToRgb(targetColorStr);
    const validTolerance = Math.max(0, Number(tolerance)); 
    const toleranceSq = validTolerance * validTolerance; // Use squared tolerance for efficiency

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        console.error("Could not get ImageData from canvas. This often occurs due to cross-origin security restrictions (CORS) if the image is from a different domain without appropriate headers.", e);
        // Draw an error message on the canvas (original image is already drawn)
        ctx.font = "bold 14px Arial";
        ctx.fillStyle = "rgba(255,0,0,0.7)"; // Semi-transparent red box for message
        ctx.fillRect(0, canvas.height/2 - 25, canvas.width, 50);
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        const messages = [
            "Pixel processing failed.",
            "Image might be cross-origin (CORS issue)."
        ];
        messages.forEach((line, index) => {
             ctx.fillText(line, canvas.width / 2, canvas.height / 2 - (messages.length-1)*9 + (index * 18) - 5); // Shift text up a bit
        });
        return canvas; 
    }
    
    const data = imageData.data; // Uint8ClampedArray: [R,G,B,A, 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];
        // Alpha channel (data[i+3]) is preserved.

        // Calculate squared Euclidean distance to the target color
        const dr = r - targetRgb.r;
        const dg = g - targetRgb.g;
        const db = b - targetRgb.b;
        const distSq = dr * dr + dg * dg + db * db;

        if (distSq <= toleranceSq) {
            // Pixel's color is within the tolerance range of the target_color.
            // Keep the original color (do nothing, as it's already in `data`).
        } else {
            // Pixel's color is outside the tolerance. Convert it to grayscale.
            // Using the luminosity method for perceived brightness:
            const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
            data[i] = gray;     // Red channel
            data[i + 1] = gray; // Green channel
            data[i + 2] = gray; // Blue channel
        }
    }

    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 Monochrome Selective Color Filter tool allows users to convert images into monochrome while preserving specific colors based on user-defined criteria. Users can select a target color and set a tolerance level to define how closely other colors should match this target to avoid being converted to gray. This tool is particularly useful for enhancing images for various applications, such as creating stylized art, preparing graphics for presentations, or focusing on specific color themes in photography. It streamlines the process of isolating and emphasizing certain colors while rendering the rest of the image in shades of gray.

Leave a Reply

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