Please bookmark this page to avoid losing your image tool!

Image To Conway’s Game Of Life Pattern Converter

(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, thresholdStr = "128", cellSizeStr = "5", liveColor = "black", deadColor = "white", invertStr = "false") {

    // 0. Initial Image Sanity Check
    // Check if originalImg is valid and has dimensions.
    if (!originalImg || typeof originalImg.width === 'undefined' || originalImg.width === 0 || originalImg.height === 0) {
        console.error("processImage: Invalid image provided or image not loaded.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 250; // A fixed size for this specific error
        errorCanvas.height = 100;
        const ctx = errorCanvas.getContext('2d');
        ctx.fillStyle = '#EEEEEE'; // Light gray background
        ctx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        ctx.fillStyle = 'red';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = '14px Arial';
        ctx.fillText("Invalid or Empty Image Provided.", errorCanvas.width / 2, errorCanvas.height / 2);
        return errorCanvas;
    }

    // 1. Parameter Parsing and Validation
    let threshold = parseInt(thresholdStr, 10);
    if (isNaN(threshold) || threshold < 0 || threshold > 255) {
        threshold = 128; // Default threshold if parsing fails or out of range
    }

    let cellSize = parseInt(cellSizeStr, 10);
    if (isNaN(cellSize) || cellSize < 1) {
        cellSize = 5; // Default cell size if parsing fails or less than 1
    }

    // liveColor and deadColor parameters are strings, defaults provided in signature.

    let invert = false; // Default inversion state
    if (typeof invertStr === 'string') {
        const lowerInv = invertStr.toLowerCase();
        invert = lowerInv === 'true' || lowerInv === '1';
    } else if (typeof invertStr === 'number') { // Handle if a number (0 or 1) is passed
        invert = invertStr === 1;
    }
    // Note: Default string 'false' for invertStr correctly results in invert = false.

    // 2. Prepare Input Canvas to Access PixelData
    const inputCanvas = document.createElement('canvas');
    inputCanvas.width = originalImg.width;
    inputCanvas.height = originalImg.height;
    // Add willReadFrequently hint for potential performance optimization
    const inputCtx = inputCanvas.getContext('2d', { willReadFrequently: true });

    if (!inputCtx) {
        // This should rarely happen in modern browsers for reasonable canvas sizes
        console.error("processImage: Could not get 2D context for input canvas.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 250; errorCanvas.height = 100;
        const ctx = errorCanvas.getContext('2d');
        ctx.fillStyle = '#EEEEEE'; ctx.fillRect(0, 0, 250, 100);
        ctx.fillStyle = 'red'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        ctx.font = '14px Arial'; ctx.fillText("Canvas Context Error (Input)", 125, 50);
        return errorCanvas;
    }
    
    inputCtx.drawImage(originalImg, 0, 0);
    
    let imageData;
    try {
        imageData = inputCtx.getImageData(0, 0, originalImg.width, originalImg.height);
    } catch (e) {
        // This error often occurs due to CORS restrictions on cross-origin images
        console.error("processImage: Could not get image data. This can happen with cross-origin images if not CORS-enabled.", e);
        const errorCanvas = document.createElement('canvas');
        // Make error canvas potentially large enough to display message, using original image size as reference
        errorCanvas.width = Math.max(350, originalImg.width); 
        errorCanvas.height = Math.max(150, originalImg.height);
        const ctx = errorCanvas.getContext('2d');
        ctx.fillStyle = '#EEEEEE';
        ctx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        ctx.fillStyle = 'red';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = 'bold 13px Arial';
        const lines = [
            "Error: Could not access image pixels.",
            "This is common with cross-origin images.",
            "Ensure image is from the same domain",
            "or that the server provides CORS headers."
        ];
        const lineHeight = 18;
        let textY = errorCanvas.height / 2 - (lines.length - 1) * lineHeight / 2;
        for (const line of lines) {
            ctx.fillText(line, errorCanvas.width / 2, textY);
            textY += lineHeight;
        }
        return errorCanvas;
    }
    const data = imageData.data; // Pixel data array (R,G,B,A, R,G,B,A, ...)

    // 3. Calculate Grid Dimensions for the Game of Life Pattern
    const gridWidth = Math.floor(originalImg.width / cellSize);
    const gridHeight = Math.floor(originalImg.height / cellSize);

    if (gridWidth === 0 || gridHeight === 0) {
        // This happens if image is smaller than cellSize in either dimension
        console.warn("processImage: Image is too small for the given cell size, or cell size is too large.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = Math.max(300, originalImg.width);
        errorCanvas.height = Math.max(120, originalImg.height);
        const ctx = errorCanvas.getContext('2d');
        ctx.fillStyle = '#EEEEEE';
        ctx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        ctx.fillStyle = 'darkorange'; // Warning color
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = 'bold 14px Arial';
        ctx.fillText('Image too small for selected cell size.', errorCanvas.width / 2, errorCanvas.height / 2 - 10);
        ctx.font = '12px Arial';
        ctx.fillText(`(Image: ${originalImg.width}x${originalImg.height}px, Cell: ${cellSize}x${cellSize}px)`, errorCanvas.width / 2, errorCanvas.height / 2 + 12);
        return errorCanvas;
    }

    // 4. Initialize Game of Life Grid: Determine Live/Dead State for Each Cell
    const initialGrid = []; // 2D array to store cell states (0 for dead, 1 for live)

    for (let gy = 0; gy < gridHeight; gy++) { // Iterate through grid rows
        initialGrid[gy] = [];
        for (let gx = 0; gx < gridWidth; gx++) { // Iterate through grid columns
            let sumLuminance = 0;
            let pixelCount = 0;
            
            // Define the image region corresponding to the current grid cell
            const startX = gx * cellSize;
            const startY = gy * cellSize;
            // Ensure sampling bounds do not exceed original image dimensions
            const endX = Math.min(startX + cellSize, originalImg.width);
            const endY = Math.min(startY + cellSize, originalImg.height);

            // Calculate average luminance for the pixels in this cell's region
            for (let y = startY; y < endY; y++) {
                for (let x = startX; x < endX; x++) {
                    const idx = (y * originalImg.width + x) * 4; // Index for R value in imageData.data
                    const r = data[idx];
                    const g = data[idx + 1];
                    const b = data[idx + 2];
                    // Standard luminance formula (perceived brightness)
                    const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
                    sumLuminance += luminance;
                    pixelCount++;
                }
            }

            // Average luminance for the cell; default to 0 if no pixels (should not happen with cellSize >= 1)
            const avgLuminance = (pixelCount > 0) ? (sumLuminance / pixelCount) : 0;
            
            let isLiveCell;
            if (invert) { // If inverted, darker pixels (lower luminance) become live
                isLiveCell = avgLuminance < threshold; 
            } else { // Otherwise, brighter pixels (higher luminance) become live
                isLiveCell = avgLuminance > threshold;
            }
            initialGrid[gy][gx] = isLiveCell ? 1 : 0; // Store 1 for live, 0 for dead
        }
    }

    // 5. Create Output Canvas and Draw the Resulting Game of Life Pattern
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = gridWidth * cellSize;  // Final canvas width based on grid
    outputCanvas.height = gridHeight * cellSize; // Final canvas height based on grid
    const outputCtx = outputCanvas.getContext('2d');

    if (!outputCtx) {
        console.error("processImage: Could not get 2D context for output canvas.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 250; errorCanvas.height = 100;
        const ctx = errorCanvas.getContext('2d');
        ctx.fillStyle = '#EEEEEE'; ctx.fillRect(0, 0, 250, 100);
        ctx.fillStyle = 'red'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        ctx.font = '14px Arial'; ctx.fillText("Canvas Context Error (Output)", 125, 50);
        return errorCanvas;
    }

    // Efficiently draw by first filling with deadColor, then drawing live cells.
    outputCtx.fillStyle = deadColor;
    outputCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);

    for (let gy = 0; gy < gridHeight; gy++) {
        for (let gx = 0; gx < gridWidth; gx++) {
            if (initialGrid[gy][gx] === 1) { // If cell is live
                outputCtx.fillStyle = liveColor;
                outputCtx.fillRect(gx * cellSize, gy * cellSize, cellSize, cellSize);
            }
        }
    }

    return outputCanvas; // Return the canvas element with the pattern
}

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 To Conway’s Game Of Life Pattern Converter transforms a given image into a pattern suitable for Conway’s Game of Life. Users can adjust various parameters such as the threshold for pixel brightness to determine which cells in the game are alive or dead, the size of the cells, and colors for live and dead cells. This tool can be utilized for educational purposes, to create artistic interpretations of images, or as a creative exercise in understanding cellular automata.

Leave a Reply

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