You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, scanlineIntensity = "0.25", noiseIntensity = "25", colorShift = "3", contrastOffset = "20") {
// Parse parameters
const scanIntensity = Math.min(Math.max(Number(scanlineIntensity), 0), 1);
const noise = Number(noiseIntensity);
const shift = Math.floor(Number(colorShift));
const contrast = Number(contrastOffset);
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Fill with black to handle transparent images (TVs generally don't have transparency)
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, width, height);
// Draw the original image
ctx.drawImage(originalImg, 0, 0);
// Get pixel data for manipulation
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Create a copy of the pixel data to read from (for chromatic aberration/RGB shift)
const copyData = new Uint8ClampedArray(data);
// Pre-calculate contrast formula factor
// Algorithm: F = (259 * (C + 255)) / (255 * (259 - C))
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (let y = 0; y < height; y++) {
// Interlaced TV scanline effect (darken every other line)
const sl = (y % 2 === 0) ? (1 - scanIntensity) : 1;
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
// Calculate RGB shift indices
let rx = x - shift;
if (rx < 0) rx = 0;
const ri = (y * width + rx) * 4;
let bx = x + shift;
if (bx >= width) bx = width - 1;
const bi = (y * width + bx) * 4;
// Read R from left-shifted position, G from center, B from right-shifted position
let r = copyData[ri];
let g = copyData[i + 1];
let b = copyData[bi + 2];
// 1. Apply Contrast
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
// 2. Apply Static Noise
const n = (Math.random() - 0.5) * noise * 2;
r += n;
g += n;
b += n;
// 3. Apply Scanlines
r *= sl;
g *= sl;
b *= sl;
// Write manipulated colors back to array buffers
// Note: Uint8ClampedArray automatically clamps values between 0 and 255
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = copyData[i + 3]; // Preserve original alpha
}
}
// Put modified data back onto canvas
ctx.putImageData(imgData, 0, 0);
// 4. Apply Vignette (darkened corners)
const cx = width / 2;
const cy = height / 2;
const maxRadius = Math.sqrt(cx * cx + cy * cy);
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, 0.65)');
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// 5. Apply subtle CRT phosphor screen glow
ctx.globalCompositeOperation = 'screen';
ctx.fillStyle = 'rgba(20, 35, 20, 0.1)';
ctx.fillRect(0, 0, width, height);
// Reset composite operation to default
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes