Please bookmark this page to avoid losing your image tool!

Image To Domino Pattern Art 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.
function processImage(originalImg, inputCellSize = 10, outputCellSize = 20, dominoOrientation = "vertical", dotColor = "black", dominoBodyColor = "white", lineColor = "black", dominoLineThickness = 1, gap = 2, invertBrightnessToPips = 0) {

    if (!originalImg || originalImg.width === 0 || originalImg.height === 0) {
        const canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        // Optional: fill with a default color or leave transparent
        // const ctx = canvas.getContext('2d');
        // ctx.fillStyle = 'gray';
        // ctx.fillRect(0,0,1,1);
        console.error("Original image is not loaded or has zero dimensions.");
        return canvas;
    }

    // Sanitize numerical parameters
    inputCellSize = Math.max(1, Math.floor(inputCellSize));
    outputCellSize = Math.max(1, Math.floor(outputCellSize)); 
    gap = Math.max(0, Math.floor(gap));
    dominoLineThickness = Math.max(1, Math.floor(dominoLineThickness));

    // Create a temporary canvas to get image pixel data
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = originalImg.width;
    tempCanvas.height = originalImg.height;
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImg, 0, 0);
    const fullImageData = tempCtx.getImageData(0, 0, originalImg.width, originalImg.height);

    // Helper function to get average grayscale value from a block of image data
    const getAverageGray = (imageData, sx, sy, blockWidth, blockHeight) => {
        let sumGray = 0;
        let count = 0;
        const data = imageData.data;
        const imageActualWidth = imageData.width; // width of the imageData buffer

        // Clamp block coordinates and dimensions to be within imageData bounds
        const startY = Math.max(0, sy);
        const endY = Math.min(imageData.height, sy + blockHeight);
        const startX = Math.max(0, sx);
        const endX = Math.min(imageActualWidth, sx + blockWidth);

        for (let y = startY; y < endY; y++) {
            for (let x = startX; x < endX; x++) {
                const i = (y * imageActualWidth + x) * 4; // Calculate index in the 1D data array
                const r = data[i];
                const g = data[i + 1];
                const b = data[i + 2];
                // Standard luminance calculation
                const gray = 0.299 * r + 0.587 * g + 0.114 * b;
                sumGray += gray;
                count++;
            }
        }
        
        // If block is outside image or has no pixels, treat as black.
        // The mapGrayToPips function will then handle inversion if specified.
        if (count === 0) return 0; 
        return sumGray / count;
    };
    
    const maxPipsPerHalf = 6; // Standard dominoes go up to 6 pips per half

    // Helper function to map a grayscale value (0-255) to a pip count (0-maxPipsPerHalf)
    const mapGrayToPips = (gray) => {
        const val = gray / 255; // Normalize gray value to 0 (black) - 1 (white)
        const numBins = maxPipsPerHalf + 1; // e.g., 7 bins for 0-6 pips
        let pips;

        if (invertBrightnessToPips === 1) { // Lighter image area = more pips
            pips = Math.floor(val * numBins);
        } else { // Darker image area = more pips (default behavior)
            pips = Math.floor((1 - val) * numBins);
        }
        // Clamp pips to ensure it's within [0, maxPipsPerHalf]
        return Math.min(maxPipsPerHalf, Math.max(0, pips));
    };

    // Define dot patterns for 0-6 pips
    // Coordinates are factors of cellSize (e.g., 0.5 means center)
    const dotRadius = Math.max(0.5, outputCellSize / 14); // Ensure minimum radius for visibility
    const dotPatterns = {
        0: [],
        1: [[0.5, 0.5]],
        2: [[0.25, 0.25], [0.75, 0.75]], // Diagonal
        3: [[0.25, 0.25], [0.5, 0.5], [0.75, 0.75]], // Diagonal + center
        4: [[0.25, 0.25], [0.75, 0.25], [0.25, 0.75], [0.75, 0.75]], // Corners
        5: [[0.25, 0.25], [0.75, 0.25], [0.5, 0.5], [0.25, 0.75], [0.75, 0.75]], // Corners + center
        6: [ // Two columns of three dots
            [0.25, 0.25], [0.75, 0.25],
            [0.25, 0.5], [0.75, 0.5],
            [0.25, 0.75], [0.75, 0.75]
        ]
    };

    // Helper function to draw dots for one half of a domino
    const drawDominoHalf = (ctx, halfX, halfY, cellSize, pips, currentDotColor) => {
        ctx.fillStyle = currentDotColor;
        const pattern = dotPatterns[pips] || []; // Default to empty if pips out of range (should not happen with clamping)
        pattern.forEach(([cxFactor, cyFactor]) => {
            ctx.beginPath();
            ctx.arc(halfX + cxFactor * cellSize, halfY + cyFactor * cellSize, dotRadius, 0, 2 * Math.PI);
            ctx.fill();
        });
    };

    let numDominoesX, numDominoesY;
    let outputDominoActualWidth, outputDominoActualHeight; // Dimensions of a single drawn domino piece

    const orientation = String(dominoOrientation).toLowerCase();

    if (orientation === "vertical") {
        numDominoesX = Math.floor(originalImg.width / inputCellSize);
        numDominoesY = Math.floor(originalImg.height / (inputCellSize * 2));
        outputDominoActualWidth = outputCellSize;
        outputDominoActualHeight = outputCellSize * 2;
    } else { // Assume "horizontal"
        numDominoesX = Math.floor(originalImg.width / (inputCellSize * 2));
        numDominoesY = Math.floor(originalImg.height / inputCellSize);
        outputDominoActualWidth = outputCellSize * 2;
        outputDominoActualHeight = outputCellSize;
    }

    if (numDominoesX <= 0 || numDominoesY <= 0) {
        const canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        console.warn("Image is too small for the specified inputCellSize to create any dominoes.");
        return canvas;
    }
    
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = numDominoesX * (outputDominoActualWidth + gap) - (gap > 0 ? gap : 0);
    outputCanvas.height = numDominoesY * (outputDominoActualHeight + gap) - (gap > 0 ? gap : 0);
    const outputCtx = outputCanvas.getContext('2d');

    // ---- Main loop to process image blocks and draw dominoes ----
    for (let dy = 0; dy < numDominoesY; dy++) { // Iterate through domino rows
        for (let dx = 0; dx < numDominoesX; dx++) { // Iterate through domino columns
            let imgBlockX1, imgBlockY1, imgBlockX2, imgBlockY2; // Coordinates for the two halves in the source image

            if (orientation === "vertical") {
                imgBlockX1 = dx * inputCellSize;
                imgBlockY1 = dy * 2 * inputCellSize;
                imgBlockX2 = dx * inputCellSize;
                imgBlockY2 = dy * 2 * inputCellSize + inputCellSize;
            } else { // Horizontal
                imgBlockX1 = dx * 2 * inputCellSize;
                imgBlockY1 = dy * inputCellSize;
                imgBlockX2 = dx * 2 * inputCellSize + inputCellSize;
                imgBlockY2 = dy * inputCellSize;
            }

            // Get average gray values for the two halves
            const gray1 = getAverageGray(fullImageData, imgBlockX1, imgBlockY1, inputCellSize, inputCellSize);
            const gray2 = getAverageGray(fullImageData, imgBlockX2, imgBlockY2, inputCellSize, inputCellSize);

            // Map gray values to pip counts
            const pips1 = mapGrayToPips(gray1);
            const pips2 = mapGrayToPips(gray2);

            // Calculate draw position on the output canvas
            const currentDrawX = dx * (outputDominoActualWidth + gap);
            const currentDrawY = dy * (outputDominoActualHeight + gap);

            // Draw domino body (background rectangle)
            outputCtx.fillStyle = dominoBodyColor;
            outputCtx.fillRect(currentDrawX, currentDrawY, outputDominoActualWidth, outputDominoActualHeight);
            
            // Draw the two halves and the separating line
            outputCtx.fillStyle = lineColor; // Set color for the line
            if (orientation === "vertical") {
                drawDominoHalf(outputCtx, currentDrawX, currentDrawY, outputCellSize, pips1, dotColor);
                // Draw line centered between halves
                const lineY = currentDrawY + outputCellSize - Math.ceil(dominoLineThickness / 2);
                outputCtx.fillRect(currentDrawX, lineY, outputCellSize, dominoLineThickness);
                drawDominoHalf(outputCtx, currentDrawX, currentDrawY + outputCellSize, outputCellSize, pips2, dotColor);
            } else { // Horizontal
                drawDominoHalf(outputCtx, currentDrawX, currentDrawY, outputCellSize, pips1, dotColor);
                // Draw line centered between halves
                const lineX = currentDrawX + outputCellSize - Math.ceil(dominoLineThickness / 2);
                outputCtx.fillRect(lineX, currentDrawY, dominoLineThickness, outputCellSize);
                drawDominoHalf(outputCtx, currentDrawX + outputCellSize, currentDrawY, outputCellSize, pips2, dotColor);
            }
        }
    }
    return outputCanvas;
}

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 Domino Pattern Art Converter transforms your images into a unique representation using domino patterns. By dividing the original image into blocks, the tool calculates average brightness levels to determine the number of dots (pips) on each domino, mimicking the appearance of traditional game pieces. Users can customize various parameters, including cell sizes, colors, and orientations, allowing for personalized artwork suitable for crafts, games, educational purposes, or simply for fun. This tool is ideal for creating visually appealing pieces that blend photography with gaming aesthetics.

Leave a Reply

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