You can edit the below JavaScript code to customize the image tool.
async function processImage(originalImg, redHueCenterDegStr = "0", hueToleranceDegStr = "30", minSaturationStr = "0.2", minLightnessStr = "0.1") {
// Parse parameters to numbers
const targetHue = parseFloat(redHueCenterDegStr); // e.g., 0 for red
const tolerance = parseFloat(hueToleranceDegStr); // e.g., 30, so 0 +/- 30 degrees
const minSaturation = parseFloat(minSaturationStr); // e.g., 0.2 (0-1 range for saturation)
const minLightness = parseFloat(minLightnessStr); // e.g., 0.1 (0-1 range for lightness)
// Create a canvas element
const canvas = document.createElement('canvas');
// Use { willReadFrequently: true } for potential performance optimization
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Set canvas dimensions to the original image's dimensions
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Handle cases where the image might not be fully loaded or has no dimensions
if (canvas.width === 0 || canvas.height === 0) {
console.warn("Original image has zero width or height. Ensure it is loaded before calling processImage.");
// Return a small, empty canvas as a fallback
canvas.width = 1;
canvas.height = 1;
ctx.clearRect(0, 0, 1, 1); // Make it transparent
return canvas;
}
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Get the image data (pixel array) from the canvas
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error getting image data for processing:", e);
// If getImageData fails (e.g., cross-origin image without CORS), draw an error message on the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the drawn image
ctx.font = "14px Arial";
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
let msg = "Error: Cannot process image.";
if (e.name === 'SecurityError') {
msg = "Error: Image source is cross-origin and not CORS-enabled.";
}
const lines = msg.split('\n');
const lineHeight = 18;
const startY = canvas.height / 2 - (lines.length -1) * lineHeight / 2;
for(let k=0; k<lines.length; k++) {
ctx.fillText(lines[k], canvas.width / 2, startY + k * lineHeight);
}
return canvas;
}
const data = imageData.data;
// Helper function: Convert RGB to HSL
// Input r, g, b are in [0, 255]
// Output h is in [0, 359] degrees, s and l are in [0, 1]
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;
const l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic (grayscale)
} 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;
}
let finalH = Math.round(h * 360);
// Ensure hue is in [0, 359]. If Math.round results in 360 (e.g. for 359.99...), map it to 0.
finalH = finalH % 360;
return [finalH, s, l];
}
// Normalize targetHue to be within [0, 359] degrees for consistent calculations
const normalizedTargetHue = (targetHue % 360 + 360) % 360;
// Calculate the hue range to be preserved. Handles wrapping around 360 degrees.
const hueMin = (normalizedTargetHue - tolerance + 360) % 360;
const hueMax = (normalizedTargetHue + tolerance + 360) % 360;
// Iterate through the pixel data (each pixel has R, G, B, A components)
for (let i = 0; i < data.length; i += 4) {
const r_val = data[i];
const g_val = data[i + 1];
const b_val = data[i + 2];
// Alpha component (data[i + 3]) is typically left unchanged
const [h_pixel, s_pixel, l_pixel] = rgbToHsl(r_val, g_val, b_val);
let isTargetHueRegion;
if (hueMin <= hueMax) {
// Normal hue range (e.g., min=60, max=120 for greens)
isTargetHueRegion = (h_pixel >= hueMin && h_pixel <= hueMax);
} else {
// Hue range wraps around 360 (e.g., min=330, max=30 for reds)
isTargetHueRegion = (h_pixel >= hueMin || h_pixel <= hueMax);
}
// Determine if the pixel's color should be preserved
// It must fall within the target hue range AND meet minimum saturation and lightness.
// The saturation check is crucial: achromatic colors (s_pixel=0) have h_pixel=0.
// If targetHue is red (0 deg), this check ensures grays don't get preserved as red.
const preserveColor = isTargetHueRegion && (s_pixel >= minSaturation) && (l_pixel >= minLightness);
if (!preserveColor) {
// Convert to grayscale using the luminosity method (standard perceptual brightness)
const gray = 0.299 * r_val + 0.587 * g_val + 0.114 * b_val;
data[i] = gray; // Red channel
data[i + 1] = gray; // Green channel
data[i + 2] = gray; // Blue channel
}
// If preserveColor is true, the original R, G, B values remain unchanged.
}
// Put the modified image data back onto 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!
The Image Red Filter Black And White Effect Tool allows users to apply a unique filter to their images, creating a striking black and white effect while preserving specific red hues. This tool is useful for photographers and designers looking to emphasize red elements in their visuals, making them pop against a monochromatic background. It’s ideal for artistic projects, enhancing social media images, or creating distinctive graphics where a selective color effect is desired.