You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, shadowColorStr = "#000080", highlightColorStr = "#FFFFE0", amount = 1.0) {
/**
* Parses a color string (e.g., hex, rgb, color name) into an [R, G, B] array.
* @param {string} colorStr The color string to parse.
* @returns {Array<number>|null} An array [R, G, B] or null if parsing fails.
*/
function _parseColorInternal(colorStr) {
if (!colorStr || typeof colorStr !== 'string') {
return null;
}
// Create a temporary 2D context (doesn't need a visible canvas element)
const ctx = document.createElement('canvas').getContext('2d');
if (!ctx) {
// This should theoretically not happen in a standard browser environment.
console.error("Failed to create 2D context for color parsing.");
return null;
}
// Assign the color string to fillStyle. The browser will parse and canonicalize it.
// First, set to a known distinct value to ensure proper parsing detection.
ctx.fillStyle = 'rgba(0,0,0,1)'; // Opaque black, unlikely to be accidentally matched.
ctx.fillStyle = colorStr; // Attempt to parse the user's color string.
const canonicalColor = ctx.fillStyle; // Read back the canonical color string (e.g., "#rrggbb").
// Modern browsers convert valid colors to hex (#RRGGBB or #RRGGBBAA) or rgba().
// We are interested in #RRGGBB format.
// Example: "red" becomes "#ff0000", "rgb(0,255,0)" becomes "#00ff00".
// If `colorStr` is invalid (e.g., "notAColor"), `fillStyle` often defaults to "#000000" (black).
// Check if the canonical form is a hex color.
if (canonicalColor.startsWith('#') && (canonicalColor.length === 7 || canonicalColor.length === 9)) {
// If an invalid color string was provided and it defaulted to black,
// this parser will return [0,0,0]. This is often an acceptable implicit fallback.
const r = parseInt(canonicalColor.substring(1, 3), 16);
const g = parseInt(canonicalColor.substring(3, 5), 16);
const b = parseInt(canonicalColor.substring(5, 7), 16);
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
return [r, g, b];
}
}
// Potentially handle rgba() string if browser returns that, though hex is more common for fillStyle.
if (canonicalColor.startsWith('rgb')) { // e.g. rgb(0, 0, 0) or rgba(0, 0, 0, 1)
const parts = canonicalColor.match(/(\d+(\.\d+)?)/g);
if (parts && parts.length >= 3) {
const r = parseInt(parts[0]);
const g = parseInt(parts[1]);
const b = parseInt(parts[2]);
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
return [r,g,b];
}
}
}
return null; // Parsing failed or color string was not recognized.
}
// Default RGB values (used if parsing fails or for clarity)
const defaultShadowRGB = [0, 0, 128]; // Navy Blue
const defaultHighlightRGB = [255, 255, 224]; // Light Yellow
// Parse shadow color
let [sR, sG, sB] = defaultShadowRGB;
const parsedShadow = _parseColorInternal(shadowColorStr);
if (parsedShadow) {
[sR, sG, sB] = parsedShadow;
} else if (shadowColorStr && shadowColorStr !== "") { // Warn only if a non-empty string failed
console.warn(`Invalid shadowColor: "${shadowColorStr}". Using default: Navy Blue.`);
}
// Parse highlight color
let [hR, hG, hB] = defaultHighlightRGB;
const parsedHighlight = _parseColorInternal(highlightColorStr);
if (parsedHighlight) {
[hR, hG, hB] = parsedHighlight;
} else if (highlightColorStr && highlightColorStr !== "") { // Warn only if a non-empty string failed
console.warn(`Invalid highlightColor: "${highlightColorStr}". Using default: Light Yellow.`);
}
// Validate and sanitize the 'amount' parameter
let numAmount = Number(amount);
if (isNaN(numAmount)) {
console.warn(`Invalid amount: "${amount}". Using default 1.0.`);
numAmount = 1.0;
}
numAmount = Math.max(0, Math.min(1, numAmount)); // Clamp amount to [0, 1] range
// Canvas setup
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Get image dimensions. naturalWidth/Height for intrinsic size, fallback to width/height.
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Handle cases where the image might not be loaded or has no dimensions.
if (imgWidth === 0 || imgHeight === 0) {
// console.warn("Image has zero width or height. Returning empty canvas.");
return canvas; // Return empty (but correctly sized) canvas.
}
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Try-catch for getImageData, as it can throw security errors for cross-origin images without CORS.
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Failed to get image data, possibly due to CORS policy. Returning original image drawn on canvas.", e);
// For cross-origin issues, the canvas is tainted. We can't process pixels.
// Return the canvas with the image drawn, but not processed.
return canvas;
}
const data = imageData.data;
const originalWeight = 1.0 - numAmount; // Weight of the original pixel color
// Iterate over each pixel
for (let i = 0; i < data.length; i += 4) {
const r_orig = data[i]; // Original red
const g_orig = data[i + 1]; // Original green
const b_orig = data[i + 2]; // Original blue
// Alpha channel data[i+3] is preserved
// Calculate perceptual luminance (brightness) of the original pixel
const lum = 0.299 * r_orig + 0.587 * g_orig + 0.114 * b_orig;
const lumNorm = Math.max(0, Math.min(1, lum / 255.0)); // Normalize luminance to [0, 1]
// Calculate weights for shadow and highlight colors based on luminance and amount
const shadowWeight = (1.0 - lumNorm) * numAmount;
const highlightWeight = lumNorm * numAmount;
// Blend original pixel with shadow and highlight colors
let newR = r_orig * originalWeight + sR * shadowWeight + hR * highlightWeight;
let newG = g_orig * originalWeight + sG * shadowWeight + hG * highlightWeight;
let newB = b_orig * originalWeight + sB * shadowWeight + hB * highlightWeight;
// Assign new RGB values, ensuring they are clamped to [0, 255]
data[i] = Math.max(0, Math.min(255, Math.round(newR)));
data[i+1] = Math.max(0, Math.min(255, Math.round(newG)));
data[i+2] = Math.max(0, Math.min(255, Math.round(newB)));
}
// Put the modified pixel 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!
The Image Split Tone Filter tool allows users to enhance their images by applying a split tone effect. This effect applies a shadow color and a highlight color to the different tonal areas of the image, enabling a unique artistic expression. Users can customize the shadow and highlight colors, as well as control the intensity of the effect. This tool is useful for photographers and artists looking to create visually striking images or achieve a specific mood, making it ideal for enhancing portraits, landscapes, or any creative projects.