You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, tileSize = 20, gap = 2, curvature = 0.15, shineIntensity = 0.5, shineDensity = 0.1, gapColor = "black") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgWidth = originalImg.width;
const imgHeight = originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Ensure tileSize and gap are valid
tileSize = Math.max(1, tileSize);
gap = Math.max(0, gap);
// Fill background for gaps
ctx.fillStyle = gapColor;
ctx.fillRect(0, 0, imgWidth, imgHeight);
const imgCenterX = imgWidth / 2;
const imgCenterY = imgHeight / 2;
// Normalization factor for distortion effect. Using Math.max ensures points on the
// larger dimension's edge correspond to a normalized distance of 1.
// Add Math.max(1, ...) to prevent division by zero for 1x1 or smaller images.
const normDistFactor = Math.max(1, imgCenterX, imgCenterY);
const step = tileSize + gap;
for (let y = 0; y < imgHeight; y += step) {
for (let x = 0; x < imgWidth; x += step) {
// Current tile's destination top-left corner on the canvas
const tileDestX = x;
const tileDestY = y;
// Determine the actual size of the facet to draw, can be smaller at image edges
const currentTileWidth = Math.min(tileSize, imgWidth - tileDestX);
const currentTileHeight = Math.min(tileSize, imgHeight - tileDestY);
// Skip if the tile is too small or completely outside (redundant with loop condition but safe)
if (currentTileWidth <= 0 || currentTileHeight <= 0) {
continue;
}
// Center of the current destination facet
const facetCenterX_dest = tileDestX + currentTileWidth / 2;
const facetCenterY_dest = tileDestY + currentTileHeight / 2;
// Calculate normalized distance of facet center from image center
const dx_norm = facetCenterX_dest - imgCenterX;
const dy_norm = facetCenterY_dest - imgCenterY;
const distFromImgCenter = Math.sqrt(dx_norm * dx_norm + dy_norm * dy_norm);
let normalizedRadialPos = 0;
if (normDistFactor > 0) { // normDistFactor is max(1, ...) so always > 0
normalizedRadialPos = distFromImgCenter / normDistFactor;
}
// Distortion factor for sampling source image
// Positive curvature: pulls source sample towards center and shrinks it (barrel distortion reflection)
// Negative curvature: pushes source sample outwards and expands it (pincushion distortion reflection)
let radialShiftAndScale = 1 - curvature * normalizedRadialPos * normalizedRadialPos;
radialShiftAndScale = Math.max(0.01, radialShiftAndScale); // Prevent zero/negative scale
// Calculate source region center (shifted by distortion)
const srcCenterX = imgCenterX + dx_norm * radialShiftAndScale;
const srcCenterY = imgCenterY + dy_norm * radialShiftAndScale;
// Calculate source region dimensions (scaled by distortion)
const srcTotalWidth = currentTileWidth * radialShiftAndScale;
const srcTotalHeight = currentTileHeight * radialShiftAndScale;
// Calculate source region top-left before clipping
const srcClipX = srcCenterX - srcTotalWidth / 2;
const srcClipY = srcCenterY - srcTotalHeight / 2;
// Clip source rectangle to be within originalImg bounds
// Determine top-left of source after ensuring it's within [0, imgWidth/Height]
const finalSrcX = Math.max(0, srcClipX);
const finalSrcY = Math.max(0, srcClipY);
// Calculate how much the source width/height needs to be adjusted
// due to finalSrcX/Y being potentially different from srcClipX/Y
const widthAdjustment = finalSrcX - srcClipX;
const heightAdjustment = finalSrcY - srcClipY;
const adjustedSrcWidth = srcTotalWidth - widthAdjustment;
const adjustedSrcHeight = srcTotalHeight - heightAdjustment;
// Ensure source width/height don't exceed image boundaries from finalSrcX/Y
const clippedSrcWidth = Math.max(0.1, Math.min(adjustedSrcWidth, imgWidth - finalSrcX));
const clippedSrcHeight = Math.max(0.1, Math.min(adjustedSrcHeight, imgHeight - finalSrcY));
if (clippedSrcWidth < 0.1 || clippedSrcHeight < 0.1) {
continue; // Skip if source region is too small after clipping
}
ctx.drawImage(originalImg,
finalSrcX, finalSrcY, clippedSrcWidth, clippedSrcHeight,
tileDestX, tileDestY, currentTileWidth, currentTileHeight);
// Add shine effect
if (Math.random() < shineDensity && shineIntensity > 0) {
const shineX = tileDestX + currentTileWidth / 2;
const shineY = tileDestY + currentTileHeight / 2;
const shineRadius = Math.min(currentTileWidth, currentTileHeight) / 2;
if (shineRadius > 0) {
const shineGradient = ctx.createRadialGradient(
shineX, shineY, 0,
shineX, shineY, shineRadius
);
shineGradient.addColorStop(0, `rgba(255, 255, 255, ${shineIntensity})`);
shineGradient.addColorStop(0.8, `rgba(255, 255, 255, ${shineIntensity * 0.3})`);
shineGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = shineGradient;
ctx.fillRect(tileDestX, tileDestY, currentTileWidth, currentTileHeight);
}
}
}
}
return canvas;
}
Apply Changes