Please bookmark this page to avoid losing your image tool!

Image Embroidery 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.
async function processImage(originalImg, stitchSize = 10, colorSimplificationFactor = 48, stitchLineWidthFactor = 0.15, fabricColor = "transparent") {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const w = originalImg.width;
    const h = originalImg.height;

    // Ensure originalImg has valid dimensions
    if (!w || !h) {
        console.error("Original image has zero width or height. Ensure the image is loaded.");
        // Return a small, empty canvas or one with fabric color
        canvas.width = Math.max(1, w); // Ensure width/height are at least 1
        canvas.height = Math.max(1, h);
        if (fabricColor && fabricColor !== "transparent") {
            ctx.fillStyle = fabricColor;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }
        return canvas;
    }

    canvas.width = w;
    canvas.height = h;

    // Fill background if a fabric color is specified
    if (fabricColor && fabricColor !== "transparent") {
        ctx.fillStyle = fabricColor;
        ctx.fillRect(0, 0, w, h);
    }

    // Use a temporary canvas to draw the original image and get its pixel data.
    // This helps in dealing with various image sources (e.g. SVG) and simplifies pixel access.
    const tempCanvas = document.createElement('canvas');
    const tempCtx = tempCanvas.getContext('2d');
    tempCanvas.width = w;
    tempCanvas.height = h;
    
    try {
        // Draw the original image onto the temporary canvas
        tempCtx.drawImage(originalImg, 0, 0, w, h);
    } catch (e) {
        console.error("Error drawing original image to temporary canvas:", e);
        // Fallback: return the current canvas (empty or fabric-colored)
        return canvas;
    }

    let fullImageData;
    try {
        // Get pixel data from the temporary canvas
        fullImageData = tempCtx.getImageData(0, 0, w, h);
    } catch (e) {
        console.error("Error getting image data (possibly due to tainted canvas if image is cross-origin without CORS):", e);
        // Fallback: return the current canvas
        return canvas;
    }
    
    const pixels = fullImageData.data;

    // Validate and sanitize parameters
    stitchSize = Math.max(1, Math.floor(stitchSize));
    // colorSimplificationFactor: 1 means no simplification. Must be at least 1.
    colorSimplificationFactor = Math.max(1, Math.floor(colorSimplificationFactor)); 
    stitchLineWidthFactor = Math.max(0.01, stitchLineWidthFactor); // Ensure a small positive factor

    // Calculate actual stitch line width
    const actualStitchLineWidth = Math.max(1, Math.floor(stitchSize * stitchLineWidthFactor));
    ctx.lineWidth = actualStitchLineWidth;
    // 'round' line caps give a softer, more thread-like appearance to stitches
    ctx.lineCap = 'round'; 

    // Iterate over the image in blocks defined by stitchSize
    for (let yBlock = 0; yBlock < h; yBlock += stitchSize) {
        for (let xBlock = 0; xBlock < w; xBlock += stitchSize) {
            // Determine the actual width and height of the current block (can be smaller at edges)
            const currentBlockW = Math.min(stitchSize, w - xBlock);
            const currentBlockH = Math.min(stitchSize, h - yBlock);

            if (currentBlockW <= 0 || currentBlockH <= 0) continue; // Should not happen with proper loop logic

            let r_sum = 0, g_sum = 0, b_sum = 0, a_sum = 0;
            let numPixelsInBlock = 0;

            // Calculate the average color of the pixels within the current block
            for (let j = 0; j < currentBlockH; j++) { // y-offset within the block
                for (let i = 0; i < currentBlockW; i++) { // x-offset within the block
                    const px = xBlock + i; // Absolute x-coordinate in the image
                    const py = yBlock + j; // Absolute y-coordinate in the image
                    
                    // Calculate the index for the pixel data array
                    const dataIndex = (py * w + px) * 4;
                    
                    r_sum += pixels[dataIndex];     // Red
                    g_sum += pixels[dataIndex + 1]; // Green
                    b_sum += pixels[dataIndex + 2]; // Blue
                    a_sum += pixels[dataIndex + 3]; // Alpha
                    numPixelsInBlock++;
                }
            }
            
            if (numPixelsInBlock === 0) continue;

            let avgR = r_sum / numPixelsInBlock;
            let avgG = g_sum / numPixelsInBlock;
            let avgB = b_sum / numPixelsInBlock;
            let avgA = a_sum / numPixelsInBlock;

            // If the average alpha of the block is very low, treat it as transparent and skip drawing
            if (avgA < 10) { // Alpha threshold (0-255 range)
                continue;
            }

            // Apply color simplification (quantization)
            if (colorSimplificationFactor > 1) {
                avgR = Math.floor(avgR / colorSimplificationFactor) * colorSimplificationFactor;
                avgG = Math.floor(avgG / colorSimplificationFactor) * colorSimplificationFactor;
                avgB = Math.floor(avgB / colorSimplificationFactor) * colorSimplificationFactor;
            }

            // Clamp R,G,B values to the valid 0-255 range after simplification and averaging
            avgR = Math.max(0, Math.min(255, Math.round(avgR)));
            avgG = Math.max(0, Math.min(255, Math.round(avgG)));
            avgB = Math.max(0, Math.min(255, Math.round(avgB)));
            
            const finalAlphaValue = Math.max(0, Math.min(255, Math.round(avgA)));
            const stitchColor = `rgba(${avgR}, ${avgG}, ${avgB}, ${finalAlphaValue / 255.0})`;
            
            ctx.strokeStyle = stitchColor;

            // Draw the cross-stitch pattern for the block
            // The stitch is drawn from corner to corner of the (currentBlockW, currentBlockH) area,
            // starting at (xBlock, yBlock). Stitches at edges might be smaller/incomplete as a result.
            
            // Draw the '\' part of the cross-stitch
            ctx.beginPath();
            ctx.moveTo(xBlock, yBlock);
            ctx.lineTo(xBlock + currentBlockW, yBlock + currentBlockH);
            ctx.stroke();

            // Draw the '/' part of the cross-stitch
            ctx.beginPath();
            ctx.moveTo(xBlock + currentBlockW, yBlock);
            ctx.lineTo(xBlock, yBlock + currentBlockH);
            ctx.stroke();
        }
    }
    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 Embroidery Filter is a tool that transforms images into a visually appealing cross-stitch embroidery style. Users can customize parameters such as stitch size, color simplification, and fabric color to achieve different artistic effects. This tool is useful for artists, designers, and hobbyists looking to create unique textile or fabric designs from their digital images, as well as for adding a handmade aesthetic to digital artwork. Whether for personal projects, digital art enhancements, or creating custom graphics for printed materials, the Image Embroidery Filter provides an innovative way to reinterpret images.

Leave a Reply

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