Please bookmark this page to avoid losing your image tool!

Image Canyon Depth Filter Effect Tool

(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.
async function processImage(originalImg,
    shadowColorStr = "40,30,20",    // Dark brown for deep shadows
    midLowColorStr = "100,70,50",   // Medium brown
    midHighColorStr = "180,140,100", // Light brown/tan
    highlightColorStr = "230,200,170", // Pale orange/yellow for highlights
    edgeStrength = 0.6,          // 0.0 (no edge darkening) to 1.0 (edges become much darker)
    edgeThreshold = 50           // Gradient magnitude (0-~1442) to detect an edge
) {
    // Helper: Parse "r,g,b" string to [r, g, b] array
    function parseColor(colorStr, defaultColor = [0,0,0]) {
        try {
            const colors = colorStr.split(',').map(s => parseInt(s.trim(), 10));
            if (colors.length !== 3 || colors.some(isNaN)) {
                console.warn(`Invalid color string format: "${colorStr}". Using default.`);
                return defaultColor.map(c => Math.max(0, Math.min(255, c)));
            }
            return colors.map(c => Math.max(0, Math.min(255, c))); // Clamp to 0-255
        } catch (e) {
            console.error(`Error parsing color string "${colorStr}": ${e.message}. Using default.`);
            return defaultColor.map(c => Math.max(0, Math.min(255, c)));
        }
    }

    const shadowCol = parseColor(shadowColorStr, [40,30,20]);
    const midLowCol = parseColor(midLowColorStr, [100,70,50]);
    const midHighCol = parseColor(midHighColorStr, [180,140,100]);
    const highlightCol = parseColor(highlightColorStr, [230,200,170]);

    // Helper: Linear interpolation for colors
    function lerpColor(color1, color2, t) {
        const t_clamped = Math.max(0, Math.min(1, t));
        return [
            Math.round(color1[0] * (1 - t_clamped) + color2[0] * t_clamped),
            Math.round(color1[1] * (1 - t_clamped) + color2[1] * t_clamped),
            Math.round(color1[2] * (1 - t_clamped) + color2[2] * t_clamped)
        ];
    }

    // Helper: Get luminance (Rec. 709) from RGB values
    function getLuminance(r, g, b) {
        return 0.2126 * r + 0.7152 * g + 0.0722 * b;
    }

    const canvas = document.createElement('canvas');
    // Add willReadFrequently for potential performance optimization if supported.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    if (!originalImg.complete || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
        console.error("Original image is not loaded or has no dimensions.");
        canvas.width = 200;
        canvas.height = 100;
        ctx.fillStyle = "pink";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "black";
        ctx.textAlign = "center";
        ctx.fillText("Error: Image not loaded.", canvas.width / 2, canvas.height / 2);
        return canvas;
    }

    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Error getting ImageData (possibly CORS issue):", e);
        ctx.clearRect(0,0,canvas.width, canvas.height);
        ctx.fillStyle = "gray";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.font = "14px Arial";
        ctx.fillText("Error: Could not process image.", canvas.width / 2, canvas.height / 2 - 10);
        ctx.fillText("(May be a cross-origin issue)", canvas.width / 2, canvas.height / 2 + 10);
        return canvas;
    }
    
    const data = imageData.data;
    const width = canvas.width;
    const height = canvas.height;

    const outputImageData = ctx.createImageData(width, height);
    const outputData = outputImageData.data;

    // Precompute luminance for all pixels
    const luminances = new Float32Array(width * height);
    for (let i = 0; i < data.length; i += 4) {
        luminances[i / 4] = getLuminance(data[i], data[i + 1], data[i + 2]);
    }

    const T1_3 = 1/3; // Threshold for first segment
    const T2_3 = 2/3; // Threshold for second segment

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const pixelArrIdx = y * width + x; // Index for 1D luminance array
            const dataArrIdx = pixelArrIdx * 4;  // Index for 1D pixel data array (R value)

            const L = luminances[pixelArrIdx];
            const t_norm = L / 255.0; // Normalized luminance [0, 1]

            // 1. Color Mapping based on Luminance
            let mappedR, mappedG, mappedB;
            if (t_norm < T1_3) {
                const local_t = t_norm * 3; // Scale [0, 1/3) to [0, 1)
                [mappedR, mappedG, mappedB] = lerpColor(shadowCol, midLowCol, local_t);
            } else if (t_norm < T2_3) {
                const local_t = (t_norm - T1_3) * 3; // Scale [1/3, 2/3) to [0, 1)
                [mappedR, mappedG, mappedB] = lerpColor(midLowCol, midHighCol, local_t);
            } else {
                const local_t = (t_norm - T2_3) * 3; // Scale [2/3, 1] to [0, 1]
                [mappedR, mappedG, mappedB] = lerpColor(midHighCol, highlightCol, local_t);
            }

            // 2. Edge Detection (Sobel operator)
            let isEdgePixel = false;
            if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
                // Get indices of 3x3 neighborhood in the 1D luminances array
                const tl = (y - 1) * width + (x - 1); const tc = (y - 1) * width + x; const tr = (y - 1) * width + (x + 1);
                const ml = y * width + (x - 1);       /* mc is current */      const mr = y * width + (x + 1);
                const bl = (y + 1) * width + (x - 1); const bc = (y + 1) * width + x; const br = (y + 1) * width + (x + 1);

                const Gx = -luminances[tl] + luminances[tr]
                         -2 * luminances[ml] + 2 * luminances[mr]
                         -luminances[bl] + luminances[br];

                const Gy = -luminances[tl] - 2 * luminances[tc] - luminances[tr]
                         +luminances[bl] + 2 * luminances[bc] + luminances[br];
                
                const gradientMagnitude = Math.sqrt(Gx * Gx + Gy * Gy);
                if (gradientMagnitude > edgeThreshold) {
                    isEdgePixel = true;
                }
            }

            // 3. Apply Edge Effect (darken edges)
            let finalR = mappedR;
            let finalG = mappedG;
            let finalB = mappedB;

            if (isEdgePixel && edgeStrength > 0) {
                const darkeningFactor = Math.max(0, 1 - edgeStrength);
                finalR = Math.round(mappedR * darkeningFactor);
                finalG = Math.round(mappedG * darkeningFactor);
                finalB = Math.round(mappedB * darkeningFactor);
            }
            
            outputData[dataArrIdx]     = Math.max(0, Math.min(255, finalR));
            outputData[dataArrIdx + 1] = Math.max(0, Math.min(255, finalG));
            outputData[dataArrIdx + 2] = Math.max(0, Math.min(255, finalB));
            outputData[dataArrIdx + 3] = data[dataArrIdx + 3]; // Preserve original alpha channel
        }
    }

    ctx.putImageData(outputImageData, 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 Canyon Depth Filter Effect Tool enhances images by applying a depth effect that emphasizes shadows, mid-tones, and highlights using customizable color parameters. This tool is ideal for adding a dramatic or artistic effect to photographs, illustrations, or digital art, making it useful for designers, photographers, and content creators looking to improve visual appeal and depth perception in their images.

Leave a Reply

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