You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies various video-like effects to a static image.
*
* @param {Image} originalImg The original source image object.
* @param {string} effectName The name of the effect to apply. Available options: 'glitch', 'noise', 'scanlines', 'oldfilm', 'rgbsplit', 'pixelate', 'invert', 'sepia'. Defaults to 'glitch'.
* @param {number} intensity A value between 0.0 and 1.0 that controls the strength of the effect. Defaults to 0.5.
* @returns {HTMLCanvasElement} A new canvas element with the applied effect.
*/
function processImage(originalImg, effectName = 'glitch', intensity = 0.5) {
const canvas = document.createElement('canvas');
// Using willReadFrequently is a performance hint for browsers for frequent getImageData calls.
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
const w = originalImg.naturalWidth;
const h = originalImg.naturalHeight;
canvas.width = w;
canvas.height = h;
// Clamp intensity to a valid 0.0 - 1.0 range
intensity = Math.max(0, Math.min(1, intensity));
// For most effects, we draw the image first. Pixelate is an exception.
if (effectName.toLowerCase() !== 'pixelate') {
ctx.drawImage(originalImg, 0, 0, w, h);
}
switch (effectName.toLowerCase()) {
case 'glitch':
{
if (intensity === 0) break;
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
const originalData = new Uint8ClampedArray(data);
for (let y = 0; y < h; y++) {
// Introduce a random chance to start a glitch strip
if (Math.random() < intensity * 0.05) {
const blockHeight = Math.floor(Math.random() * h * 0.1) + 1;
const xOffset = Math.floor((Math.random() - 0.5) * w * 0.2 * intensity);
for (let j = y; j < y + blockHeight && j < h; j++) {
for (let x = 0; x < w; x++) {
const sourceIndex = (j * w + x) * 4;
let targetX = x + xOffset;
if (targetX >= 0 && targetX < w) {
const targetIndex = (j * w + targetX) * 4;
data[targetIndex] = originalData[sourceIndex];
data[targetIndex + 1] = originalData[sourceIndex + 1];
data[targetIndex + 2] = originalData[sourceIndex + 2];
}
}
}
y += blockHeight; // Skip past the glitched block
}
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'noise':
{
if (intensity === 0) break;
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
const amount = 255 * intensity * 0.2; // Max 20% noise
for (let i = 0; i < data.length; i += 4) {
const random = (Math.random() - 0.5) * amount;
data[i] += random; // red
data[i + 1] += random; // green
data[i + 2] += random; // blue
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'scanlines':
{
if (intensity === 0) break;
const lineHeight = Math.max(1, Math.floor(intensity * 5));
ctx.fillStyle = `rgba(0, 0, 0, ${intensity * 0.2})`;
for (let y = 0; y < h; y += lineHeight * 2) {
ctx.fillRect(0, y, w, lineHeight);
}
break;
}
case 'oldfilm':
{
if (intensity === 0) break;
// 1. Sepia & Grain
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
let r = data[i], g = data[i + 1], b = data[i + 2];
// Sepia calculation
let tr = 0.393 * r + 0.769 * g + 0.189 * b;
let tg = 0.349 * r + 0.686 * g + 0.168 * b;
let tb = 0.272 * r + 0.534 * g + 0.131 * b;
// Blend with original based on intensity
data[i] = r * (1 - intensity) + tr * intensity;
data[i + 1] = g * (1 - intensity) + tg * intensity;
data[i + 2] = b * (1 - intensity) + tb * intensity;
// Grain
const noise = (Math.random() - 0.5) * 60 * intensity;
data[i] += noise;
data[i + 1] += noise;
data[i + 2] += noise;
}
ctx.putImageData(imageData, 0, 0);
// 2. Scratches
const numScratches = Math.floor(w * 0.05 * intensity);
for (let i = 0; i < numScratches; i++) {
const x = Math.random() * w;
ctx.strokeStyle = `rgba(255, 255, 255, ${Math.random() * 0.4 * intensity})`;
ctx.lineWidth = Math.random() * 2 + 0.5;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x + (Math.random() - 0.5) * 10, h);
ctx.stroke();
}
// 3. Vignette
const outerRadius = Math.sqrt(w * w + h * h) / 2;
const gradient = ctx.createRadialGradient(w / 2, h / 2, outerRadius * (1 - intensity * 0.9), w / 2, h / 2, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${0.7 * intensity})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
break;
}
case 'rgbsplit':
{
if (intensity === 0) break;
const imageData = ctx.getImageData(0, 0, w, h);
const originalData = new Uint8ClampedArray(imageData.data);
const data = imageData.data;
const offset = Math.floor(w * 0.015 * intensity);
for (let i = 0; i < data.length; i += 4) {
const x = (i / 4) % w;
const y = Math.floor((i / 4) / w);
const rIndex = (y * w + Math.max(0, x - offset)) * 4;
const bIndex = (y * w + Math.min(w - 1, x + offset)) * 4;
data[i] = originalData[rIndex]; // Red from left
data[i + 2] = originalData[bIndex + 2]; // Blue from right
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'pixelate':
{
// Intensity 0 -> 1px (no change), Intensity 1 -> ~20px
const pixelSize = Math.max(1, Math.round(intensity * 20));
if (pixelSize <= 1) {
ctx.drawImage(originalImg, 0, 0, w, h);
break;
};
const smallW = Math.ceil(w / pixelSize);
const smallH = Math.ceil(h / pixelSize);
// Draw original image scaled down into a temporary canvas
const tempCanvas = document.createElement('canvas');
tempCanvas.width = smallW;
tempCanvas.height = smallH;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0, smallW, smallH);
// Scale temp canvas back up to original size with smoothing disabled
ctx.imageSmoothingEnabled = false;
ctx.drawImage(tempCanvas, 0, 0, smallW, smallH, 0, 0, w, h);
break;
}
case 'invert':
{
if (intensity === 0) break;
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
data[i] = r * (1 - intensity) + (255 - r) * intensity;
data[i + 1] = g * (1 - intensity) + (255 - g) * intensity;
data[i + 2] = b * (1 - intensity) + (255 - b) * intensity;
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'sepia':
{
if (intensity === 0) break;
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
const tr = 0.393 * r + 0.769 * g + 0.189 * b;
const tg = 0.349 * r + 0.686 * g + 0.168 * b;
const tb = 0.272 * r + 0.534 * g + 0.131 * b;
// Blend with original color based on intensity
data[i] = r * (1 - intensity) + tr * intensity;
data[i + 1] = g * (1 - intensity) + tg * intensity;
data[i + 2] = b * (1 - intensity) + tb * intensity;
}
ctx.putImageData(imageData, 0, 0);
break;
}
default:
// If effectName is unknown, the original image is already on the canvas.
break;
}
return canvas;
}
Apply Changes