Please bookmark this page to avoid losing your image tool!

Image Byzantine Icon 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, posterizeLevels = 5, outlineColorStr = "0,0,0", warmth = 20, saturationBoost = 1.1) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    canvas.width = originalImg.width;
    canvas.height = originalImg.height;

    // Helper to clamp values
    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    // Ensure posterizeLevels is at least 2
    const actualPosterizeLevels = Math.max(2, Math.floor(posterizeLevels));

    // Helper: Posterize a single color channel
    function posterizeChannel(value, levels) {
        if (levels <= 1) return value; // Should not happen due to Math.max(2,...)
        const step = 255 / (levels - 1);
        return Math.round(value / step) * step;
    }

    // Helper: RGB to HSL conversion
    // r, g, b in [0, 255]
    // Returns h, s, l in [0, 1]
    function rgbToHsl(r, g, b) {
        r /= 255; g /= 255; b /= 255;
        const max = Math.max(r, g, b), min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;

        if (max === min) {
            h = s = 0; // achromatic
        } else {
            const d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            h /= 6;
        }
        return { h, s, l };
    }

    // Helper: HSL to RGB conversion
    // h, s, l in [0, 1]
    // Returns r, g, b in [0, 255]
    function hslToRgb(h, s, l) {
        let r, g, b;
        if (s === 0) {
            r = g = b = l; // achromatic
        } else {
            function hue2rgb(p, q, t) {
                if (t < 0) t += 1;
                if (t > 1) t -= 1;
                if (t < 1 / 6) return p + (q - p) * 6 * t;
                if (t < 1 / 2) return q;
                if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                return p;
            }
            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            const p = 2 * l - q;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }
        return {
            r: Math.round(r * 255),
            g: Math.round(g * 255),
            b: Math.round(b * 255)
        };
    }
    
    // Parse outlineColorStr
    const [oR, oG, oB] = outlineColorStr.split(',').map(s => parseInt(s.trim(), 10));

    // Draw original image to canvas to get its pixel data
    ctx.drawImage(originalImg, 0, 0);
    const originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const originalData = originalImageData.data;

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

    // This will store only posterized values, without tint/saturation, for edge detection
    const posterizedOnlyData = new Uint8ClampedArray(originalData.length);

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

    // Step 1: Posterize, apply warmth, and saturation. Store results.
    for (let i = 0; i < originalData.length; i += 4) {
        let r = originalData[i];
        let g = originalData[i+1];
        let b = originalData[i+2];
        const a = originalData[i+3];

        // Posterize
        const pr = posterizeChannel(r, actualPosterizeLevels);
        const pg = posterizeChannel(g, actualPosterizeLevels);
        const pb = posterizeChannel(b, actualPosterizeLevels);

        posterizedOnlyData[i]   = pr;
        posterizedOnlyData[i+1] = pg;
        posterizedOnlyData[i+2] = pb;
        posterizedOnlyData[i+3] = a; // Preserve alpha

        // Apply warmth to posterized colors
        let warmedR = clamp(pr + warmth, 0, 255);
        let warmedG = clamp(pg + warmth * 0.7, 0, 255); // Less warmth for green
        let warmedB = clamp(pb - warmth * 0.3, 0, 255); // Cool down blue slightly

        // Apply saturation boost
        let hsl = rgbToHsl(warmedR, warmedG, warmedB);
        hsl.s = clamp(hsl.s * saturationBoost, 0, 1);
        let finalRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
        
        outputData[i]   = finalRgb.r;
        outputData[i+1] = finalRgb.g;
        outputData[i+2] = finalRgb.b;
        outputData[i+3] = a; // Preserve alpha
    }

    // Step 2: Detect edges from the posterizedOnlyData
    const edgeMap = new Uint8Array(width * height); // 0 for no edge, 1 for edge
    const edgeThreshold = 1; // Any difference in posterized color value indicates an edge

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const currentPixelIndex = (y * width + x) * 4;
            const r1 = posterizedOnlyData[currentPixelIndex];
            const g1 = posterizedOnlyData[currentPixelIndex + 1];
            const b1 = posterizedOnlyData[currentPixelIndex + 2];

            // Check right neighbor
            if (x < width - 1) {
                const rightPixelIndex = (y * width + (x + 1)) * 4;
                const r2 = posterizedOnlyData[rightPixelIndex];
                const g2 = posterizedOnlyData[rightPixelIndex + 1];
                const b2 = posterizedOnlyData[rightPixelIndex + 2];
                if (Math.abs(r1 - r2) > edgeThreshold || Math.abs(g1 - g2) > edgeThreshold || Math.abs(b1 - b2) > edgeThreshold) {
                    edgeMap[y * width + x] = 1;
                }
            }

            // Check bottom neighbor
            if (y < height - 1) {
                const bottomPixelIndex = ((y + 1) * width + x) * 4;
                const r2 = posterizedOnlyData[bottomPixelIndex];
                const g2 = posterizedOnlyData[bottomPixelIndex + 1];
                const b2 = posterizedOnlyData[bottomPixelIndex + 2];
                 if (Math.abs(r1 - r2) > edgeThreshold || Math.abs(g1 - g2) > edgeThreshold || Math.abs(b1 - b2) > edgeThreshold) {
                    edgeMap[y * width + x] = 1;
                }
            }
        }
    }

    // Step 3: Draw the processed base image (posterized, warmed, saturated)
    ctx.putImageData(outputImageData, 0, 0);

    // Step 4: Draw outlines on top
    ctx.fillStyle = `rgb(${oR},${oG},${oB})`;
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            if (edgeMap[y * width + x] === 1) {
                ctx.fillRect(x, y, 1, 1); // Draw a 1px outline
            }
        }
    }

    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 Byzantine Icon Filter Effect Tool allows users to apply a distinctive artistic effect to their images, emulating the visual style of Byzantine icons. This tool enables you to posterize images into a specified number of color levels, adjust their warmth and saturation, and add an outline effect for enhanced edge definition. Perfect for artists, graphic designers, or anyone looking to create stylized images, this tool can transform photos into unique artwork suitable for various creative projects, including posters, social media graphics, and more.

Leave a Reply

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