You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, fisheyeIntensity = 0.6, camLabel = "FRONT DOOR", timestamp = "auto", vignetteOpacity = 0.8) {
// Cap dimensions for performance to prevent browser freezing during pixel manipulation
const MAX_DIM = 1200;
let scale = 1;
if (originalImg.width > MAX_DIM || originalImg.height > MAX_DIM) {
scale = MAX_DIM / Math.max(originalImg.width, originalImg.height);
}
const w = Math.round(originalImg.width * scale);
const h = Math.round(originalImg.height * scale);
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Draw the original scaled image
ctx.drawImage(originalImg, 0, 0, w, h);
// Apply Fisheye / Barrel Distortion
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
const outData = new Uint8ClampedArray(data.length);
const cx = w / 2;
const cy = h / 2;
const diag = Math.sqrt(cx * cx + cy * cy);
const k = Number(fisheyeIntensity);
const factor = 1 / (1 + k);
// Using bilinear interpolation
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const nx = (x - cx) / diag;
const ny = (y - cy) / diag;
const r2 = nx * nx + ny * ny;
// Calculate mapping ratio for barrel distortion
const map_ratio = (1 + k * r2) * factor;
const sx = cx + (x - cx) * map_ratio;
const sy = cy + (y - cy) * map_ratio;
const dstIdx = (y * w + x) * 4;
if (sx >= 0 && sx < w - 1 && sy >= 0 && sy < h - 1) {
const isx = Math.floor(sx);
const isy = Math.floor(sy);
const fx = sx - isx;
const fy = sy - isy;
const idx1 = (isy * w + isx) * 4;
const idx2 = idx1 + 4;
const idx3 = ((isy + 1) * w + isx) * 4;
const idx4 = idx3 + 4;
for (let c = 0; c < 3; c++) {
const top = data[idx1 + c] * (1 - fx) + data[idx2 + c] * fx;
const bot = data[idx3 + c] * (1 - fx) + data[idx4 + c] * fx;
outData[dstIdx + c] = top * (1 - fy) + bot * fy;
}
outData[dstIdx + 3] = 255;
} else {
// Out of bounds mapped to black
outData[dstIdx] = 0;
outData[dstIdx + 1] = 0;
outData[dstIdx + 2] = 0;
outData[dstIdx + 3] = 255;
}
}
}
ctx.putImageData(new ImageData(outData, w, h), 0, 0);
// Apply Vignette Effect
if (Number(vignetteOpacity) > 0) {
const gradient = ctx.createRadialGradient(cx, cy, diag * 0.45, cx, cy, diag);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${Number(vignetteOpacity)})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
// Smart Doorbell UI Overlay Styling
const padding = Math.min(w, h) * 0.04;
const fontSize = Math.max(14, Math.min(w, h) * 0.035);
ctx.font = `bold ${fontSize}px "Segoe UI", "Helvetica Neue", Arial, sans-serif`;
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
ctx.shadowColor = 'rgba(0, 0, 0, 0.85)';
ctx.shadowBlur = 6;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.textBaseline = 'top';
// Camera Label (Top-Left)
ctx.textAlign = 'left';
ctx.fillText(camLabel.toUpperCase(), padding, padding);
// Timestamp (Top-Right)
let displayTime = timestamp;
if (displayTime === "auto") {
const now = new Date();
const pad = (n) => n.toString().padStart(2, '0');
const dateStr = `${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())}`;
const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
displayTime = `${dateStr} ${timeStr}`;
}
ctx.textAlign = 'right';
ctx.fillText(displayTime, w - padding, padding);
// Recording indicator dot next to Timestamp
const textWidth = ctx.measureText(displayTime).width;
ctx.beginPath();
const dotRadius = fontSize * 0.35;
ctx.arc(w - padding - textWidth - padding * 0.7, padding + fontSize * 0.5, dotRadius, 0, Math.PI * 2);
ctx.fillStyle = '#ff3333';
ctx.shadowColor = 'rgba(255, 50, 50, 0.7)';
ctx.shadowBlur = 8;
ctx.fill();
// Reset shadow properties for UI shapes
ctx.shadowBlur = 4;
ctx.shadowColor = 'black';
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
// Battery Icon (Bottom-Right)
ctx.lineWidth = Math.max(2, fontSize * 0.1);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)';
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
const batW = fontSize * 1.8;
const batH = fontSize * 0.9;
const batX = w - padding - batW;
const batY = h - padding - batH;
ctx.strokeRect(batX, batY, batW, batH);
ctx.fillRect(batX + 2, batY + 2, batW * 0.65 - 4, batH - 4); // Simulate 65% charge
ctx.fillRect(batX + batW + 2, batY + batH * 0.25, batW * 0.1, batH * 0.5); // Battery terminal
// Watermark / Brand substitute (Bottom-Left)
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
ctx.font = `italic 900 ${fontSize * 1.2}px "Arial Black", Arial, sans-serif`;
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillText("DOORBELL", padding, h - padding);
return canvas;
}
Apply Changes