You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, level = 'medium') {
const w = originalImg.naturalWidth || originalImg.width;
const h = originalImg.naturalHeight || originalImg.height;
if (!w || !h) {
throw new Error("Invalid image dimensions.");
}
let pixelHeight, brightness, blurAmount, motionOffset, noiseLevel, jpegQuality, scanlineAlpha;
// Define CCTV severity levels parameters
switch (level.toLowerCase()) {
case 'mild':
pixelHeight = 240;
brightness = 80;
blurAmount = 2; // px
motionOffset = Math.max(3, w * 0.015);
noiseLevel = 30;
jpegQuality = 0.4;
scanlineAlpha = 0.15;
break;
case 'harsh':
pixelHeight = 96;
brightness = 40;
blurAmount = 8;
motionOffset = Math.max(15, w * 0.05);
noiseLevel = 90;
jpegQuality = 0.05; // Extreme compression artifacts
scanlineAlpha = 0.4;
break;
case 'medium':
default:
pixelHeight = 144;
brightness = 60;
blurAmount = 5;
motionOffset = Math.max(8, w * 0.03);
noiseLevel = 60;
jpegQuality = 0.15;
scanlineAlpha = 0.25;
break;
}
// 1. Lower Resolution (Downscale)
const scale = Math.min(1.0, pixelHeight / h);
const smW = Math.max(1, Math.floor(w * scale));
const smH = Math.max(1, Math.floor(h * scale));
const smallCanvas = document.createElement('canvas');
smallCanvas.width = smW;
smallCanvas.height = smH;
const smCtx = smallCanvas.getContext('2d');
smCtx.imageSmoothingEnabled = true;
smCtx.drawImage(originalImg, 0, 0, smW, smH);
// 2. Setup Main Canvas
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Turn off image smoothing during upscale to create harsh NEAREST-NEIGHBOR pixelation
ctx.imageSmoothingEnabled = false;
// Apply baseline dark lighting and core blur
ctx.filter = `brightness(${brightness}%) blur(${blurAmount}px)`;
// Apply Motion Blur: Draw the small canvas upscaled multiple times thinly with an offset scatter
const motionSteps = 6;
ctx.globalAlpha = 1.0 / motionSteps;
for (let i = 0; i < motionSteps; i++) {
// Compute offset horizontally along an assumed movement vector
const dx = (i - Math.floor(motionSteps / 2)) * (motionOffset / motionSteps);
const dy = dx * 0.1; // Minimal vertical drift
ctx.drawImage(smallCanvas, 0, 0, smW, smH, dx, dy, w, h);
}
// Reset properties
ctx.filter = 'none';
ctx.globalAlpha = 1.0;
// 3. Compression Artifacts (JPEG encoding/decoding technique to recreate macro-blocks naturally)
const dataUrl = canvas.toDataURL('image/jpeg', jpegQuality);
const jpegImg = await new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => resolve(canvas); // Fallback to current canvas data
img.src = dataUrl;
});
ctx.clearRect(0, 0, w, h);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(jpegImg, 0, 0, w, h);
// 4. Heavy Noise Application (Manual additive normal-distributed static)
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
// Fast approximation of normally distributed random number logic
const randNormal = (Math.random() + Math.random() + Math.random() - 1.5) * 2;
const uniformNoise = randNormal * noiseLevel;
// Apply noise and clamp limits between 0-255 using Math max/min
data[i] = Math.max(0, Math.min(255, data[i] + uniformNoise)); // R
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + uniformNoise)); // G
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + uniformNoise)); // B
// Alpha (data[i + 3]) is left untouched
}
ctx.putImageData(imgData, 0, 0);
// 5. Scan Lines
const maxScanlines = 600;
const scanlineCount = Math.min(h, maxScanlines);
const lineThickness = Math.max(1, Math.floor(h / scanlineCount));
const lineGap = lineThickness * 2;
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineAlpha})`;
for (let y = 0; y < h; y += lineGap) {
ctx.fillRect(0, y, w, lineThickness);
}
// 6. Generic CCTV Overlay UI Overlay (Text Date / Time)
const fontSize = Math.max(12, Math.floor(Math.min(w, h) * 0.04));
ctx.font = `${fontSize}px "Courier New", Courier, monospace`;
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
ctx.textBaseline = "top";
// Top-Left "REC" with Record Dot
ctx.fillText("REC ●", w * 0.04, h * 0.04);
// Bottom-Left Simulated Timestamp
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const min = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
ctx.textBaseline = "bottom";
ctx.fillText(`CAM-01 ${yyyy}-${mm}-${dd} ${hh}:${min}:${ss}`, w * 0.04, h * 0.96);
return canvas;
}
Apply Changes