You can edit the below JavaScript code to customize the image tool.
Apply Changes
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;
}
Apply Changes