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!
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.