Please bookmark this page to avoid losing your image tool!

Image 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, targetColorHex_str = "#0000FF", tolerance_num = 50) {
    // Helper function to convert hex color string to an {r, g, b} object
    function hexToRgb(hex_str) {
        if (typeof hex_str !== 'string') return null;
        // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
        const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex_str = hex_str.replace(shorthandRegex, (m, r_char, g_char, b_char) => {
            return r_char + r_char + g_char + g_char + b_char + b_char;
        });

        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex_str);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }

    // Helper function to calculate Euclidean distance between two RGB colors
    function colorDistance(rgb1, rgb2) {
        // Check if rgb1 or rgb2 are null or undefined, which might happen if targetRgb is not set.
        if (!rgb1 || !rgb2) return Infinity; 
        const dr = rgb1.r - rgb2.r;
        const dg = rgb1.g - rgb2.g;
        const db = rgb1.b - rgb2.b;
        return Math.sqrt(dr * dr + dg * dg + db * db);
    }

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

    // Ensure the image object exists and is loaded, valid, and has dimensions
    if (!originalImg || typeof originalImg.complete !== 'boolean' || !originalImg.complete || 
        typeof originalImg.naturalWidth !== 'number' || originalImg.naturalWidth === 0 ||
        typeof originalImg.naturalHeight !== 'number' || originalImg.naturalHeight === 0) {
        console.error("Provided image is not loaded, is invalid, or has no dimensions.");
        canvas.width = 250; 
        canvas.height = 100;
        ctx.fillStyle = "lightgray";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "red";
        ctx.font = "14px Arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("Error: Image not loaded or invalid.", canvas.width / 2, canvas.height / 2);
        return canvas;
    }
    
    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;

    // Draw the original image onto the canvas
    try {
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Error drawing image onto canvas: ", e);
        // Clear canvas and draw error message
        ctx.fillStyle = "lightgray";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "red";
        ctx.font = "14px Arial"; // Ensure font is appropriate for potentially small canvas
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("Error drawing image.", canvas.width / 2, canvas.height / 2);
        return canvas;
    }

    let targetRgb = hexToRgb(targetColorHex_str);
    if (!targetRgb) {
        const defaultHex = "#0000FF"; // Default to Blue
        console.warn(`Invalid targetColorHex_str: "${targetColorHex_str}". Using default ${defaultHex}.`);
        targetRgb = hexToRgb(defaultHex); 
        if (!targetRgb) { // This should ideally not happen if defaultHex is valid
             console.error("Default hex color is also invalid. Critical error in color parsing logic.");
             // Fallback to a hardcoded known safe RGB to prevent further errors
             targetRgb = { r: 0, g: 0, b: 255 };
        }
    }
    
    // Ensure tolerance is a valid number
    let numericTolerance = Number(tolerance_num);
    if (isNaN(numericTolerance) || numericTolerance < 0) {
        console.warn(`Invalid tolerance value: "${tolerance_num}". Using default 50.`);
        numericTolerance = 50;
    }


    // Get image data from the canvas
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        // This typically happens due to cross-origin issues if the image source is external and not CORS-enabled
        console.error("Error getting image data (likely cross-origin restrictions):", e);
        ctx.save(); // Save current context state (font, fillStyle, etc.)
        ctx.fillStyle = "rgba(200, 200, 200, 0.8)"; // Semi-transparent overlay to make text readable
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "red";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        
        const message = "Error: Cannot process image pixels.";
        const subMessage = "(Possibly cross-origin restrictions)";
        
        // Dynamically adjust font size based on canvas dimensions
        let fontSize = Math.max(10, Math.min(canvas.width / (message.length * 0.6), canvas.height / 5));
        ctx.font = `${fontSize}px Arial`;
        ctx.fillText(message, canvas.width / 2, canvas.height / 2 - fontSize * 0.7);
        
        let subFontSize = Math.max(8, fontSize * 0.8);
        ctx.font = `${subFontSize}px Arial`;
        ctx.fillText(subMessage, canvas.width / 2, canvas.height / 2 + fontSize * 0.7);
        
        ctx.restore(); // Restore context state
        return canvas;
    }
    
    const data = imageData.data;
    // Index constants for accessing RGBA components in the flat data array
    const R_IDX = 0, G_IDX = 1, B_IDX = 2; // A_IDX = 3 (Alpha is preserved)

    for (let i = 0; i < data.length; i += 4) {
        const r = data[i + R_IDX];
        const g = data[i + G_IDX];
        const b = data[i + B_IDX];

        const pixelRgb = { r, g, b };
        
        if (colorDistance(pixelRgb, targetRgb) > numericTolerance) {
            // Convert to grayscale using the luminosity method (standard ITU-R BT.709 coefficients)
            const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b; // More perceptually accurate grayscale
            // const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Older standard, also common
            data[i + R_IDX] = gray;
            data[i + G_IDX] = gray;
            data[i + B_IDX] = gray;
        }
        // Else: pixel color is within tolerance of targetColor, so keep its original color.
    }

    // Put the modified image data back onto the canvas
    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 Selective Color Filter is a powerful online tool that enables users to modify images by applying a selective color filter. This tool allows you to choose a target color, specified in hexadecimal format, and adjust the tolerance level for color matching. Pixels that do not match the selected color within the defined tolerance are transformed into grayscale, while the pixels that match are preserved in their original color. This functionality is useful for artistic effects, highlighting specific elements in an image, or creating striking visual contrasts in photos. It is ideal for graphic designers, photographers, and anyone looking to enhance their images with unique color manipulation.

Leave a Reply

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