Please bookmark this page to avoid losing your image tool!

Image Screen Print Filter

(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, levels = 4, paletteStr = "") {
    const canvas = document.createElement('canvas');
    // Set willReadFrequently to true for potential performance improvements
    // if getImageData/putImageData are called often, though for a single pass it might not be critical.
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Use naturalWidth/Height if available (intrinsic dimensions), otherwise fallback to width/height
    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    if (imgWidth === 0 || imgHeight === 0) {
        console.error("Image has zero dimensions. Cannot process. Ensure image is loaded.");
        // Return an empty canvas (or handle error as appropriate)
        canvas.width = 0;
        canvas.height = 0;
        return canvas;
    }

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

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

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        // This can happen due to cross-origin restrictions if the image is from a different domain
        // and CORS headers are not set.
        console.error("Could not get image data. This may be due to cross-origin restrictions.", e);
        // Fallback: return the canvas with the original image drawn, but no filter applied.
        return canvas;
    }
    
    const data = imageData.data;

    let usePalette = false;
    let parsedPalette = [];

    if (paletteStr && typeof paletteStr === 'string' && paletteStr.trim() !== "") {
        const hexColors = paletteStr.split(',').map(c => c.trim());
        parsedPalette = hexColors.map(hex => {
            // Validate hex color format (e.g., #RRGGBB)
            if (!/^#[0-9A-F]{6}$/i.test(hex)) {
                console.warn(`Invalid hex color '${hex}' in palette. It should be in #RRGGBB format. Skipping.`);
                return null;
            }
            // Convert hex to RGB
            const r = parseInt(hex.slice(1, 3), 16);
            const g = parseInt(hex.slice(3, 5), 16);
            const b = parseInt(hex.slice(5, 7), 16);
            return { r, g, b };
        }).filter(c => c !== null); // Remove any nulls from invalid entries

        if (parsedPalette.length > 0) {
            usePalette = true;
        } else {
            console.warn("Palette string provided, but no valid colors were found. Falling back to 'levels'-based posterization.");
        }
    }

    if (usePalette) {
        // Posterize to the specified palette
        for (let i = 0; i < data.length; i += 4) {
            const r = data[i];
            const g = data[i + 1];
            const b = data[i + 2];
            // Alpha (data[i+3]) is preserved

            let minDistanceSq = Infinity;
            let closestColor = parsedPalette[0]; // Initialize with the first palette color

            for (const palColor of parsedPalette) {
                const dr = r - palColor.r;
                const dg = g - palColor.g;
                const db = b - palColor.b;
                // Use squared Euclidean distance for efficiency (avoids Math.sqrt)
                const distanceSq = dr * dr + dg * dg + db * db;

                if (distanceSq < minDistanceSq) {
                    minDistanceSq = distanceSq;
                    closestColor = palColor;
                }
                // Optimization: if an exact match is found, no need to check further for this pixel
                if (minDistanceSq === 0) {
                    break;
                }
            }
            data[i] = closestColor.r;
            data[i + 1] = closestColor.g;
            data[i + 2] = closestColor.b;
        }
    } else {
        // Posterize based on the number of levels
        let numLevelsParam = Number(levels);
        let actualLevels;

        if (isNaN(numLevelsParam)) {
            // The 'levels' parameter from the function signature has a default of 4.
            const functionDefaultLevels = 4; 
            console.warn(`Invalid 'levels' parameter: '${levels}'. Using default of ${functionDefaultLevels}.`);
            actualLevels = functionDefaultLevels;
        } else if (numLevelsParam <= 0) {
            console.warn(`'levels' parameter (${levels}) must be positive. Using a minimum of 2 levels.`);
            actualLevels = 2;
        } else {
            // Ensure actualLevels is at least 2 (e.g., for black and white per channel)
            // Math.round(0.x) = 0, Math.round(1.x) where x<5 = 1.
            // So Math.max(2, round(positive_value)) ensures it's at least 2.
            actualLevels = Math.max(2, Math.round(numLevelsParam));
        }
        
        // actualLevels is now guaranteed to be an integer >= 2.
        // The factor determines the size of each color step.
        // (actualLevels - 1) gives the number of intervals.
        const factor = 255 / (actualLevels - 1);

        for (let i = 0; i < data.length; i += 4) {
            data[i] = Math.round(data[i] / factor) * factor;
            data[i + 1] = Math.round(data[i + 1] / factor) * factor;
            data[i + 2] = Math.round(data[i + 2] / factor) * factor;
            // Alpha (data[i+3]) is preserved

            // Clamp values to 0-255, though Math.round should keep them in range if input is 0-255.
            // This is an extra safety, good practice.
            data[i] = Math.min(255, Math.max(0, data[i]));
            data[i+1] = Math.min(255, Math.max(0, data[i+1]));
            data[i+2] = Math.min(255, Math.max(0, data[i+2]));
        }
    }

    ctx.putImageData(imageData, 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 Screen Print Filter is a web-based tool that allows users to apply a screen printing effect to their images. This tool can alter images by reducing their color depth, either by using a customizable palette of colors or by specifying the number of color levels to posterize the image. Real-world use cases for this tool include creating stylistic prints for textiles, developing graphics for merchandise, enhancing illustrations for creative projects, or crafting social media visuals that stand out due to their unique color effects.

Leave a Reply

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