Please bookmark this page to avoid losing your image tool!

Image Art Deco Filter Effect 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.
async function processImage(originalImg, posterizeLevels = 4, borderFactor = 1.0, paletteString = "#000000,#1E2D2F,#C09F58,#EAE0C8") {

    // Helper function to parse color strings (hex, rgb, color names) into an {r, g, b, a} object.
    function parseColor(colorStr) {
        if (typeof colorStr !== 'string' || colorStr.trim() === "") {
            // console.warn(`Invalid color string input: "${colorStr}". Defaulting to black.`);
            return { r: 0, g: 0, b: 0, a: 255 };
        }

        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = 1;
        tempCanvas.height = 1;
        const tempCtx = tempCanvas.getContext('2d');
        
        if (!tempCtx) { // Fallback if context cannot be created for some rare reason
            // console.warn("Temporary canvas context for color parsing failed. Defaulting color to black.");
            return { r: 0, g: 0, b: 0, a: 255 };
        }

        tempCtx.fillStyle = '#000'; // Initialize with a known opaque color
        try {
            tempCtx.fillStyle = colorStr.trim();
        } catch (e) {
            // console.warn(`Error setting fillStyle for color string: "${colorStr}". Defaulting to black.`, e);
            return { r: 0, g: 0, b: 0, a: 255 };
        }
        
        const computedColor = tempCtx.fillStyle; // Will be #rrggbb or rgba(r,g,b,a)

        if (computedColor.startsWith('#')) {
            let hex = computedColor;
            let r = 0, g = 0, b = 0;
            if (hex.length === 4) { // #RGB
                r = parseInt(hex[1] + hex[1], 16);
                g = parseInt(hex[2] + hex[2], 16);
                b = parseInt(hex[3] + hex[3], 16);
            } else if (hex.length === 7) { // #RRGGBB
                r = parseInt(hex.substring(1, 3), 16);
                g = parseInt(hex.substring(3, 5), 16);
                b = parseInt(hex.substring(5, 7), 16);
            } else {
                // console.warn(`Could not parse hex color: ${colorStr} (computed: ${computedColor}). Defaulting to black.`);
                return { r: 0, g: 0, b: 0, a: 255 };
            }
            return { r, g, b, a: 255 };
        } else if (computedColor.startsWith('rgb')) { // rgb(r, g, b) or rgba(r, g, b, a)
            const parts = computedColor.substring(computedColor.indexOf('(') + 1, computedColor.lastIndexOf(')')).split(/,\s*/);
            const rVal = parseInt(parts[0], 10);
            const gVal = parseInt(parts[1], 10);
            const bVal = parseInt(parts[2], 10);
            const aVal = parts.length > 3 ? Math.round(parseFloat(parts[3]) * 255) : 255;
            if (isNaN(rVal) || isNaN(gVal) || isNaN(bVal) || isNaN(aVal)) {
                // console.warn(`Could not parse rgb/rgba color: ${colorStr} (computed: ${computedColor}). Defaulting to black.`);
                return { r: 0, g: 0, b: 0, a: 255 };
            }
            return { r: rVal, g: gVal, b: bVal, a: aVal };
        }
        
        // console.warn(`Could not parse color: ${colorStr} (computed: ${computedColor}). Defaulting to black.`);
        return { r: 0, g: 0, b: 0, a: 255 };
    }

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

    if (!ctx) {
        // console.error("Failed to get 2D context for processing image.");
        // Return a very small canvas or handle error appropriately
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 1; errorCanvas.height = 1;
        return errorCanvas;
    }
    
    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        return canvas; // Return empty canvas if image has no dimensions
    }

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        // console.error("Could not get ImageData (e.g., CORS issue if image from another domain without CORS headers).", e);
        // Draw a placeholder or return the original canvas
        ctx.clearRect(0,0,canvas.width, canvas.height);
        ctx.fillStyle = "grey";
        ctx.fillRect(0,0,canvas.width, canvas.height);
        ctx.fillStyle = "black";
        ctx.textAlign = "center";
        ctx.fillText("Error processing image", canvas.width/2, canvas.height/2);
        return canvas;
    }
    const data = imageData.data;

    const rawPalette = paletteString.split(',');
    let artDecoPaletteRgb = rawPalette.map(colorStr => parseColor(colorStr.trim()));
    
    // Filter out any malformed color objects (though parseColor defaults to black)
    artDecoPaletteRgb = artDecoPaletteRgb.filter(c => 
        c && typeof c.r === 'number' && typeof c.g === 'number' && typeof c.b === 'number' && typeof c.a === 'number'
    );

    if (artDecoPaletteRgb.length === 0) {
        artDecoPaletteRgb.push(parseColor("#000000")); // Default to black
        artDecoPaletteRgb.push(parseColor("#FFFFFF")); // and white, if palette is empty or fully unparseable
    }

    const numQuantLevels = Math.max(2, Math.floor(posterizeLevels)); // At least 2 levels (e.g., binary)
    const R_LUMINANCE = 0.299;
    const G_LUMINANCE = 0.587;
    const B_LUMINANCE = 0.114;

    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];

        // 1. Convert to grayscale
        const gray = R_LUMINANCE * r + G_LUMINANCE * g + B_LUMINANCE * b;

        // 2. Posterize grayscale value
        const grayStep = 255 / (numQuantLevels - 1);
        const posterizedGray = Math.round(Math.round(gray / grayStep) * grayStep);
        
        // 3. Map posterized gray to a palette color
        // Normalized gray (0 to 1) determines which color from the palette to pick.
        // Palette is assumed to be ordered (e.g., dark to light by user).
        const normalizedGray = Math.min(1, Math.max(0, posterizedGray / 255)); 

        let paletteIndex;
        if (artDecoPaletteRgb.length === 1) {
            paletteIndex = 0;
        } else {
            // Map normalizedGray to an index in the palette array
            // The (artDecoPaletteRgb.length - 1e-9) ensures that when normalizedGray is 1,
            // it correctly maps to the last index after Math.floor.
            paletteIndex = Math.floor(normalizedGray * (artDecoPaletteRgb.length - 1e-9));
            paletteIndex = Math.min(paletteIndex, artDecoPaletteRgb.length - 1); // Clamp index
        }
        
        const chosenColor = artDecoPaletteRgb[paletteIndex];

        data[i] = chosenColor.r;
        data[i + 1] = chosenColor.g;
        data[i + 2] = chosenColor.b;
        // Alpha (data[i+3]) is preserved from original image
    }

    ctx.putImageData(imageData, 0, 0);

    // 4. Add optional Art Deco style border
    if (borderFactor > 0 && canvas.width > 0 && canvas.height > 0) {
        let borderColorRgb = { r: 0, g: 0, b: 0 }; // Default border to black

        if (artDecoPaletteRgb.length > 0) {
            // Prefer a very dark color from the palette if available
            let foundDark = artDecoPaletteRgb.find(c => (R_LUMINANCE * c.r + G_LUMINANCE * c.g + B_LUMINANCE * c.b) < 64); // Arbitrary "dark" threshold 
            if (foundDark) {
                borderColorRgb = foundDark;
            } else { // Otherwise, use the color with the overall minimum luminance from the palette
                borderColorRgb = artDecoPaletteRgb.reduce((darkest, current) => {
                    const lumDarkest = R_LUMINANCE * darkest.r + G_LUMINANCE * darkest.g + B_LUMINANCE * darkest.b;
                    const lumCurrent = R_LUMINANCE * current.r + G_LUMINANCE * current.g + B_LUMINANCE * current.b;
                    return lumCurrent < lumDarkest ? current : darkest;
                }, artDecoPaletteRgb[0]);
            }
        }
        
        ctx.strokeStyle = `rgb(${borderColorRgb.r},${borderColorRgb.g},${borderColorRgb.b})`;
        
        const minDimension = Math.min(canvas.width, canvas.height);
        let baseLineWidth = Math.max(1, minDimension * 0.01); // Base thickness: 1% of min dimension
        let lineWidth = Math.max(1, baseLineWidth * borderFactor);
        lineWidth = Math.min(lineWidth, Math.max(1, minDimension * 0.1)); // Cap max border width (e.g., 10% of min dimension)
        ctx.lineWidth = lineWidth;

        // Draw outer border slightly inset so it's fully visible
        ctx.strokeRect(lineWidth / 2, lineWidth / 2, canvas.width - lineWidth, canvas.height - lineWidth);

        // Add an inner border line if borderFactor is high enough and space allows
        const innerBorderFactorThreshold = 1.5; 
        if (borderFactor >= innerBorderFactorThreshold) {
            const inset = lineWidth * 2.0; // Inset for the second border line
            if (canvas.width > inset * 2 + lineWidth && canvas.height > inset * 2 + lineWidth) { // Ensure space for inner border
                 ctx.lineWidth = Math.max(1, lineWidth * 0.5); // Inner border can be thinner
                 ctx.strokeRect(inset, inset, canvas.width - 2 * inset, canvas.height - 2 * inset);
            }
        }
    }

    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 Art Deco Filter Effect Application allows users to enhance their images with a distinct Art Deco style. By applying a custom color palette, posterizing the image into a limited set of brightness levels, and optionally adding a decorative border, this tool creates striking visual effects ideal for artistic projects, graphic design, and social media posts. Users can customize the filter settings, such as the number of posterization levels and border width, to achieve a unique aesthetic that resembles the bold and geometric designs characteristic of the Art Deco movement.

Leave a Reply

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