Please bookmark this page to avoid losing your image tool!

Image African Mask 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.
function processImage(originalImg, paletteColorsStr = "85,50,25;180,120,40;150,80,30;210,105,30;0,0,0;240,220,180", outlineColorStr = "0,0,0", outlineThickness = 2) {

    // 1. Parse and validate parameters
    let parsedPalette = [];
    if (paletteColorsStr && typeof paletteColorsStr === 'string' && paletteColorsStr.trim() !== "") {
        parsedPalette = paletteColorsStr.split(';').map(cStr =>
            cStr.split(',').map(nStr => Number(nStr.trim()))
        ).filter(cArr => cArr.length === 3 && cArr.every(n => !isNaN(n) && n >= 0 && n <= 255));
    }
    
    if (parsedPalette.length === 0) { // Fallback to a hardcoded default
        parsedPalette = [[85,50,25], [180,120,40], [150,80,30], [210,105,30], [0,0,0], [240,220,180]];
    }

    let parsedOutlineColor = [];
    if (outlineColorStr && typeof outlineColorStr === 'string' && outlineColorStr.trim() !== "") {
        const tempColor = outlineColorStr.split(',').map(nStr => Number(nStr.trim()));
         if (tempColor.length === 3 && tempColor.every(n => !isNaN(n) && n >= 0 && n <= 255)) {
            parsedOutlineColor = tempColor;
         }
    }
    if (parsedOutlineColor.length === 0) { // Fallback
        parsedOutlineColor = [0,0,0]; // Default to black
    }
    
    let tempThickness = Number(outlineThickness);
    if (isNaN(tempThickness)) {
        // If originalImg parameter for outlineThickness (e.g. from a text input) was not a number, use default.
        // Note: The function signature gives default `2` if `outlineThickness` is undefined.
        // This handles `null`, `""` (becomes 0 from Number()), or explicitly non-numeric strings.
        tempThickness = 2; 
    }
    const finalOutlineThickness = Math.max(0, Math.round(tempThickness));


    // 2. Create canvas and draw original image
    const canvas = document.createElement('canvas');
    
    // Ensure originalImg is loaded and has dimensions
    if (!originalImg || typeof originalImg.width !== 'number' || typeof originalImg.height !== 'number' || originalImg.width === 0 || originalImg.height === 0) {
        console.error("Invalid or unloaded image provided.");
        // Return a small, empty canvas or throw an error
        canvas.width = 1;
        canvas.height = 1;
        return canvas;
    }

    canvas.width = originalImg.width;
    canvas.height = originalImg.height;
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    ctx.drawImage(originalImg, 0, 0);

    // 3. Get image data
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Error getting ImageData. If image is cross-origin, ensure it has 'Access-Control-Allow-Origin' header.", e);
        return canvas; // Return canvas with original image if processing is not possible
    }
    
    const data = imageData.data;
    const W = canvas.width;
    const H = canvas.height;

    // 4. Create buffer for quantized image
    const quantizedData = new Uint8ClampedArray(data.length);

    // 5. Color Quantization Pass
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i+1];
        const b = data[i+2];
        const a = data[i+3];

        if (a === 0) { // Preserve fully transparent pixels
            quantizedData[i]   = 0;
            quantizedData[i+1] = 0;
            quantizedData[i+2] = 0;
            quantizedData[i+3] = 0;
            continue;
        }
        
        let minDist = Infinity;
        let chosenColor = parsedPalette[0]; 
        for (const pColor of parsedPalette) {
            const dist = Math.sqrt(
                Math.pow(r - pColor[0], 2) +
                Math.pow(g - pColor[1], 2) +
                Math.pow(b - pColor[2], 2)
            );
            if (dist < minDist) {
                minDist = dist;
                chosenColor = pColor;
            }
        }
        quantizedData[i]   = chosenColor[0];
        quantizedData[i+1] = chosenColor[1];
        quantizedData[i+2] = chosenColor[2];
        quantizedData[i+3] = a; 
    }

    if (finalOutlineThickness === 0) { // If no outline, just put quantized data and return
        ctx.putImageData(new ImageData(quantizedData, W, H), 0, 0);
        return canvas;
    }

    // 6. Create Edge Map (based on quantizedData)
    const edgeMap = new Array(W * H).fill(false);
    for (let y = 0; y < H; y++) {
        for (let x = 0; x < W; x++) {
            const idx = (y * W + x) * 4;
            if (quantizedData[idx+3] === 0) continue; // Ignore transparent pixels for edge source

            const rC = quantizedData[idx];
            const gC = quantizedData[idx+1];
            const bC = quantizedData[idx+2];
            let isBaseEdge = false;

            for (let dy = -1; dy <= 1; dy++) {
                for (let dx = -1; dx <= 1; dx++) {
                    if (dx === 0 && dy === 0) continue;
                    const nx = x + dx;
                    const ny = y + dy;

                    if (nx >= 0 && nx < W && ny >= 0 && ny < H) {
                        const nidx = (ny * W + nx) * 4;
                        if (quantizedData[nidx+3] === 0) { // Edge with a transparent area
                            isBaseEdge = true;
                            break;
                        }
                        if (quantizedData[nidx] !== rC || quantizedData[nidx+1] !== gC || quantizedData[nidx+2] !== bC) {
                            isBaseEdge = true;
                            break;
                        }
                    } else { // Pixel is at the image boundary, consider it an edge
                        isBaseEdge = true;
                        break;
                    }
                }
                if (isBaseEdge) break;
            }
            if (isBaseEdge) {
                edgeMap[y * W + x] = true;
            }
        }
    }

    // 7. Final Output Pass (applying outlines based on edgeMap and thickness)
    const outputData = new Uint8ClampedArray(data.length);
    const radius = Math.floor((finalOutlineThickness - 1) / 2); 

    for (let y = 0; y < H; y++) {
        for (let x = 0; x < W; x++) {
            const pixelIdx = (y * W + x) * 4;
            
            if (quantizedData[pixelIdx+3] === 0) { // Preserve transparency
                outputData[pixelIdx] = 0; outputData[pixelIdx+1] = 0; outputData[pixelIdx+2] = 0; outputData[pixelIdx+3] = 0;
                continue;
            }
            
            let applyOutline = false;
            // Check neighborhood in edgeMap for an edge pixel
            // The window size is (2*radius+1) x (2*radius+1), which corresponds to finalOutlineThickness for odd values
            for (let ky = -radius; ky <= radius; ky++) {
                for (let kx = -radius; kx <= radius; kx++) {
                    const currentX = x + kx;
                    const currentY = y + ky;

                    if (currentX >= 0 && currentX < W && currentY >= 0 && currentY < H) {
                        if (edgeMap[currentY * W + currentX]) {
                            applyOutline = true;
                            break;
                        }
                    }
                }
                if (applyOutline) break;
            }
            
            if (applyOutline) {
                outputData[pixelIdx]     = parsedOutlineColor[0];
                outputData[pixelIdx + 1] = parsedOutlineColor[1];
                outputData[pixelIdx + 2] = parsedOutlineColor[2];
                outputData[pixelIdx + 3] = quantizedData[pixelIdx + 3]; 
            } else {
                outputData[pixelIdx]     = quantizedData[pixelIdx];
                outputData[pixelIdx + 1] = quantizedData[pixelIdx + 1];
                outputData[pixelIdx + 2] = quantizedData[pixelIdx + 2];
                outputData[pixelIdx + 3] = quantizedData[pixelIdx + 3];
            }
        }
    }

    // 8. Put manipulated data back to canvas
    ctx.putImageData(new ImageData(outputData, W, H), 0, 0);

    // 9. Return canvas
    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 African Mask Filter Effect Tool allows users to apply a unique filter effect to their images, simulating the style of traditional African masks. By utilizing customizable color palettes, outline colors, and thickness settings, users can transform their images into art that emphasizes bold colors and distinctive outlines. This tool is ideal for artists, graphic designers, or anyone looking to create striking visuals for social media, digital art projects, or personal use. It can enhance photos for creative storytelling, art projects, or just for fun artistic expression.

Leave a Reply

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