You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
pixelation = "2",
brightness = "1.2",
contrast = "1.2",
noiseIntensity = "35",
rgbShiftAmount = "4",
glitchIntensity = "15",
vignetteIntensity = "0.7",
colorTint = "grayscale", // options: "none", "amber", "green", "blue", "grayscale"
timestamp = "1998-05-12 14:32:01 PLAY",
showRec = "true"
) {
// Parse parameters
const pix = Math.max(1, parseInt(pixelation) || 2);
const b = parseFloat(brightness) || 1.2;
const c = parseFloat(contrast) || 1.2;
const noiseAmt = parseFloat(noiseIntensity) || 35;
const shiftOrig = parseFloat(rgbShiftAmount) || 4;
const glitchAmt = parseFloat(glitchIntensity) || 15;
const vigAmt = parseFloat(vignetteIntensity) || 0.7;
const isRec = String(showRec).toLowerCase() === 'true' || showRec === '1';
const tint = String(colorTint).toLowerCase();
// Canvas setup
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Pixelation Phase
const miniCanvas = document.createElement('canvas');
miniCanvas.width = Math.ceil(width / pix);
miniCanvas.height = Math.ceil(height / pix);
const miniCtx = miniCanvas.getContext('2d');
miniCtx.drawImage(originalImg, 0, 0, miniCanvas.width, miniCanvas.height);
const midCanvas = document.createElement('canvas');
midCanvas.width = width;
midCanvas.height = height;
const midCtx = midCanvas.getContext('2d');
midCtx.imageSmoothingEnabled = false;
midCtx.drawImage(miniCanvas, 0, 0, width, height);
// 2. VHS Glitch Phase (Horizontal shifts)
const offCanvas = document.createElement('canvas');
offCanvas.width = width;
offCanvas.height = height;
const offCtx = offCanvas.getContext('2d');
// Fill black background in case glitch leaves transparent gaps
offCtx.fillStyle = 'black';
offCtx.fillRect(0, 0, width, height);
let yOffset = 0;
while (yOffset < height) {
let sliceHeight = Math.random() * (height * 0.05) + (height * 0.01);
if (yOffset + sliceHeight > height) sliceHeight = height - yOffset;
let xOffset = 0;
// Apply random intense glitch to ~15% of slices
if (Math.random() < 0.15) {
xOffset = (Math.random() - 0.5) * glitchAmt * (width / 500);
}
offCtx.drawImage(
midCanvas,
0, yOffset, width, sliceHeight,
xOffset, yOffset, width, sliceHeight
);
yOffset += sliceHeight;
}
// 3. Pixel Manipulation (RGB Shift, Noise, Brightness, Contrast, Tint)
const imgData = offCtx.getImageData(0, 0, width, height);
const data = imgData.data;
const outData = new Uint8ClampedArray(data.length);
// Scale shift relative to image width so it has consistent effect
const shiftPix = Math.floor(shiftOrig * Math.max(1, width / 1000));
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
// Chromatic aberration (RGB shift)
const iRed = (y * width + Math.max(0, x - shiftPix)) * 4;
const iBlue = (y * width + Math.min(width - 1, x + shiftPix)) * 4;
let red = data[iRed];
let green = data[i + 1];
let blue = data[iBlue + 2];
// Brightness & Contrast
red = ((red * b) - 128) * c + 128;
green = ((green * b) - 128) * c + 128;
blue = ((blue * b) - 128) * c + 128;
// Static Noise
const noise = (Math.random() - 0.5) * noiseAmt * 2;
red += noise;
green += noise;
blue += noise;
// Color Tint
if (tint !== 'none') {
const luma = 0.299 * red + 0.587 * green + 0.114 * blue;
if (tint === 'amber') {
red = luma * 1.2;
green = luma * 0.9;
blue = luma * 0.2;
} else if (tint === 'green') {
red = luma * 0.2;
green = luma * 1.2;
blue = luma * 0.2;
} else if (tint === 'blue') {
red = luma * 0.3;
green = luma * 0.5;
blue = luma * 1.3;
} else { // grayscale
red = luma;
green = luma;
blue = luma;
}
}
outData[i] = red;
outData[i + 1] = green;
outData[i + 2] = blue;
outData[i + 3] = 255; // fully opaque
}
}
const newImgData = new ImageData(outData, width, height);
ctx.putImageData(newImgData, 0, 0);
// 4. CRT Scanlines
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
for (let y = 0; y < height; y += 4) {
ctx.fillRect(0, y, width, 2);
}
// 5. VHS Bottom Tracking Distortions
ctx.fillStyle = 'rgba(255, 255, 255, 0.07)';
for(let i = 0; i < 8; i++) {
let trackY = height - Math.random() * (height * 0.15);
let trackH = Math.random() * 6 + 1;
ctx.fillRect(0, trackY, width, trackH);
}
// 6. Vignette Effect
const cx = width / 2;
const cy = height / 2;
const maxRadius = Math.max(cx, cy) * 1.2;
const gradient = ctx.createRadialGradient(cx, cy, maxRadius * 0.4, cx, cy, maxRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${Math.min(1, vigAmt)})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// 7. Security Overlay (Timestamp & REC)
const fontSize = Math.max(16, Math.floor(height * 0.04));
ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
ctx.textBaseline = 'middle';
// Text styling (glow/shadow to make it readable)
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillStyle = 'white';
const paddingX = Math.max(10, Math.floor(width * 0.05));
const paddingY = Math.max(10, Math.floor(height * 0.05));
// Draw Timestamp (Bottom Left)
if (timestamp && timestamp.trim() !== '') {
ctx.fillText(timestamp, paddingX, height - paddingY - fontSize / 2);
}
// Draw REC indicator (Top Right)
if (isRec) {
const recText = "REC";
const recWidth = ctx.measureText(recText).width;
const textX = width - paddingX - recWidth;
const textY = paddingY + fontSize / 2;
ctx.fillText(recText, textX, textY);
// Red flashing dot (simulated as static on photo)
const dotRadius = fontSize * 0.3;
ctx.fillStyle = '#ff0000';
ctx.shadowColor = 'rgba(255,0,0,0.8)'; // Red glow
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(textX - dotRadius * 2, textY, dotRadius, 0, Math.PI * 2);
ctx.fill();
}
return canvas;
}
Apply Changes