You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
laceColor = "#eeeeee",
laceThickness = "8",
eyeletCount = "6",
edgeMargin = "40",
addBow = "true"
) {
const canvas = document.createElement("canvas");
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
// Draw original image
ctx.drawImage(originalImg, 0, 0);
const count = Math.max(2, parseInt(eyeletCount, 10));
const thickness = parseFloat(laceThickness);
const margin = Math.min(parseFloat(edgeMargin), width / 4);
const drawBow = String(addBow).toLowerCase() === "true" || addBow === "1";
// Allocate space at the bottom if we are drawing a bow
const bowSpace = drawBow ? Math.min(150, height * 0.25) : margin;
const startY = margin;
let endY = height - bowSpace;
// Fallback if image aspect ratio is extreme
if (endY <= startY) {
endY = height - margin;
}
const spacing = (endY - startY) / (count - 1);
const leftX = margin;
const rightX = width - margin;
// Helper to draw a straight lace segment
const drawLaceSegment = (x1, y1, x2, y2) => {
ctx.shadowColor = 'rgba(0, 0, 0, 0.7)';
ctx.shadowBlur = 6;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 4;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Base lace
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = thickness;
ctx.strokeStyle = laceColor;
ctx.stroke();
ctx.shadowColor = 'transparent';
// Inner highlights to make it look rounded
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = thickness * 0.4;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
ctx.stroke();
// Inner shadow texture
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = thickness * 0.8;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
ctx.stroke();
};
// Helper to draw curved lace (for bows and loose ends)
const drawLaceCurve = (x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2) => {
ctx.shadowColor = 'rgba(0, 0, 0, 0.6)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 4;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
ctx.lineWidth = thickness;
ctx.strokeStyle = laceColor;
ctx.stroke();
ctx.shadowColor = 'transparent';
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
ctx.lineWidth = thickness * 0.4;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
ctx.lineWidth = thickness * 0.8;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
ctx.stroke();
};
// 1. Draw main body laces (Crisscross pattern)
// Starting horizontal straight connection at the top
drawLaceSegment(leftX, startY, rightX, startY);
for (let i = 0; i < count - 1; i++) {
const y1 = startY + i * spacing;
const y2 = startY + (i + 1) * spacing;
// Alternate over/under layering for authenticity
if (i % 2 === 0) {
drawLaceSegment(leftX, y1, rightX, y2);
drawLaceSegment(rightX, y1, leftX, y2);
} else {
drawLaceSegment(rightX, y1, leftX, y2);
drawLaceSegment(leftX, y1, rightX, y2);
}
}
// 2. Draw Eyelets (holes + metallic grommets) over the laces
const eyeletRadius = Math.max(6, thickness * 0.9);
const rimThickness = Math.max(3, thickness * 0.4);
for (let i = 0; i < count; i++) {
const y = startY + i * spacing;
const coords = [leftX, rightX];
coords.forEach((x, index) => {
// Shadow behind the eyelet to separate from image
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 2;
// Draw baseline for shadow
ctx.beginPath();
ctx.arc(x, y, eyeletRadius + rimThickness / 2, 0, Math.PI * 2);
ctx.fillStyle = '#666666';
ctx.fill();
ctx.shadowColor = 'transparent';
// Black hole to "swallow" the lace lines
ctx.beginPath();
ctx.arc(x, y, eyeletRadius, 0, Math.PI * 2);
ctx.fillStyle = '#181818';
ctx.fill();
// Inner dark rim shadow for depth
ctx.beginPath();
ctx.arc(x, y, eyeletRadius, 0, Math.PI * 2);
ctx.lineWidth = 2;
ctx.strokeStyle = '#050505';
ctx.stroke();
// Draw metallic ring (grommet)
ctx.beginPath();
ctx.arc(x, y, eyeletRadius + rimThickness / 2, 0, Math.PI * 2);
ctx.lineWidth = rimThickness;
// Create metallic gradient
const directionMultiplier = index === 0 ? 1 : -1;
const grad = ctx.createLinearGradient(
x - eyeletRadius * directionMultiplier,
y - eyeletRadius,
x + eyeletRadius * directionMultiplier,
y + eyeletRadius
);
grad.addColorStop(0, '#f0f0f0');
grad.addColorStop(0.3, '#999999');
grad.addColorStop(0.7, '#666666');
grad.addColorStop(1, '#cccccc');
ctx.strokeStyle = grad;
ctx.stroke();
});
}
// 3. Draw finishing Bow if requested
if (drawBow && (endY < height)) {
const bowY = endY;
const centerX = (leftX + rightX) / 2;
const loopW = Math.max(30, (rightX - leftX) * 0.35);
const loopH = Math.min(60, loopW * 0.8);
// Curving strings emerging from last eyelet void, merging to center knot
drawLaceCurve(leftX, bowY, leftX + loopW*0.4, bowY + loopH*0.4, centerX - loopW*0.2, bowY, centerX, bowY);
drawLaceCurve(rightX, bowY, rightX - loopW*0.4, bowY + loopH*0.4, centerX + loopW*0.2, bowY, centerX, bowY);
// Left "bunny ear" loop
drawLaceCurve(centerX, bowY, centerX - loopW*1.2, bowY - loopH, centerX - loopW*1.6, bowY + loopH, centerX, bowY);
// Right "bunny ear" loop
drawLaceCurve(centerX, bowY, centerX + loopW*1.2, bowY - loopH, centerX + loopW*1.6, bowY + loopH, centerX, bowY);
// Left dangling tail
drawLaceCurve(centerX, bowY, centerX - loopW*0.2, bowY + loopH, centerX - loopW*0.4, bowY + loopH*2, centerX - loopW*0.6, bowY + loopH*2.5);
// Right dangling tail
drawLaceCurve(centerX, bowY, centerX + loopW*0.2, bowY + loopH, centerX + loopW*0.4, bowY + loopH*2, centerX + loopW*0.6, bowY + loopH*2.5);
// The Knot
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 5;
ctx.shadowOffsetY = 3;
ctx.beginPath();
ctx.arc(centerX, bowY, thickness, 0, Math.PI * 2);
ctx.fillStyle = laceColor;
ctx.fill();
ctx.shadowColor = 'transparent';
// Knot highlight for texture
ctx.beginPath();
ctx.arc(centerX - thickness*0.2, bowY - thickness*0.2, thickness * 0.35, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,255,255,0.4)';
ctx.fill();
}
return canvas;
}
Apply Changes