You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
numRoutes = 7,
routeColorStr = "255,220,50,0.7", // R,G,B,A format string
routeThickness = 3,
glowColorStr = "255,255,150,0.4", // R,G,B,A format string
glowRadius = 15,
originX = 0.5, // 0.0 to 1.0, relative to image width
originY = 0.5, // 0.0 to 1.0, relative to image height
waviness = 0.3, // 0.0 (straight) up to e.g. 1.0 for significant waviness
compositeOp = 'lighter' // e.g., 'lighter', 'screen', 'source-over'
) {
// Helper function to parse and validate color strings (R,G,B,A)
function parseColorString(colorStrInput, defaultAlpha = 1.0) {
if (typeof colorStrInput !== 'string') {
return { r: 0, g: 0, b: 0, a: 0, valid: false, str: "rgba(0,0,0,0)" };
}
const parts = colorStrInput.split(',').map(s => parseFloat(s.trim()));
if (parts.length < 3 || parts.slice(0, 3).some(isNaN)) {
// R, G, B must be valid numbers
return { r: 0, g: 0, b: 0, a: 0, valid: false, str: "rgba(0,0,0,0)" };
}
let r = parts[0];
let g = parts[1];
let b = parts[2];
let a = (parts.length > 3 && !isNaN(parts[3])) ? parts[3] : defaultAlpha;
// Clamp values to valid ranges
r = Math.max(0, Math.min(255, Math.round(r)));
g = Math.max(0, Math.min(255, Math.round(g)));
b = Math.max(0, Math.min(255, Math.round(b)));
a = Math.max(0, Math.min(1, a));
return {
r, g, b, a,
valid: true,
str: `rgba(${r},${g},${b},${a})`
};
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Parse colors and numeric parameters
const parsedRouteColor = parseColorString(routeColorStr);
const parsedGlowColor = parseColorString(glowColorStr);
const routesToDraw = Math.max(0, Number(numRoutes));
const currentRouteThickness = Math.max(0, Number(routeThickness));
const currentGlowRadius = Math.max(0, Number(glowRadius));
// Early exit if no visible effect will be produced
const isRouteLineVisible = parsedRouteColor.valid && parsedRouteColor.a > 0 && currentRouteThickness > 0;
const isGlowVisible = parsedGlowColor.valid && parsedGlowColor.a > 0 && currentGlowRadius > 0;
if (routesToDraw === 0 || (!isRouteLineVisible && !isGlowVisible)) {
return canvas; // Return canvas with just the original image
}
const actualOriginX = Math.max(0, Math.min(1, originX)) * canvas.width;
const actualOriginY = Math.max(0, Math.min(1, originY)) * canvas.height;
const currentWaviness = Math.max(0, waviness);
const originalCompositeOp = ctx.globalCompositeOperation;
const validCompositeOps = [
'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 (validCompositeOps.includes(compositeOp)) {
ctx.globalCompositeOperation = compositeOp;
} else {
ctx.globalCompositeOperation = 'lighter'; // Fallback default
}
ctx.lineCap = 'round';
for (let i = 0; i < routesToDraw; i++) {
let endX, endY;
const side = Math.floor(Math.random() * 4);
switch (side) {
case 0: // Top border
endX = Math.random() * canvas.width;
endY = 0;
break;
case 1: // Right border
endX = canvas.width;
endY = Math.random() * canvas.height;
break;
case 2: // Bottom border
endX = Math.random() * canvas.width;
endY = canvas.height;
break;
case 3: // Left border
endX = 0;
endY = Math.random() * canvas.height;
break;
}
const midPointX = (actualOriginX + endX) / 2;
const midPointY = (actualOriginY + endY) / 2;
const segmentLengthOriginToMid = Math.sqrt(
Math.pow(midPointX - actualOriginX, 2) +
Math.pow(midPointY - actualOriginY, 2)
);
const maxDeviation = segmentLengthOriginToMid * currentWaviness;
const actualDeviation = Math.random() * maxDeviation;
const angleOriginToEnd = Math.atan2(endY - actualOriginY, endX - actualOriginX);
const deviationSide = (Math.random() < 0.5 ? 1 : -1); // Randomly deviate left or right
const controlPointAngle = angleOriginToEnd + (Math.PI / 2) * deviationSide;
const cpX = midPointX + actualDeviation * Math.cos(controlPointAngle);
const cpY = midPointY + actualDeviation * Math.sin(controlPointAngle);
ctx.beginPath();
ctx.moveTo(actualOriginX, actualOriginY);
ctx.quadraticCurveTo(cpX, cpY, endX, endY);
// Apply glow using shadowBlur
if (isGlowVisible) {
ctx.shadowColor = parsedGlowColor.str;
ctx.shadowBlur = currentGlowRadius;
} else {
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
}
// Draw the main route line OR a dummy line if only glow is visible
if (isRouteLineVisible) {
ctx.strokeStyle = parsedRouteColor.str;
ctx.lineWidth = currentRouteThickness;
ctx.stroke();
} else if (isGlowVisible) {
// Route line itself is invisible, but glow is active. Stroke a minimal line for the shadow.
// Use glow color but very transparent for the stroke itself, shadow will use full glowColor.str
ctx.strokeStyle = `rgba(${parsedGlowColor.r},${parsedGlowColor.g},${parsedGlowColor.b},0.005)`;
ctx.lineWidth = 1; // Minimal width to cast shadow effectively
ctx.stroke();
}
}
// Reset canvas state modifications
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.globalCompositeOperation = originalCompositeOp;
return canvas;
}
Apply Changes