You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, localizedText = "HIFI STEREO DUB - COMMENCING PLAYBACK", vhsMode = "PLAY", intensity = 5, topText = "HIFI STEREO") {
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 original image
ctx.drawImage(originalImg, 0, 0);
// Extract image data for pixel manipulation
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Create new image data container for manipulated pixels
const newImgData = ctx.createImageData(width, height);
const newData = newImgData.data;
// Normalize parameters
const shiftScale = Math.max(1, Math.round(width * 0.002 * intensity));
const normalizedIntensity = Math.min(10, Math.max(0, intensity));
// Generate VHS tracking distortion bands
const trackingBands = [];
const numBands = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < numBands; i++) {
trackingBands.push({
y: Math.floor(Math.random() * height),
h: Math.floor(height * 0.04 * (Math.random() + 0.5)),
offset: Math.floor((Math.random() * 10 - 5) * normalizedIntensity)
});
}
// Process pixels for chromatic aberration, tracking blur, and noise static
for (let y = 0; y < height; y++) {
let bandOffset = 0;
let inBand = false;
// Determine if current row is inside a distorted tracking band
for (const band of trackingBands) {
if (y >= band.y && y < band.y + band.h) {
inBand = true;
bandOffset = band.offset + Math.floor((Math.random() * 4 - 2) * normalizedIntensity);
break;
}
}
// Occasional independent line tracking noise
const isLineNoise = Math.random() < (0.01 * normalizedIntensity);
const lineOffset = isLineNoise ? Math.floor(Math.random() * 10 - 5) : 0;
const totalOffset = bandOffset + lineOffset;
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
// Chromatic shift (RGB separation)
let rX = x + shiftScale + totalOffset;
let gX = x + totalOffset;
let bX = x - shiftScale + totalOffset;
// Constrain out-of-bounds to horizontal edges
rX = Math.min(width - 1, Math.max(0, rX));
gX = Math.min(width - 1, Math.max(0, gX));
bX = Math.min(width - 1, Math.max(0, bX));
let rIndex = (y * width + rX) * 4;
let gIndex = (y * width + gX) * 4;
let bIndex = (y * width + bX) * 4;
newData[index] = data[rIndex]; // Red channel
newData[index+1] = data[gIndex+1]; // Green channel
newData[index+2] = data[bIndex+2]; // Blue channel
newData[index+3] = data[index+3]; // Alpha channel
// Apply randomized VHS noise
if (inBand || Math.random() < 0.05 * normalizedIntensity) {
const noiseAmount = Math.random() * 40 * (inBand ? 2 : 1) * (normalizedIntensity / 5);
const noiseSign = Math.random() > 0.5 ? 1 : -1;
const noise = noiseAmount * noiseSign;
// Heavy static specs vs subtle color noise
if (Math.random() < 0.1) {
const staticVal = Math.random() > 0.5 ? 255 : 0;
newData[index] = (newData[index] + staticVal) / 2;
newData[index+1] = (newData[index+1] + staticVal) / 2;
newData[index+2] = (newData[index+2] + staticVal) / 2;
} else {
newData[index] = Math.max(0, Math.min(255, newData[index] + noise));
newData[index+1] = Math.max(0, Math.min(255, newData[index+1] + noise));
newData[index+2] = Math.max(0, Math.min(255, newData[index+2] + noise));
}
}
}
}
ctx.putImageData(newImgData, 0, 0);
// Render CRT Scanlines
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
for (let i = 0; i < height; i += 4) {
ctx.fillRect(0, i, width, 1);
}
ctx.fillStyle = 'rgba(255, 255, 255, 0.05)';
for (let i = 2; i < height; i += 4) {
ctx.fillRect(0, i, width, 1);
}
// VHS Overlay / OSD text styling
const osdFontSize = Math.max(20, Math.floor(height * 0.06));
ctx.font = `bold ${osdFontSize}px 'Courier New', Courier, monospace`;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.shadowColor = 'black';
ctx.shadowBlur = Math.max(2, Math.floor(height * 0.005));
ctx.shadowOffsetX = Math.max(1, Math.floor(width * 0.003));
ctx.shadowOffsetY = Math.max(1, Math.floor(height * 0.003));
// Status Indicator
ctx.fillStyle = '#00ff00';
ctx.fillText(`${vhsMode} ►`, width * 0.05, height * 0.05);
// HIFI STEREO Label
ctx.textAlign = 'right';
ctx.fillStyle = '#ffffff';
ctx.fillText(topText, width * 0.95, height * 0.05);
// Retro Timecode
ctx.textAlign = 'left';
ctx.fillText("AM 12:00", width * 0.05, height * 0.85);
// Dubbing/Localization Subtitle
if (localizedText) {
ctx.shadowBlur = Math.max(4, Math.floor(height * 0.01));
const subFontSize = Math.max(24, Math.floor(height * 0.07));
ctx.font = `bold ${subFontSize}px Arial, Helvetica, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
const subX = width / 2;
const subY = height * 0.96;
ctx.lineWidth = Math.max(2, Math.floor(height * 0.006));
ctx.strokeStyle = 'black';
ctx.strokeText(localizedText, subX, subY);
ctx.fillStyle = '#f1f114'; // Classic subtitle yellow
ctx.fillText(localizedText, subX, subY);
}
return canvas;
}
Apply Changes