You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
scanAngleDegrees = 45,
scanColor = "rgba(100, 255, 100, 0.7)", // Color of the scanning beam. Assumed to be rgba/hsla for transparency manipulation.
scanArcWidthDegrees = 60, // Width of the beam in degrees
overlayColor = "rgba(0, 30, 0, 0.4)", // Overall green tint
grayscaleImage = 1, // 1 to apply grayscale to original image, 0 to keep colors
showGrid = 1, // 1 to show grid lines, 0 to hide
gridColor = "rgba(50, 150, 50, 0.6)", // Color of grid lines
gridCircles = 5, // Number of concentric circles in the grid
gridLines = 8, // Number of radial lines in the grid
centerCrosshair = 1, // 1 to show center crosshair, 0 to hide
crosshairColor = "rgba(50, 150, 50, 0.8)" // Color of center crosshair
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth || originalImg.width || 0;
canvas.height = originalImg.naturalHeight || originalImg.height || 0;
if (canvas.width === 0 || canvas.height === 0) {
// Return empty canvas if image has no dimensions, or dimensions couldn't be determined
return canvas;
}
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const radius = Math.min(cx, cy);
// --- Start drawing ---
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 1. Outer Circular Mask
ctx.save(); // Save context state before clipping
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.clip();
// 2. Draw Original Image (optionally grayscaled)
if (grayscaleImage === 1) {
ctx.filter = 'grayscale(100%)';
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
if (grayscaleImage === 1) {
ctx.filter = 'none'; // Reset filter
}
// 3. Apply Overlay Color to the entire clipped circular area
if (overlayColor && overlayColor !== "transparent" && !overlayColor.endsWith(", 0)") && !overlayColor.endsWith(",0)")) { // Check for actual color vs fully transparent
ctx.fillStyle = overlayColor;
ctx.fillRect(0, 0, canvas.width, canvas.height); // Will be clipped to circle
}
// 4. Draw Grid
if (showGrid === 1 && (gridCircles > 0 || gridLines > 0)) {
ctx.strokeStyle = gridColor;
ctx.lineWidth = 1; // Consider making this a parameter or adaptive based on radius
// Concentric circles
if (gridCircles > 0) {
for (let i = 0; i < gridCircles; i++) {
ctx.beginPath();
const circleRadius = (radius / gridCircles) * (i + 1);
ctx.arc(cx, cy, circleRadius, 0, 2 * Math.PI);
ctx.stroke();
}
}
// Radial lines
if (gridLines > 0) {
for (let i = 0; i < gridLines; i++) {
// Ensure lines don't converge messily at the very center if crosshair is also there
const lineStartRadius = (centerCrosshair === 1 && radius * 0.05 > 5) ? Math.min(radius * 0.05, 10) : 0;
const angle = (2 * Math.PI / gridLines) * i;
ctx.beginPath();
ctx.moveTo(cx + lineStartRadius * Math.cos(angle), cy + lineStartRadius * Math.sin(angle));
ctx.lineTo(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
ctx.stroke();
}
}
}
// 5. Draw Scan Beam
// Clamp scanArcWidthDegrees to be < 360 for conic gradient color stop logic
const effectiveScanArcWidthDegrees = Math.max(0, Math.min(scanArcWidthDegrees, 359.999));
if (effectiveScanArcWidthDegrees > 0) { // Only draw beam if it has a width
const scanAngleRad = (scanAngleDegrees * Math.PI / 180);
// Prepare transparent version of scanColor. Assumes scanColor is rgba or hsla.
let transparentScanColor = "rgba(0,0,0,0)"; // Fallback
const lastCommaIndex = scanColor.lastIndexOf(',');
const colorPrefix = scanColor.substring(0, lastCommaIndex + 1).toLowerCase();
if (lastCommaIndex !== -1 && (colorPrefix.startsWith("rgba(") || colorPrefix.startsWith("hsla("))) {
transparentScanColor = scanColor.substring(0, lastCommaIndex + 1) + "0)";
} else {
// For non-rgba/hsla explicitly, this is a simple guess.
// A more robust solution would parse various color formats (hex, rgb, names).
// For now, warn and use a default fully transparent black.
if (typeof console !== 'undefined' && console.warn) {
console.warn("Radar scan beam's fade-to-transparent effect might not be ideal if scanColor is not in rgba/hsla format that includes an alpha value. Using default transparent for fade effect.");
}
}
if (typeof ctx.createConicGradient === "function") {
const arcFraction = (effectiveScanArcWidthDegrees / 2) / 360; // Fraction of full circle for half arc
const beamGradient = ctx.createConicGradient(scanAngleRad, cx, cy);
beamGradient.addColorStop(0, scanColor); // Peak color at the center of the beam
beamGradient.addColorStop(arcFraction, transparentScanColor);
// The gradient wraps, so a stop at 1-arcFraction makes the other side of the beam fade
beamGradient.addColorStop(1 - arcFraction, transparentScanColor);
ctx.fillStyle = beamGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height); // Fill the clipped circle
} else {
// Fallback for browsers not supporting conic gradient: Draw a solid arc
const beamWidthRad = effectiveScanArcWidthDegrees * Math.PI / 180;
const beamStartAngleRad = scanAngleRad - beamWidthRad / 2;
const beamEndAngleRad = scanAngleRad + beamWidthRad / 2;
ctx.fillStyle = scanColor;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, beamStartAngleRad, beamEndAngleRad);
ctx.closePath();
ctx.fill();
if (typeof console !== 'undefined' && console.warn) {
console.warn("Radar scan beam uses a solid color because createConicGradient is not supported in this browser environment.");
}
}
}
// 6. Draw Center Crosshair
if (centerCrosshair === 1) {
ctx.strokeStyle = crosshairColor;
ctx.lineWidth = 1;
const crosshairSize = Math.min(radius * 0.05, 10); // Adaptive size, capped at 10px
ctx.beginPath();
ctx.moveTo(cx - crosshairSize, cy);
ctx.lineTo(cx + crosshairSize, cy); // Horizontal line
ctx.moveTo(cx, cy - crosshairSize);
ctx.lineTo(cx, cy + crosshairSize); // Vertical line
ctx.stroke();
}
// 7. Restore from Outer Circular Mask clip
ctx.restore(); // Restore context state from before clipping
return canvas;
}
Apply Changes