You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, highlightColorStr = "rgba(255, 255, 224, 0.2)", shadowColorStr = "rgba(50, 50, 100, 0.2)") {
// 1. Create canvas and context
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use naturalWidth/Height for Image objects, or width/height if it's from another canvas/video
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Helper function to parse color strings (e.g., "rgba(r,g,b,a)", "#RRGGBB", "colorname")
// Returns [R, G, B, A_normalized] where A_normalized is 0-1 (0 for fully transparent, 1 for fully opaque)
function parseColor(colorStrInner) {
// Create a temporary 1x1 canvas to parse the color string.
// This is a robust way to convert any valid CSS color string to RGBA components.
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 1;
tempCanvas.height = 1;
const tempCtx = tempCanvas.getContext('2d');
if (!tempCtx) { // Should not happen in a standard browser environment
console.error("Could not get 2D context for color parsing. Please ensure you are running in a browser.");
return [0, 0, 0, 0]; // Fallback to transparent black
}
tempCtx.fillStyle = colorStrInner; // Assign color string to fillStyle
tempCtx.fillRect(0, 0, 1, 1); // Draw a 1x1 rectangle with that color
// Get the RGBA data of the drawn pixel
const colorData = tempCtx.getImageData(0, 0, 1, 1).data;
return [colorData[0], colorData[1], colorData[2], colorData[3] / 255.0]; // R, G, B (0-255), Alpha (0-1)
}
// 2. Parse highlight and shadow colors using the helper
const hc = parseColor(highlightColorStr); // [hR, hG, hB, hA_norm]
const sc = parseColor(shadowColorStr); // [sR, sG, sB, sA_norm]
// 3. Draw original image to the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// 4. Get image data for pixel manipulation
// If width or height is 0 (e.g., image not loaded properly), getImageData will throw an error.
if (canvas.width === 0 || canvas.height === 0) {
// Return the empty (but correctly sized) canvas.
// The caller might want to check canvas.width and canvas.height.
console.warn("Image has zero width or height. Returning empty canvas.");
return canvas;
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // This is a Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]
// 5. Process each pixel to apply split-tone effect
const midLuminance = 127.5; // Mid-point (0-255 scale) for distinguishing shadows/highlights
for (let i = 0; i < data.length; i += 4) {
const r_orig = data[i];
const g_orig = data[i+1];
const b_orig = data[i+2];
// data[i+3] is the original alpha of the pixel, which we'll preserve.
// Calculate luminance (perceived brightness, range 0-255)
const L = 0.299 * r_orig + 0.587 * g_orig + 0.114 * b_orig;
// Initialize final color with original color
let r_final = r_orig;
let g_final = g_orig;
let b_final = b_orig;
// Apply shadow tint
// shadow_blending_factor: determines how much this pixel is considered "in shadow".
// Ranges from 1 (deepest shadow, L=0) to 0 (at or above midLuminance).
let shadow_blending_factor = (midLuminance - L) / midLuminance;
shadow_blending_factor = Math.max(0, Math.min(1, shadow_blending_factor)); // Clamp to [0,1]
if (shadow_blending_factor > 0 && sc[3] > 0) { // sc[3] is shadowColor's alpha (0-1)
// eff_sA: effective alpha for shadow tint, combining shadowColor's alpha and pixel's "shadowiness".
const eff_sA = sc[3] * shadow_blending_factor;
// Blend original pixel color with shadow color
r_final = r_final * (1 - eff_sA) + sc[0] * eff_sA;
g_final = g_final * (1 - eff_sA) + sc[1] * eff_sA;
b_final = b_final * (1 - eff_sA) + sc[2] * eff_sA;
}
// Apply highlight tint (to the potentially already shadow-tinted color)
// highlight_blending_factor: determines how much this pixel is considered "in highlight".
// Ranges from 0 (at or below midLuminance) to 1 (brightest highlight, L=255).
let highlight_blending_factor = (L - midLuminance) / (255.0 - midLuminance);
highlight_blending_factor = Math.max(0, Math.min(1, highlight_blending_factor)); // Clamp to [0,1]
if (highlight_blending_factor > 0 && hc[3] > 0) { // hc[3] is highlightColor's alpha (0-1)
// eff_hA: effective alpha for highlight tint
const eff_hA = hc[3] * highlight_blending_factor;
// Blend current pixel color (which might be shadow-tinted) with highlight color
r_final = r_final * (1 - eff_hA) + hc[0] * eff_hA;
g_final = g_final * (1 - eff_hA) + hc[1] * eff_hA;
b_final = b_final * (1 - eff_hA) + hc[2] * eff_hA;
}
// Assign the final, tinted color back to the imageData.
// Values are automatically clamped to [0,255] because `data` is a Uint8ClampedArray.
data[i] = r_final;
data[i+1] = g_final;
data[i+2] = b_final;
// data[i+3] (alpha) remains unchanged from the original image.
}
// 6. Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
// 7. Return the canvas element
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 Subtle Split-tone Filter is an online tool designed to enhance your images by applying a subtle split-tone effect. It allows users to customize highlight and shadow colors, providing a unique and artistic flair to photographs. Use cases include enhancing portrait photography, creating atmospheric landscapes, or adding a vintage touch to personal images. The tool is ideal for photographers, graphic designers, and hobbyists looking to manipulate their images creatively without extensive software knowledge.