Please bookmark this page to avoid losing your image tool!

Image Geometric Filter Application

(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, filterType = "sobel", strength = 1.0) {
    const canvas = document.createElement('canvas');
    // Optimization hint for frequent readbacks, though support varies.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    canvas.width = imgWidth;
    canvas.height = imgHeight;

    // Handle cases of 0-width/height images gracefully
    if (imgWidth === 0 || imgHeight === 0) {
        console.warn("Image has zero width or height. Returning empty canvas.");
        return canvas; // Return empty canvas (0x0)
    }

    ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        // This can happen due to tainted canvas (e.g., cross-origin image without CORS)
        console.error("Error getting ImageData: ", e);
        // Return a new empty canvas of the same size, as processing is not possible.
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = imgWidth;
        errorCanvas.height = imgHeight;
        // Optionally, draw an error message on errorCanvas here.
        return errorCanvas;
    }
    
    const data = imageData.data;
    // Use imageData.width/height as source of truth after getImageData
    const width = imageData.width; 
    const height = imageData.height;

    // If image is too small for 3x3 kernel, convolution is not well-defined with current border handling.
    // Return a copy of the original image on a new canvas.
    if (width < 3 || height < 3) {
        const smallCanvas = document.createElement('canvas');
        smallCanvas.width = width;
        smallCanvas.height = height;
        const smallCtx = smallCanvas.getContext('2d');
        smallCtx.drawImage(originalImg, 0, 0, width, height);
        console.warn("Image is too small for 3x3 convolution filter. Returning original image copy.");
        return smallCanvas;
    }

    const outputData = new Uint8ClampedArray(data.length);

    // Kernels definition
    const kernels = {
        sobel_gx: [
            [-1, 0, 1],
            [-2, 0, 2],
            [-1, 0, 1]
        ],
        sobel_gy: [ // Standard Sobel Y (points down for positive gradient)
            [-1, -2, -1],
            [ 0,  0,  0],
            [ 1,  2,  1]
        ],
        sharpen: [
            [ 0,-1, 0],
            [-1, 5,-1],
            [ 0,-1, 0]
        ],
        blur: [ // Box blur 3x3
            [1/9, 1/9, 1/9],
            [1/9, 1/9, 1/9],
            [1/9, 1/9, 1/9]
        ]
    };

    // Helper function to get pixel index from (x,y) coordinates
    const getIndex = (x, y, w = width) => (y * w + x) * 4;
    // Helper function to clamp a value between min and max (default 0-255)
    const clamp = (value, min = 0, max = 255) => Math.max(min, Math.min(max, value));

    if (filterType === "sobel" || filterType === "sobel_x" || filterType === "sobel_y") {
        // Create a Float32Array for grayscale values for precision during calculation
        const grayData = new Float32Array(width * height);
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const i = getIndex(x, y);
                // Standard luminance calculation
                grayData[y * width + x] = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
            }
        }

        const kx = kernels.sobel_gx;
        const ky = kernels.sobel_gy;

        // Iterate over interior pixels (kernel requires 1-pixel border)
        for (let y = 1; y < height - 1; y++) {
            for (let x = 1; x < width - 1; x++) {
                let sumGx = 0, sumGy = 0;
                // Apply 3x3 kernel
                for (let j = -1; j <= 1; j++) { // Kernel Y offset
                    for (let i = -1; i <= 1; i++) { // Kernel X offset
                        const grayVal = grayData[(y + j) * width + (x + i)];
                        sumGx += grayVal * kx[j + 1][i + 1];
                        sumGy += grayVal * ky[j + 1][i + 1];
                    }
                }

                let finalVal;
                if (filterType === "sobel_x") {
                    finalVal = clamp(Math.abs(sumGx) * strength);
                } else if (filterType === "sobel_y") {
                    finalVal = clamp(Math.abs(sumGy) * strength);
                } else { // "sobel" (magnitude)
                    finalVal = clamp(Math.sqrt(sumGx * sumGx + sumGy * sumGy) * strength);
                }

                const outIndex = getIndex(x, y);
                outputData[outIndex]     = finalVal; // R
                outputData[outIndex + 1] = finalVal; // G
                outputData[outIndex + 2] = finalVal; // B
                outputData[outIndex + 3] = 255;      // Alpha (opaque)
            }
        }
        // Border handling for Sobel-type filters: set border pixels to black and opaque
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
                    const idx = getIndex(x, y);
                    outputData[idx] = 0; outputData[idx+1] = 0; outputData[idx+2] = 0; outputData[idx+3] = 255;
                }
            }
        }

    } else if (filterType === "sharpen" || filterType === "blur") {
        const kernel = (filterType === "sharpen") ? kernels.sharpen : kernels.blur;

        // Iterate over interior pixels
        for (let y = 1; y < height - 1; y++) {
            for (let x = 1; x < width - 1; x++) {
                let sumR = 0, sumG = 0, sumB = 0;
                // Apply 3x3 kernel to each color channel
                for (let j = -1; j <= 1; j++) { // Kernel Y offset
                    for (let i = -1; i <= 1; i++) { // Kernel X offset
                        const kVal = kernel[j + 1][i + 1];
                        const nIndex = getIndex(x + i, y + j); // Neighbor index
                        sumR += data[nIndex] * kVal;
                        sumG += data[nIndex + 1] * kVal;
                        sumB += data[nIndex + 2] * kVal;
                    }
                }

                const outIndex = getIndex(x, y);
                // Apply strength using linear interpolation: result = original + strength * (filtered - original)
                // This provides intuitive control: strength=0 is original, strength=1 is full filter.
                outputData[outIndex]   = clamp(data[outIndex]   + strength * (sumR - data[outIndex]));
                outputData[outIndex+1] = clamp(data[outIndex+1] + strength * (sumG - data[outIndex+1]));
                outputData[outIndex+2] = clamp(data[outIndex+2] + strength * (sumB - data[outIndex+2]));
                outputData[outIndex+3] = data[outIndex+3]; // Preserve original alpha
            }
        }
        // Border handling for Sharpen/Blur: copy original pixels to avoid a black frame
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
                    const idx = getIndex(x, y);
                    outputData[idx]   = data[idx];
                    outputData[idx+1] = data[idx+1];
                    outputData[idx+2] = data[idx+2];
                    outputData[idx+3] = data[idx+3];
                }
            }
        }
    } else {
        console.warn(`Unknown filterType: "${filterType}". Returning copy of original image.`);
        // If filterType is unknown, copy original image data to outputData
        for(let i=0; i < data.length; i++) {
            outputData[i] = data[i];
        }
    }

    // Create a new canvas for the output
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = width;
    outputCanvas.height = height;
    const outputCtx = outputCanvas.getContext('2d');
    // Create ImageData for the output canvas
    const newImageData = outputCtx.createImageData(width, height);
    newImageData.data.set(outputData); // Set the processed pixel data
    outputCtx.putImageData(newImageData, 0, 0); // Draw the ImageData onto the canvas

    return outputCanvas;
}

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 Geometric Filter Application is a tool that allows users to apply various geometric filters to images, enhancing their visual characteristics. Users can choose from several filter types including Sobel, sharpening, and blurring, with the ability to adjust the strength of the effects. This tool is particularly useful for photographers, graphic designers, and digital artists who want to emphasize edges, reduce noise, or create stylistic effects in their images. It can be applied in a variety of contexts such as photo editing, graphic design projects, and educational purposes to illustrate the impact of image processing techniques.

Leave a Reply

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