You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
subtitleText = "I'VE BEEN WAITING FOR THIS MOMENT.",
videoTitle = "VHS_RIP_HIFI_MONO_DUB.avi",
timeCode = "00:24:12 / 01:45:00",
glitchIntensity = 10,
noiseLevel = 25
) {
// Ensure numeric values
glitchIntensity = Number(glitchIntensity) || 10;
noiseLevel = Number(noiseLevel) || 25;
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const ctx = canvas.getContext('2d');
// 1. Draw base image with slight blur & contrast adjustment for the VHS/DVDRip feel
ctx.filter = 'contrast(1.2) saturate(0.85) blur(0.6px)';
ctx.drawImage(originalImg, 0, 0);
ctx.filter = 'none';
// Scale multiplier for relative UI sizing
const scale = Math.max(1, canvas.width / 800);
// 2. Apply Chromatic Aberration and Grain Noise
if (glitchIntensity > 0 || noiseLevel > 0) {
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
const outData = new Uint8ClampedArray(data);
// Pixel drift amount
const shiftR = Math.floor(canvas.width * (glitchIntensity / 2000));
const shiftB = -Math.floor(canvas.width * (glitchIntensity / 2000));
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const i = (y * canvas.width + x) * 4;
// Random TV static/noise
const noise = (Math.random() - 0.5) * noiseLevel;
const rx = Math.max(0, Math.min(canvas.width - 1, x - shiftR));
const bx = Math.max(0, Math.min(canvas.width - 1, x - shiftB));
const iR = (y * canvas.width + rx) * 4;
const iB = (y * canvas.width + bx) * 4;
outData[i] = Math.min(255, Math.max(0, data[iR] + noise)); // Red Channel Shift + Noise
outData[i+1] = Math.min(255, Math.max(0, data[i+1] + noise)); // Green Channel Base + Noise
outData[i+2] = Math.min(255, Math.max(0, data[iB+2] + noise)); // Blue Channel Shift + Noise
outData[i+3] = data[i+3]; // Alpha Channel
}
}
ctx.putImageData(new ImageData(outData, canvas.width, canvas.height), 0, 0);
}
// 3. VHS Scanlines
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
for (let y = 0; y < canvas.height; y += 4) {
ctx.fillRect(0, y, canvas.width, 1.5);
}
ctx.fillStyle = 'rgba(255, 255, 255, 0.05)';
for (let y = 2; y < canvas.height; y += 4) {
ctx.fillRect(0, y, canvas.width, 1);
}
// 4. Horizontal Glitch Tears (Tracking error)
const numTears = Math.floor(Math.random() * glitchIntensity);
for (let j = 0; j < numTears; j++) {
const tearY = Math.random() * canvas.height;
const tearH = Math.random() * 15 * scale;
const tearX = (Math.random() - 0.5) * glitchIntensity * 6 * scale;
ctx.drawImage(canvas, 0, tearY, canvas.width, tearH, tearX, tearY, canvas.width, tearH);
}
// 5. Video Player Platform Overlay (Screenshot interface look)
const barHeight = 45 * scale;
// Bottom vignette to make subs / player legible
const grad = ctx.createLinearGradient(0, canvas.height - barHeight - 90 * scale, 0, canvas.height);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, 'rgba(0,0,0,0.85)');
ctx.fillStyle = grad;
ctx.fillRect(0, canvas.height - barHeight - 90 * scale, canvas.width, barHeight + 90 * scale);
// Player bottom bar
ctx.fillStyle = 'rgba(15, 15, 15, 0.9)';
ctx.fillRect(0, canvas.height - barHeight, canvas.width, barHeight);
// Progress bar timeline
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(0, canvas.height - barHeight, canvas.width, 4 * scale);
ctx.fillStyle = '#ff0000'; // Active tracking playback
const progress = 0.35 + Math.random() * 0.4; // random position between 35% - 75%
ctx.fillRect(0, canvas.height - barHeight, canvas.width * progress, 4 * scale);
ctx.beginPath();
ctx.arc(canvas.width * progress, canvas.height - barHeight + 2 * scale, 6 * scale, 0, Math.PI * 2);
ctx.fill();
// Player Toolbar Icons Let side (Play, skip, volume)
ctx.fillStyle = '#FFFFFF';
ctx.font = `${18 * scale}px Arial`;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText("▶", 20 * scale, canvas.height - barHeight / 2);
ctx.font = `${16 * scale}px Arial`;
ctx.fillText("⏭", 55 * scale, canvas.height - barHeight / 2);
ctx.fillText("🔊", 95 * scale, canvas.height - barHeight / 2);
// Timecode
ctx.font = `${14 * scale}px "Lucida Console", Monaco, monospace`;
ctx.fillText(timeCode, 130 * scale, canvas.height - barHeight / 2 + 1 * scale);
// Player Toolbar Icons Right side (Settings, Fullscreen)
ctx.textAlign = 'right';
ctx.fillText("⚙", canvas.width - 60 * scale, canvas.height - barHeight / 2);
ctx.font = `${16 * scale}px Arial`;
ctx.fillText("⛶", canvas.width - 25 * scale, canvas.height - barHeight / 2);
// VCR / Player OSD at the top-left (Retro UI style)
ctx.font = `bold ${24 * scale}px "Courier New", Courier, monospace`;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = '#00FF00'; // Retro VCR green
ctx.shadowOffsetX = 2 * scale;
ctx.shadowOffsetY = 2 * scale;
ctx.shadowColor = 'rgba(0,0,0,1)';
ctx.shadowBlur = 4 * scale;
ctx.fillText("PLAY ►", 25 * scale, 25 * scale);
ctx.font = `${16 * scale}px "Courier New", Courier, monospace`;
ctx.fillStyle = '#FFFFFF'; // Info text
ctx.fillText(`FILE: ${videoTitle}`, 25 * scale, 55 * scale);
// Reset shadow
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
// 6. Mono Language Dub Translator Text (Stylized DVDRip subtitles)
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.font = `bold ${36 * scale}px Arial, Helvetica, sans-serif`;
ctx.lineWidth = 5 * scale;
ctx.strokeStyle = '#000000';
ctx.fillStyle = '#FFDD00'; // High-contrast rip yellow
// Support multi-line subtitles separated by actual newline or \n strings
const lines = String(subtitleText).replace(/\\n/g, '\n').split('\n');
let currentY = canvas.height - barHeight - 25 * scale - (lines.length - 1) * (40 * scale);
lines.forEach(line => {
// Render outline
ctx.lineJoin = 'round';
ctx.miterLimit = 2;
ctx.strokeText(line, canvas.width / 2, currentY);
// Render inside text
ctx.fillText(line, canvas.width / 2, currentY);
currentY += 40 * scale;
});
return canvas;
}
Apply Changes