Please bookmark this page to avoid losing your image tool!

Image Graduated Neutral Density 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, gradientDirection = "top-to-bottom", density = 0.5, transitionStart = 0, transitionEnd = 50, filterColor = "black") {
    // Parameter sanitization: Convert to number and clamp/validate
    let numDensity = parseFloat(density);
    if (isNaN(numDensity)) numDensity = 0.5; // Default if parsing fails
    numDensity = Math.max(0, Math.min(1, numDensity));

    let numTransitionStart = parseFloat(transitionStart);
    if (isNaN(numTransitionStart)) numTransitionStart = 0;
    numTransitionStart = Math.max(0, Math.min(100, numTransitionStart));

    let numTransitionEnd = parseFloat(transitionEnd);
    if (isNaN(numTransitionEnd)) numTransitionEnd = 50;
    numTransitionEnd = Math.max(0, Math.min(100, numTransitionEnd));

    if (numTransitionStart > numTransitionEnd) {
        // If start is after end, swap them to maintain logical order
        [numTransitionStart, numTransitionEnd] = [numTransitionEnd, numTransitionStart];
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Ensure the image is loaded before trying to use its dimensions or draw it
    // Check if originalImg.src is truthy (not empty string, null, or undefined) AND image is not complete
    if (originalImg.src && !originalImg.complete) { 
        try {
            await new Promise((resolve, reject) => {
                originalImg.onload = resolve;
                originalImg.onerror = (err) => {
                    // err might be an Event object or string depending on browser/context
                    let message = "Image failed to load.";
                    if (typeof err === 'string') message += ` Error: ${err}`;
                    else if (err && err.message) message += ` Error: ${err.message}`;
                    else if (originalImg.src) message += ` SRC: ${originalImg.src}`;
                    reject(new Error(message));
                };
                 // Double check if src is truly set, as an empty string might pass the initial check
                if (!originalImg.src) { // This condition might be redundant if originalImg.src check above is strict
                     reject(new Error("Image has no src attribute."));
                }
            });
        } catch (error) {
            console.error("Error loading image in processImage:", error);
            // Fallback: return an error-indicating canvas
            canvas.width = 200; canvas.height = 100; // Default size
            ctx.fillStyle = "lightgray"; ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = "red"; ctx.font = "12px Arial"; ctx.textAlign = "center";
            ctx.fillText("Error loading image.", canvas.width / 2, canvas.height / 2 - 10);
            ctx.fillText(error.message.substring(0,30) + (error.message.length > 30 ? "..." : ""), canvas.width/2, canvas.height/2 + 10);
            return canvas;
        }
    } else if (!originalImg.src && !(originalImg.naturalWidth || originalImg.width) && !(originalImg.height || originalImg.naturalHeight)) {
        // Handles case of `new Image()` passed without `src` set and no dimensions
        console.warn("processImage: Input image has no src and no dimensions. Returning small empty canvas.");
        canvas.width = 1; canvas.height = 1;
        return canvas;
    }
    
    const w = originalImg.naturalWidth || originalImg.width;
    const h = originalImg.naturalHeight || originalImg.height;

    if (w === 0 || h === 0) {
        console.warn("processImage: Image dimensions are zero. Returning small empty canvas.");
        canvas.width = 1; canvas.height = 1;
        return canvas;
    }

    canvas.width = w;
    canvas.height = h;

    ctx.drawImage(originalImg, 0, 0, w, h);

    // Parse filterColor to r, g, b components
    let r_color = 0, g_color = 0, b_color = 0; // Default to black
    const tempElem = document.createElement('div');
    tempElem.style.display = 'none'; 
    tempElem.style.color = filterColor; 
    document.body.appendChild(tempElem); 
    const computedColor = window.getComputedStyle(tempElem).color;
    document.body.removeChild(tempElem);

    const colorMatch = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
    if (colorMatch) {
        r_color = parseInt(colorMatch[1]);
        g_color = parseInt(colorMatch[2]);
        b_color = parseInt(colorMatch[3]);
    } else {
        console.warn(`Could not parse filterColor "${filterColor}". Defaulting to black.`);
    }

    const colorStartStr = `rgba(${r_color},${g_color},${b_color},${numDensity})`;
    const colorEndStr = `rgba(${r_color},${g_color},${b_color},0)`;

    const stop1Pos = numTransitionStart / 100;
    const stop2Pos = numTransitionEnd / 100;

    let grad;
    let x0_grad, y0_grad, x1_grad, y1_grad;

    switch (String(gradientDirection).toLowerCase()) {
        case "bottom-to-top":
            x0_grad = 0; y0_grad = h; x1_grad = 0; y1_grad = 0;
            break;
        case "left-to-right":
            x0_grad = 0; y0_grad = 0; x1_grad = w; y1_grad = 0;
            break;
        case "right-to-left":
            x0_grad = w; y0_grad = 0; x1_grad = 0; y1_grad = 0;
            break;
        case "top-to-bottom":
        default: 
            x0_grad = 0; y0_grad = 0; x1_grad = 0; y1_grad = h;
            if (String(gradientDirection).toLowerCase() !== "top-to-bottom") {
                console.warn(`Invalid gradientDirection "${gradientDirection}". Defaulting to "top-to-bottom".`);
            }
            break;
    }
    
    grad = ctx.createLinearGradient(x0_grad, y0_grad, x1_grad, y1_grad);

    if (stop1Pos === stop2Pos) { // Hard edge or solid fill
        if (stop1Pos <= 0) { // Entirely transparent (transition starts and ends at the beginning)
            grad.addColorStop(0, colorEndStr); 
        } else if (stop1Pos >= 1) { // Entirely dense (transition starts and ends at the end)
            grad.addColorStop(1, colorStartStr);
        } else { // Sharp transition somewhere in the middle (0 < stop1Pos < 1)
            grad.addColorStop(stop1Pos, colorStartStr);
            grad.addColorStop(Math.min(1.0, stop1Pos + 0.000001), colorEndStr); 
        }
    } else { // Soft edge (stop1Pos < stop2Pos)
        grad.addColorStop(stop1Pos, colorStartStr);
        grad.addColorStop(stop2Pos, colorEndStr);
    }
    
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, w, h);

    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 Graduated Neutral Density Filter Effect Tool allows users to apply a graduated neutral density filter effect to their images. Users can control the direction of the gradient, adjust the density of the filter, and customize the transition between the filter and the original image. This tool is useful for photographers and graphic designers looking to enhance their images by balancing exposure across different areas, particularly in landscape photography where uneven lighting can be a challenge.

Leave a Reply

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