You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
outputWidth = 512,
outputHeight = 512,
tileScale = 1.0,
tileRotation = 0, // Rotation in degrees for each tile
globalOpacity = 1.0,
patternOffsetX = 0, // Overall pattern X offset
patternOffsetY = 0 // Overall pattern Y offset
) {
// Parameter sanitization and type conversion
const numOutputWidth = Math.max(1, parseInt(String(outputWidth), 10) || 512);
const numOutputHeight = Math.max(1, parseInt(String(outputHeight), 10) || 512);
const numTileScale = Math.max(0.001, parseFloat(String(tileScale)) || 1.0); // Avoid zero/negative/too small
const numTileRotation = parseFloat(String(tileRotation)) || 0;
const numGlobalOpacity = Math.max(0, Math.min(1, parseFloat(String(globalOpacity)) || 1.0));
const numPatternOffsetX = parseFloat(String(patternOffsetX)) || 0;
const numPatternOffsetY = parseFloat(String(patternOffsetY)) || 0;
const errorCanvas = document.createElement('canvas');
errorCanvas.width = numOutputWidth;
errorCanvas.height = numOutputHeight;
const errorCtx = errorCanvas.getContext('2d');
function drawError(message) {
if (errorCtx) {
errorCtx.fillStyle = 'grey';
errorCtx.fillRect(0, 0, numOutputWidth, numOutputHeight);
const fontSize = Math.max(12, Math.min(numOutputWidth, numOutputHeight) / 20);
errorCtx.font = `${fontSize}px Arial`;
errorCtx.fillStyle = 'white';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
errorCtx.fillText(message, numOutputWidth / 2, numOutputHeight / 2);
}
return errorCanvas;
}
if (!originalImg || !(originalImg instanceof HTMLImageElement)) {
return drawError("Invalid image object provided.");
}
// Handle image loading
if (!originalImg.complete) {
try {
await new Promise((resolve, reject) => {
originalImg.onload = resolve;
originalImg.onerror = () => reject(new Error("Image loading failed."));
// If src is not set and not already loading/complete, it won't load.
if (!originalImg.src && !originalImg.complete) {
reject(new Error("Image has no src and is not loaded."));
}
});
} catch (e) {
console.error(e.message);
return drawError(e.message);
}
}
// After load (or if already complete), check natural dimensions
if (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
return drawError("Image has zero dimensions or is invalid.");
}
const tileNativeWidth = originalImg.naturalWidth;
const tileNativeHeight = originalImg.naturalHeight;
// Create the main output canvas
const outputCanvas = document.createElement('canvas');
outputCanvas.width = numOutputWidth;
outputCanvas.height = numOutputHeight;
const ctx = outputCanvas.getContext('2d');
if (!ctx) {
return drawError("Could not create output canvas context.");
}
if (numGlobalOpacity < 1.0) {
ctx.globalAlpha = numGlobalOpacity;
}
const pattern = ctx.createPattern(originalImg, 'repeat');
if (!pattern) {
if (numGlobalOpacity < 1.0) ctx.globalAlpha = 1.0; // Reset alpha
return drawError("Failed to create pattern from image.");
}
if (typeof DOMMatrix !== 'undefined') {
let finalMatrix = new DOMMatrix(); // Starts as identity matrix
// 1. Apply overall pattern offset.
// This translates the entire pattern grid.
if (numPatternOffsetX !== 0 || numPatternOffsetY !== 0) {
finalMatrix = finalMatrix.translate(numPatternOffsetX, numPatternOffsetY);
}
// 2. Create the transformation for individual tiles (scale & rotate around tile center).
// The matrix M_tile = T_center * R * S * T_invcenter applies these operations.
// DOMMatrix methods like .translate(), .scale(), .rotate() PRE-MULTIPLY: M_new = Op * M_current.
// So we build M_tile by applying T_invcenter, then S, then R, then T_center.
const tileCenterX = tileNativeWidth / 2;
const tileCenterY = tileNativeHeight / 2;
let tileTransform = new DOMMatrix(); // Identity
tileTransform = tileTransform.translate(-tileCenterX, -tileCenterY); // M_tile = T_invcenter
tileTransform = tileTransform.scale(numTileScale, numTileScale); // M_tile = S * T_invcenter
tileTransform = tileTransform.rotate(numTileRotation); // M_tile = R * S * T_invcenter
tileTransform = tileTransform.translate(tileCenterX, tileCenterY); // M_tile = T_center * R * S * T_invcenter
// 3. Combine: FinalMatrix = OffsetMatrix * TileTransformMatrix
// This means the tile transformation is applied, and then the result is offset.
finalMatrix = finalMatrix.multiply(tileTransform);
try {
pattern.setTransform(finalMatrix);
} catch (e) {
console.warn("Pattern.setTransform failed. Error:", e.message, "Proceeding without tile transformations/offset.");
// If setTransform fails, the pattern will be drawn without these custom transforms.
}
} else {
console.warn("DOMMatrix not supported. Tile scale/rotation and pattern offset may not be applied.");
}
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, numOutputWidth, numOutputHeight);
if (numGlobalOpacity < 1.0) {
ctx.globalAlpha = 1.0; // Reset globalAlpha as good practice
}
return outputCanvas;
}
Apply Changes