You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, aberrationOffset = 6, noiseAmount = 30, enableVHSOverlay = "true") {
// Create an off-screen canvas to process the image
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw the initial original image
ctx.drawImage(originalImg, 0, 0, width, height);
// Get pixel data to apply the HIFI VHS 16mm effect
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
const newImgData = ctx.createImageData(width, height);
const newData = newImgData.data;
// Parse parameters
const shift = parseInt(aberrationOffset, 10);
const noise = parseInt(noiseAmount, 10);
const addOverlay = String(enableVHSOverlay).toLowerCase() === "true";
// Setup contrast boost factor
const contrast = 30;
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
// Process pixel array
for (let y = 0; y < height; y++) {
let rowShift = 0;
// Emulate VHS tracking distortion banded at the bottom
const isTrackingBand = y > height * 0.85 && y < height * 0.92;
if (isTrackingBand) {
rowShift = Math.floor(Math.sin(y * 0.5) * 8);
rowShift += Math.floor(Math.random() * 6) - 3; // Jitter effect
}
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
// Chromatic aberration (RGB shift)
let rx = Math.min(Math.max(x + shift + rowShift, 0), width - 1);
let gx = Math.min(Math.max(x + rowShift, 0), width - 1);
let bx = Math.min(Math.max(x - shift + rowShift, 0), width - 1);
const rIdx = (y * width + rx) * 4;
const gIdx = (y * width + gx) * 4 + 1;
const bIdx = (y * width + bx) * 4 + 2;
let r = data[rIdx];
let g = data[gIdx];
let b = data[bIdx];
// 1. Contrast Adjustment
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
// 2. Color tint (warm vintage 16mm feel / color fade)
r += 15;
b -= 15;
// 3. Color compression / subtle clipping for low fidelity
r = Math.floor(r / 8) * 8;
g = Math.floor(g / 8) * 8;
b = Math.floor(b / 8) * 8;
// 4. Heavy film/VHS noise
const n = (Math.random() - 0.5) * noise;
r += n;
g += n;
b += n;
// 5. White static within the tracking band
if (isTrackingBand && Math.random() > 0.6) {
const staticNoise = 180 + Math.random() * 75;
r = staticNoise;
g = staticNoise;
b = staticNoise;
}
// 6. CRT Scanlines (darken every 3rd row)
if (y % 3 === 0) {
const sl = 0.70;
r *= sl;
g *= sl;
b *= sl;
}
// Storing the processed pixels (Uint8ClampedArray handles 0-255 bounds automatically)
newData[i] = r;
newData[i + 1] = g;
newData[i + 2] = b;
newData[i + 3] = data[(y * width + x) * 4 + 3]; // preserve original alpha
}
}
// Paint the processed image back
ctx.putImageData(newImgData, 0, 0);
// Apply Vignette overlay (darkened corners seen in 16mm projections)
const mx = width / 2;
const my = height / 2;
const radius = Math.max(width, height);
const gradient = ctx.createRadialGradient(mx, my, radius * 0.4, mx, my, radius * 0.8);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0.7)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Apply Camcorder/VCR On-Screen Display (OSD)
if (addOverlay) {
ctx.fillStyle = "rgba(230, 230, 230, 0.85)";
// Font scalling
let fontSize = Math.max(20, Math.floor(height * 0.06));
ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
ctx.shadowColor = "black";
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
// VCR Play Tag
ctx.fillText("PLAY ►", width * 0.05, height * 0.06 + fontSize);
// HIFI Indicator
ctx.fillStyle = "rgba(255, 60, 60, 0.9)";
ctx.font = `bold ${fontSize * 0.8}px "Courier New", Courier, monospace`;
ctx.fillText("HIFI STEREO", width * 0.05, height * 0.95);
// Running Timecode
ctx.fillStyle = "rgba(230, 230, 230, 0.9)";
const hrs = "0" + Math.floor(Math.random() * 3);
const mins = String(Math.floor(Math.random() * 60)).padStart(2, '0');
const secs = String(Math.floor(Math.random() * 60)).padStart(2, '0');
ctx.textAlign = "right";
ctx.fillText(`SP ${hrs}:${mins}:${secs}`, width * 0.95, height * 0.95);
}
return canvas;
}
Apply Changes