You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, knotColor = '#3a3a3a', knotOpacity = 0.3, knotSize = 30, knotWidth = 4, imageTreatment = 'grayscale', blendMode = 'overlay') {
const outputCanvas = document.createElement('canvas');
const outputCtx = outputCanvas.getContext('2d');
const w = originalImg.naturalWidth || originalImg.width;
const h = originalImg.naturalHeight || originalImg.height;
outputCanvas.width = w;
outputCanvas.height = h;
// 1. Image Treatment
if (imageTreatment === 'grayscale') {
outputCtx.filter = 'grayscale(100%)';
outputCtx.drawImage(originalImg, 0, 0, w, h);
outputCtx.filter = 'none'; // Reset filter
} else if (imageTreatment === 'sepia') {
outputCtx.filter = 'sepia(100%)';
outputCtx.drawImage(originalImg, 0, 0, w, h);
outputCtx.filter = 'none'; // Reset filter
} else { // 'none' or any other value
outputCtx.drawImage(originalImg, 0, 0, w, h);
}
// Validate and adjust knot parameters
let currentKnotSize = Math.max(20, Math.floor(knotSize / 2) * 2); // Ensure even and min size
let currentKnotWidth = Math.max(1, knotWidth);
const subSize = currentKnotSize / 2;
// Ensure knotWidth is not too large for subSize, to prevent visual errors in gaps
if (currentKnotWidth * 1.5 > subSize) {
currentKnotWidth = Math.max(1, Math.floor(subSize / 1.5));
}
// 2. Create Knot Tile Pattern
const knotTileCanvas = document.createElement('canvas');
knotTileCanvas.width = currentKnotSize;
knotTileCanvas.height = currentKnotSize;
const knotTileCtx = knotTileCanvas.getContext('2d');
knotTileCtx.strokeStyle = knotColor;
// Helper function to draw a sub-knot (a single crossing)
function drawSubKnot(ctx, x_offset, y_offset, singleCellSize, singleCellKnotWidth, isHorizontalOver) {
ctx.lineCap = 'round';
ctx.lineWidth = singleCellKnotWidth;
const halfCellSize = singleCellSize / 2;
// Space for the "under" pass, proportional to knot width
const spaceAroundCrossingLine = singleCellKnotWidth * 0.25;
const halfCrossingLineWidth = singleCellKnotWidth / 2;
if (isHorizontalOver) {
// Horizontal strand (full)
ctx.beginPath();
ctx.moveTo(x_offset, y_offset + halfCellSize);
ctx.lineTo(x_offset + singleCellSize, y_offset + halfCellSize);
ctx.stroke();
// Vertical strand (broken)
ctx.beginPath();
ctx.moveTo(x_offset + halfCellSize, y_offset);
ctx.lineTo(x_offset + halfCellSize, y_offset + halfCellSize - halfCrossingLineWidth - spaceAroundCrossingLine);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x_offset + halfCellSize, y_offset + halfCellSize + halfCrossingLineWidth + spaceAroundCrossingLine);
ctx.lineTo(x_offset + halfCellSize, y_offset + singleCellSize);
ctx.stroke();
} else { // VerticalOverHorizontal
// Vertical strand (full)
ctx.beginPath();
ctx.moveTo(x_offset + halfCellSize, y_offset);
ctx.lineTo(x_offset + halfCellSize, y_offset + singleCellSize);
ctx.stroke();
// Horizontal strand (broken)
ctx.beginPath();
ctx.moveTo(x_offset, y_offset + halfCellSize);
ctx.lineTo(x_offset + halfCellSize - halfCrossingLineWidth - spaceAroundCrossingLine, y_offset + halfCellSize);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x_offset + halfCellSize + halfCrossingLineWidth + spaceAroundCrossingLine, y_offset + halfCellSize);
ctx.lineTo(x_offset + singleCellSize, y_offset + halfCellSize);
ctx.stroke();
}
}
// Draw a 2x2 pattern of sub-knots with alternating crossings
drawSubKnot(knotTileCtx, 0, 0, subSize, currentKnotWidth, true); // Top-left: H over V
drawSubKnot(knotTileCtx, subSize, 0, subSize, currentKnotWidth, false); // Top-right: V over H
drawSubKnot(knotTileCtx, 0, subSize, subSize, currentKnotWidth, false); // Bottom-left: V over H
drawSubKnot(knotTileCtx, subSize, subSize, subSize, currentKnotWidth, true); // Bottom-right: H over V
// 3. Apply Knot Pattern to Output Canvas
const knotPattern = outputCtx.createPattern(knotTileCanvas, 'repeat');
if (knotPattern) {
outputCtx.globalAlpha = Math.min(1, Math.max(0, knotOpacity)); // Clamp opacity 0-1
// Validate blend mode input, default to 'source-over' if invalid
const validBlendModes = ['source-over', 'source-in', 'source-out', 'source-atop',
'destination-over', 'destination-in', 'destination-out', 'destination-atop',
'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken',
'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light',
'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'];
if (validBlendModes.includes(blendMode)) {
outputCtx.globalCompositeOperation = blendMode;
} else {
outputCtx.globalCompositeOperation = 'overlay'; // Default fallback
}
outputCtx.fillStyle = knotPattern;
outputCtx.fillRect(0, 0, w, h);
// Reset canvas context properties
outputCtx.globalAlpha = 1.0;
outputCtx.globalCompositeOperation = 'source-over';
} else {
console.error("Failed to create knot pattern.");
}
return outputCanvas;
}
Apply Changes