You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
type = 'cone',
observerX = 50,
observerY = 50,
radius = 50,
fovAngle = 90,
direction = 0,
darkness = 0.8,
showGuidelines = 'true',
guidelineColor = '#00ffcc'
) {
// Canvas setup
const canvas = document.createElement('canvas');
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Draw the original image onto the main canvas
ctx.drawImage(originalImg, 0, 0);
// Create an overlay canvas for the darkness/lighting effect
const overlay = document.createElement('canvas');
overlay.width = w;
overlay.height = h;
const oCtx = overlay.getContext('2d');
// Fill the overlay with darkness
oCtx.fillStyle = `rgba(0, 0, 0, ${Number(darkness)})`;
oCtx.fillRect(0, 0, w, h);
// Set composite operation to 'erase' the FOV area from the darkness
oCtx.globalCompositeOperation = 'destination-out';
// Parse and calculate variables
const obsX = w * (Number(observerX) / 100);
const obsY = h * (Number(observerY) / 100);
const diag = Math.sqrt(w * w + h * h);
// Ensure radius is at least 1 to avoid gradient errors
const absRadius = Math.max(1, diag * (Number(radius) / 100));
// Direction 0 is UP (North). Subtracting 90 degrees to align with standard canvas rotation where 0 is East.
const dirRad = (Number(direction) - 90) * Math.PI / 180;
const fovRad = Number(fovAngle) * Math.PI / 180;
const startAngle = dirRad - (fovRad / 2);
const endAngle = dirRad + (fovRad / 2);
const t = String(type).toLowerCase();
// Draw the FOV map on the overlay (this punches a hole through the darkness)
if (t === 'cone') {
oCtx.beginPath();
oCtx.moveTo(obsX, obsY);
oCtx.arc(obsX, obsY, absRadius, startAngle, endAngle);
oCtx.closePath();
const gradient = oCtx.createRadialGradient(obsX, obsY, 0, obsX, obsY, absRadius);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.7, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
oCtx.fillStyle = gradient;
oCtx.fill();
} else if (t === 'circle') {
const gradient = oCtx.createRadialGradient(obsX, obsY, 0, obsX, obsY, absRadius);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.6, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
oCtx.beginPath();
oCtx.arc(obsX, obsY, absRadius, 0, 2 * Math.PI);
oCtx.fillStyle = gradient;
oCtx.fill();
} else if (t === 'binocular') {
const offset = absRadius * 0.35;
const bRadius = Math.max(1, absRadius * 0.6);
const drawBino = (cx, cy) => {
const gradient = oCtx.createRadialGradient(cx, cy, 0, cx, cy, bRadius);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.7, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
oCtx.beginPath();
oCtx.arc(cx, cy, bRadius, 0, 2 * Math.PI);
oCtx.fillStyle = gradient;
oCtx.fill();
};
drawBino(obsX - offset, obsY);
drawBino(obsX + offset, obsY);
}
// Blend the darkness mask over the original image
ctx.drawImage(overlay, 0, 0);
// Draw technical visualization guidelines if enabled
if (String(showGuidelines).toLowerCase() === 'true') {
ctx.strokeStyle = String(guidelineColor);
ctx.fillStyle = String(guidelineColor);
ctx.lineWidth = 2;
// Draw observer point
ctx.beginPath();
ctx.arc(obsX, obsY, Math.max(3, w * 0.005), 0, 2 * Math.PI);
ctx.fill();
// Draw bounds
ctx.setLineDash([5, 5]);
if (t === 'cone') {
ctx.beginPath();
ctx.moveTo(obsX, obsY);
ctx.arc(obsX, obsY, absRadius, startAngle, endAngle);
ctx.closePath();
ctx.stroke();
} else if (t === 'circle') {
ctx.beginPath();
ctx.arc(obsX, obsY, absRadius, 0, 2 * Math.PI);
ctx.stroke();
} else if (t === 'binocular') {
const offset = absRadius * 0.35;
const bRadius = absRadius * 0.6;
ctx.beginPath();
ctx.arc(obsX - offset, obsY, bRadius, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(obsX + offset, obsY, bRadius, 0, 2 * Math.PI);
ctx.stroke();
}
ctx.setLineDash([]);
}
return canvas;
}
Apply Changes