Please bookmark this page to avoid losing your image tool!

Image Subtle Split-tone 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, 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!

Description

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.

Leave a Reply

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