Please bookmark this page to avoid losing your image tool!

Image Cross-Stitch Filter 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, stitchSize = 10, gridColor = 'rgba(0,0,0,0.1)', stitchThickness = 2) {
    // Sanitize parameters
    // Ensure parameters are parsed as numbers, with defaults if parsing fails or invalid values are given.
    stitchSize = Math.max(1, parseInt(String(stitchSize), 10) || 10);
    stitchThickness = Math.max(0.1, parseFloat(String(stitchThickness)) || 2);

    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    if (imgWidth === 0 || imgHeight === 0) {
        console.error("Image has zero dimensions or is not loaded.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 300; // Wider for better message display
        errorCanvas.height = 60;
        const errCtx = errorCanvas.getContext('2d');
        errCtx.fillStyle = '#FFDDDD'; // Light pink background for error
        errCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        errCtx.fillStyle = '#D8000C'; // Dark red text for error
        errCtx.font = '14px Arial';
        errCtx.textAlign = 'center';
        errCtx.textBaseline = 'middle';
        errCtx.fillText("Error: Image not loaded or has no dimensions.", errorCanvas.width / 2, errorCanvas.height / 2);
        return errorCanvas;
    }

    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = imgWidth;
    outputCanvas.height = imgHeight;
    const ctx = outputCanvas.getContext('2d');

    // Create a temporary canvas to get image data.
    // This is necessary because originalImg might be an HTMLImageElement
    // and we need to access its pixel data.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = imgWidth;
    tempCanvas.height = imgHeight;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); // Hint for optimization
    
    tempCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        console.error("Could not get image data, possibly due to cross-origin restrictions:", e);
        // Draw an error message on the output canvas if getImageData fails
        ctx.fillStyle = '#EEEEEE'; // Light gray background
        ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
        ctx.fillStyle = '#D8000C'; // Dark red text
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = '16px Arial';
        const messageLine1 = 'Error: Could not process image data.';
        const messageLine2 = '(This might be due to cross-origin restrictions.)';
        ctx.fillText(messageLine1, outputCanvas.width / 2, outputCanvas.height / 2 - 10);
        ctx.fillText(messageLine2, outputCanvas.width / 2, outputCanvas.height / 2 + 12);
        return outputCanvas;
    }
    const data = imageData.data;

    // Optional: fill with a background color (e.g., white mimicking fabric)
    // ctx.fillStyle = 'white';
    // ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
    // Or clear explicitly:
    ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);

    for (let yBlock = 0; yBlock < imgHeight; yBlock += stitchSize) {
        for (let xBlock = 0; xBlock < imgWidth; xBlock += stitchSize) {
            let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
            let numPixelsInBlock = 0;

            // Determine the actual size of the block, which could be smaller at the image edges
            const actualBlockWidth = Math.min(stitchSize, imgWidth - xBlock);
            const actualBlockHeight = Math.min(stitchSize, imgHeight - yBlock);

            for (let j = 0; j < actualBlockHeight; j++) {
                for (let i = 0; i < actualBlockWidth; i++) {
                    const currentX = xBlock + i;
                    const currentY = yBlock + j;
                    // Calculate the pixel's index in the flat imageData array
                    const pixelIndex = (currentY * imgWidth + currentX) * 4;

                    rSum += data[pixelIndex];
                    gSum += data[pixelIndex + 1];
                    bSum += data[pixelIndex + 2];
                    aSum += data[pixelIndex + 3];
                    numPixelsInBlock++;
                }
            }

            if (numPixelsInBlock === 0) continue; // Should mostly not happen with correct loop logic

            const avgR = Math.round(rSum / numPixelsInBlock);
            const avgG = Math.round(gSum / numPixelsInBlock);
            const avgB = Math.round(bSum / numPixelsInBlock);
            const avgA_255 = Math.round(aSum / numPixelsInBlock);
            
            // If average alpha is very low (e.g. < 4% opaque), skip drawing this stitch.
            // This prevents drawing nearly invisible black stitches for transparent areas.
            const alphaThreshold = 10; // out of 255
            if (avgA_255 < alphaThreshold) {
                 // Still draw grid if specified, even for "empty" cells
                if (gridColor && gridColor.toLowerCase() !== 'none' && gridColor !== '') {
                    ctx.strokeStyle = gridColor;
                    ctx.lineWidth = 0.5;
                    // Offset by 0.5 for crisp lines, adjust size to fit within cell
                    ctx.strokeRect(xBlock + 0.5, yBlock + 0.5, actualBlockWidth -1 , actualBlockHeight -1 );
                }
                continue;
            }

            const avgColor = `rgba(${avgR}, ${avgG}, ${avgB}, ${avgA_255 / 255.0})`;

            // Draw cross-stitch 'X'
            ctx.strokeStyle = avgColor;
            ctx.lineWidth = stitchThickness;
            ctx.lineCap = 'square'; // 'butt' or 'square'. 'square' makes stitches look a bit fuller.

            // Diagonal 1: top-left to bottom-right (\)
            ctx.beginPath();
            ctx.moveTo(xBlock, yBlock);
            ctx.lineTo(xBlock + actualBlockWidth, yBlock + actualBlockHeight);
            ctx.stroke();

            // Diagonal 2: top-right to bottom-left (/)
            ctx.beginPath();
            ctx.moveTo(xBlock + actualBlockWidth, yBlock);
            ctx.lineTo(xBlock, yBlock + actualBlockHeight);
            ctx.stroke();

            // Optional: Draw grid lines (can be drawn before or after stitches)
            if (gridColor && gridColor.toLowerCase() !== 'none' && gridColor !== '') {
                ctx.strokeStyle = gridColor;
                ctx.lineWidth = 0.5; // Thin lines for grid
                ctx.strokeRect(xBlock + 0.5, yBlock + 0.5, actualBlockWidth -1 , actualBlockHeight -1 );
            }
        }
    }
    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 Cross-Stitch Filter Application allows users to transform their images into a cross-stitch style representation. By adjusting parameters like stitch size, grid color, and stitch thickness, users can customize the appearance of their transformed images. This tool is ideal for creating unique art pieces, designing custom textiles, or simply exploring an artistic approach to digital images. Whether for personal projects, gifts, or creative expressions, this application provides a playful way to reinterpret visuals into a charming and craft-inspired format.

Leave a Reply

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