You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = 50, rgbShift = 5, showText = 'true', addScanlines = 'true') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const w = originalImg.naturalWidth || originalImg.width;
const h = originalImg.naturalHeight || originalImg.height;
canvas.width = w;
canvas.height = h;
// Draw original image as the base
ctx.drawImage(originalImg, 0, 0, w, h);
const intIntensity = typeof intensity === 'string' ? parseFloat(intensity) : intensity;
const intRgbShift = typeof rgbShift === 'string' ? parseFloat(rgbShift) : rgbShift;
// 1. Slices (Horizontal Tearing)
const numSlices = Math.floor((intIntensity / 100) * 40);
for (let i = 0; i < numSlices; i++) {
const sliceY = Math.random() * h;
const sliceH = Math.random() * (h * 0.15); // Slice height up to 15% of canvas height
const shiftX = (Math.random() - 0.5) * (intIntensity / 50) * (w * 0.1);
// Copy a slice of the canvas and draw it shifted horizontally
ctx.drawImage(
canvas,
0, sliceY, w, sliceH,
shiftX, sliceY, w, sliceH
);
}
// 2. RGB Chromatic Aberration and VHS Noise
try {
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
const outData = new Uint8ClampedArray(data.length);
const rShift = Math.floor(intRgbShift);
const bShift = -Math.floor(intRgbShift);
const noiseIntensity = intIntensity * 0.05;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const idx = (y * w + x) * 4;
// Calculate indices for R and B channels shifted
const xR = Math.min(w - 1, Math.max(0, x + rShift));
const xB = Math.min(w - 1, Math.max(0, x + bShift));
const idxR = (y * w + xR) * 4;
const idxB = (y * w + xB) * 4;
// Apply random noise based on intensity
let noise = 0;
if (Math.random() < 0.25) { // 25% of pixels get noise
noise = (Math.random() - 0.5) * noiseIntensity * 255;
}
// Uint8ClampedArray handles clamping to 0-255 automatically
outData[idx] = data[idxR] + noise; // R
outData[idx + 1] = data[idx + 1] + noise; // G
outData[idx + 2] = data[idxB + 2] + noise; // B
outData[idx + 3] = data[idx + 3]; // A
}
}
// Write shifted pixel data back to canvas
imgData.data.set(outData);
ctx.putImageData(imgData, 0, 0);
} catch (e) {
console.warn("CORS issue preventing RGB shifting, applying slice/tear fallback only.", e);
}
// 3. VHS Scanlines
const drawScanlines = String(addScanlines).toLowerCase() === 'true' || String(addScanlines).toLowerCase() === 'yes';
if (drawScanlines) {
ctx.fillStyle = `rgba(0, 0, 0, 0.2)`;
for (let y = 0; y < h; y += 4) {
ctx.fillRect(0, y, w, 2);
}
}
// 4. "LOADING" UI Text and Bar (Video Load Error Effect)
const drawText = String(showText).toLowerCase() === 'true' || String(showText).toLowerCase() === 'yes';
if (drawText) {
const cx = w / 2;
const cy = h / 2;
const fontSize = Math.max(20, Math.floor(h * 0.08));
ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const txt = "LOADING... ERROR";
// Red chromatic text shadow
ctx.fillStyle = 'rgba(255, 0, 0, 0.8)';
ctx.fillText(txt, cx - intRgbShift, cy - intRgbShift * 0.3);
// Cyan chromatic text shadow
ctx.fillStyle = 'rgba(0, 255, 255, 0.8)';
ctx.fillText(txt, cx + intRgbShift, cy + intRgbShift * 0.3);
// White main text
ctx.fillStyle = 'white';
ctx.fillText(txt, cx, cy);
// Loading Progress Bar
const barW = w * 0.4;
const barH = Math.max(10, h * 0.05);
const barX = cx - barW / 2;
const barY = cy + fontSize;
const progress = 0.78; // Stuck loading at 78%
// Bar background
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(barX, barY, barW, barH);
// Red Shift Border
ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';
ctx.lineWidth = 3;
ctx.strokeRect(barX - intRgbShift, barY, barW, barH);
// Cyan Shift Border
ctx.strokeStyle = 'rgba(0, 255, 255, 0.8)';
ctx.strokeRect(barX + intRgbShift, barY, barW, barH);
// White Border
ctx.strokeStyle = 'white';
ctx.strokeRect(barX, barY, barW, barH);
// Filled progress (mostly white, occasionally glitching out as red)
ctx.fillStyle = Math.random() > 0.7 ? 'rgba(255,0,0,0.9)' : 'white';
// Add random displacement slice shift to the progress bar filling
const displacement = (Math.random() - 0.5) * (w * 0.02);
ctx.fillRect(barX + 2 + displacement, barY + 2, (barW - 4) * progress, barH - 4);
}
return canvas;
}
Apply Changes