Please bookmark this page to avoid losing your image tool!

Image Mimeograph 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.
function processImage(originalImg, inkColorStr = "black", paperColorStr = "white", ditherScale = 4, brightnessAdjust = 0, grainAmount = 0.1) {

    // Helper function to parse CSS color strings (e.g., "red", "#FF0000", "rgb(255,0,0)")
    // into an [R, G, B, A] array.
    function _parseColor(colorStr) {
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = 1;
        tempCanvas.height = 1;
        // Use { willReadFrequently: true } for potential performance improvement if available/needed
        const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); 
        
        tempCtx.fillStyle = colorStr; // Assign color
        tempCtx.fillRect(0, 0, 1, 1); // Draw a 1x1 pixel rectangle
        
        // Get the pixel data; .data will be a Uint8ClampedArray [R, G, B, A]
        const pixelData = tempCtx.getImageData(0, 0, 1, 1).data;
        return [pixelData[0], pixelData[1], pixelData[2], pixelData[3]];
    }

    const parsedInkColor = _parseColor(inkColorStr);
    const parsedPaperColor = _parseColor(paperColorStr);

    // Bayer matrices definition
    // M(2) is the base 2x2 matrix.
    // M(4), M(8), etc., can be generated recursively or predefined.
    const _bayerMatrices = {
        2: [[0, 2], [3, 1]],
        4: [
            [0,  8,  2, 10],
            [12, 4, 14,  6],
            [3, 11,  1,  9],
            [15, 7, 13,  5]
        ]
        // 8x8 matrix will be generated if needed and not already present
    };
    
    // Helper function to generate the next power-of-2 Bayer matrix from the previous one.
    // E.g., generates 4x4 from 2x2, or 8x8 from 4x4.
    function _generateNextBayerMatrix(prevMatrix) {
        const prevSize = prevMatrix.length;
        const newSize = prevSize * 2;
        const newMatrix = Array(newSize).fill(null).map(() => Array(newSize).fill(0));

        for (let r = 0; r < prevSize; r++) {
            for (let c = 0; c < prevSize; c++) {
                const val = prevMatrix[r][c];
                newMatrix[r][c]                       = 4 * val + 0;
                newMatrix[r][c + prevSize]            = 4 * val + 2;
                newMatrix[r + prevSize][c]            = 4 * val + 3;
                newMatrix[r + prevSize][c + prevSize] = 4 * val + 1;
            }
        }
        return newMatrix;
    }

    // Ensure 8x8 matrix is available if requested or as part of progression
    if (!_bayerMatrices[8] && _bayerMatrices[4]) {
        _bayerMatrices[8] = _generateNextBayerMatrix(_bayerMatrices[4]);
    }
    
    let currentBayerMatrix;
    let actualDitherPatternSize; // This will hold the dimension (e.g., 2, 4, or 8) of the chosen matrix

    // Validate ditherScale parameter and select the appropriate Bayer matrix
    if (_bayerMatrices[ditherScale]) {
        currentBayerMatrix = _bayerMatrices[ditherScale];
        actualDitherPatternSize = ditherScale;
    } else {
        // Default to 4x4 if an unsupported/invalid scale is provided
        console.warn(`Invalid ditherScale value: ${ditherScale}. Using default 4x4 matrix.`);
        currentBayerMatrix = _bayerMatrices[4];
        actualDitherPatternSize = 4; 
    }
    
    // Setup main canvas
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    
    // Use naturalWidth/Height if available (for <img> elements), otherwise width/height
    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

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

    // Draw the original image onto the canvas
    ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    
    // Handle cases where image might not be loaded or has no dimensions
    if (imgWidth === 0 || imgHeight === 0) {
        console.warn("Image has zero width or height. Returning empty canvas.");
        return canvas; 
    }

    const imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    const data = imageData.data; // Pixel data: [R,G,B,A, R,G,B,A, ...]

    // Process each pixel
    for (let y = 0; y < imgHeight; y++) {
        for (let x = 0; x < imgWidth; x++) {
            const idx = (y * imgWidth + x) * 4; // Calculate index for the current pixel's R value
            
            const r = data[idx];
            const g = data[idx + 1];
            const b = data[idx + 2];
            // data[idx + 3] is the alpha, we'll overwrite it with ink/paper color's alpha

            // 1. Convert to grayscale (luminance method)
            let gray = 0.299 * r + 0.587 * g + 0.114 * b;

            // 2. Apply brightness adjustment
            gray += brightnessAdjust;

            // 3. Apply grain/noise
            if (grainAmount > 0) {
                // Generate noise between -(grainAmount*50) and +(grainAmount*50)
                const noise = (Math.random() - 0.5) * 2 * grainAmount * 50; 
                gray += noise;
            }
            
            // 4. Clamp gray value to 0-255 range
            gray = Math.max(0, Math.min(255, gray));

            // 5. Dithering using Bayer matrix
            // Determine which element of the Bayer matrix to use based on pixel position
            const bayerRow = y % actualDitherPatternSize;
            const bayerCol = x % actualDitherPatternSize;
            const bayerElement = currentBayerMatrix[bayerRow][bayerCol];
            
            // Normalize the Bayer matrix element and scale it to create a threshold
            // The (+0.5) helps in distributing thresholds more evenly
            const ditherThreshold = ((bayerElement + 0.5) / (actualDitherPatternSize * actualDitherPatternSize)) * 255;

            let targetColor;
            // If pixel's gray value is greater than the dither threshold, use paper color (lighter)
            // Otherwise, use ink color (darker)
            if (gray > ditherThreshold) {
                targetColor = parsedPaperColor;
            } else {
                targetColor = parsedInkColor;
            }

            // Set the new pixel color
            data[idx]     = targetColor[0]; // R
            data[idx + 1] = targetColor[1]; // G
            data[idx + 2] = targetColor[2]; // B
            data[idx + 3] = targetColor[3]; // A (use alpha from parsed ink/paper color)
        }
    }

    // Put the modified pixel data back onto the canvas
    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 Mimeograph Filter Effect Application allows users to apply a vintage mimeograph-style filter to their images. This tool transforms standard images into stylized artwork through processes like grayscale conversion, brightness adjustments, grain addition, and dithering using Bayer matrices. It is suitable for artists, designers, and anyone looking to create unique effects for presentations, social media posts, or digital art projects by adding a nostalgic touch to their images.

Leave a Reply

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