Please bookmark this page to avoid losing your image tool!

Image Compass Rose Filter Effect

(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, threshold = 50, colorsStr = "#FFFF00,#FFA500,#FF0000,#FF00FF,#800080,#0000FF,#00FFFF,#90EE90") {

    // Helper function to convert HEX color to RGB object
    function hexToRgb(hex) {
        // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
        const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);

        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }

    const canvas = document.createElement('canvas');
    // Using { willReadFrequently: true } can optimize frequent getImageData/putImageData calls
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); 
    
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;

    // Draw original image to canvas to access its pixel data
    ctx.drawImage(originalImg, 0, 0);
    
    // Get ImageData for the original image pixels.
    // This will serve as the base for the output; non-edge pixels will retain original color.
    const originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const originalData = originalImageData.data;

    // Create output ImageData object. Initialize it with the original image data.
    // We will modify pixels in outputData if they meet the edge criteria.
    const outputImageData = ctx.createImageData(canvas.width, canvas.height);
    const outputData = outputImageData.data;
    for (let i = 0; i < originalData.length; i++) {
        outputData[i] = originalData[i];
    }

    const width = canvas.width;
    const height = canvas.height;

    // Create a grayscale version of the image for gradient calculation.
    // This is a 1D array storing luminance values for each pixel.
    const grayData = new Uint8ClampedArray(width * height);
    for (let i = 0; i < originalData.length; i += 4) {
        const r = originalData[i];
        const g = originalData[i+1];
        const b = originalData[i+2];
        // Standard luminance calculation (coefficients for NTSC)
        const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
        grayData[i / 4] = luminance; // Store luminance in the 1D grayData array
    }

    // Parse the input color string.
    // The colors are expected in the order: N, NE, E, SE, S, SW, W, NW.
    const defaultColorObjects = [
        {r:255,g:255,b:0},   // N: Yellow
        {r:255,g:165,b:0},   // NE: Orange
        {r:255,g:0,b:0},     // E: Red
        {r:255,g:0,b:255},   // SE: Magenta
        {r:128,g:0,b:128},   // S: Purple
        {r:0,g:0,b:255},     // SW: Blue
        {r:0,g:255,b:255},   // W: Cyan
        {r:144,g:238,b:144}  // NW: Light Green (#90EE90)
    ];
    const inputHexColors = colorsStr.split(',');
    const parsedColors = [];
    for (let c = 0; c < 8; c++) {
        let colorObj = null;
        if (inputHexColors[c]) {
            colorObj = hexToRgb(inputHexColors[c].trim());
        }
        // Use provided color if valid, otherwise fall back to default for that direction
        parsedColors.push(colorObj || defaultColorObjects[c]);
    }
    
    // Iterate through pixels to apply Sobel operator and color based on gradient.
    // Skip border pixels (1 pixel margin) as Sobel requires a 3x3 neighborhood.
    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            // Apply Sobel X kernel
            const Gx = (
                grayData[(y-1)*width + (x+1)] - grayData[(y-1)*width + (x-1)] +
                2 * (grayData[y*width + (x+1)] - grayData[y*width + (x-1)]) +
                grayData[(y+1)*width + (x+1)] - grayData[(y+1)*width + (x-1)]
            );
            // Apply Sobel Y kernel
            const Gy = (
                grayData[(y+1)*width + (x-1)] + 2 * grayData[(y+1)*width + x] + grayData[(y+1)*width + (x+1)] -
                (grayData[(y-1)*width + (x-1)] + 2 * grayData[(y-1)*width + x] + grayData[(y-1)*width + (x+1)])
            );

            const magnitude = Math.sqrt(Gx * Gx + Gy * Gy);
            const pixelIndex = (y * width + x) * 4; // Index for the current pixel in outputData

            if (magnitude > threshold) {
                // Calculate gradient angle in degrees (-180 to 180)
                let angle = Math.atan2(Gy, Gx) * 180 / Math.PI; 
                
                // Normalize angle to [0, 360), where 0 is East, 90 is South, 180 is West, 270 is North
                if (angle < 0) {
                    angle += 360;
                }

                let mappedColor;
                // Determine compass direction based on angle and select corresponding color.
                // Colors in parsedColors are for: N, NE, E, SE, S, SW, W, NW
                if (angle > 247.5 && angle <= 292.5) mappedColor = parsedColors[0];       // N (270 deg sector)
                else if (angle > 292.5 && angle <= 337.5) mappedColor = parsedColors[1]; // NE (315 deg sector)
                else if (angle > 337.5 || angle <= 22.5) mappedColor = parsedColors[2];   // E (0 deg sector)
                else if (angle > 22.5 && angle <= 67.5) mappedColor = parsedColors[3];    // SE (45 deg sector)
                else if (angle > 67.5 && angle <= 112.5) mappedColor = parsedColors[4];   // S (90 deg sector)
                else if (angle > 112.5 && angle <= 157.5) mappedColor = parsedColors[5]; // SW (135 deg sector)
                else if (angle > 157.5 && angle <= 202.5) mappedColor = parsedColors[6]; // W (180 deg sector)
                else if (angle > 202.5 && angle <= 247.5) mappedColor = parsedColors[7]; // NW (225 deg sector)
                else { 
                     // This case should ideally not be reached if angle logic is correct
                    mappedColor = defaultColorObjects[0]; // Default to N color if something unexpected occurs
                }
                
                // Set the pixel in outputData to the determined color
                outputData[pixelIndex]   = mappedColor.r;
                outputData[pixelIndex+1] = mappedColor.g;
                outputData[pixelIndex+2] = mappedColor.b;
                outputData[pixelIndex+3] = 255; // Make edge pixels fully opaque
            }
            // else: magnitude is below threshold, pixel is not considered a strong edge.
            // Original pixel color (already in outputData) is preserved.
        }
    }

    // Put the modified image data back onto the canvas
    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 Compass Rose Filter Effect tool allows users to apply a filter to their images that enhances edge detection and overlays compass direction colors based on the angle of the edges. This tool can be useful for artists, graphic designers, or anyone looking to create visually striking images that highlight directional elements. Users can customize the color scheme for each compass direction, making it versatile for different artistic styles and preferences.

Leave a Reply

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