You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies various video-style effects to an image.
*
* @param {Image} originalImg The original javascript Image object.
* @param {string} effectName The name of the effect to apply. Available effects: 'grayscale', 'sepia', 'invert', 'posterize', 'noise', 'scanlines', 'vignette', 'blur', 'vhs', 'none'.
* @param {number} intensity A general-purpose intensity value for the effect (e.g., blur amount, noise level). Range and effect vary by filter. Recommended 1-10.
* @returns {HTMLCanvasElement} A new canvas element with the effect applied.
*/
function processImage(originalImg, effectName = 'none', intensity = 5) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const w = originalImg.naturalWidth;
const h = originalImg.naturalHeight;
canvas.width = w;
canvas.height = h;
const effect = effectName.toLowerCase();
// Early exit for no effect
if (effect === 'none') {
ctx.drawImage(originalImg, 0, 0, w, h);
return canvas;
}
// Handle filter-based effects first, as they are applied before drawing the image
if (effect === 'blur') {
// Clamp intensity to a reasonable blur radius (0-20px)
const blurAmount = Math.max(0, Math.min(20, intensity));
ctx.filter = `blur(${blurAmount}px)`;
ctx.drawImage(originalImg, 0, 0, w, h);
return canvas; // Return early, as filter is applied
}
// For all other effects, draw the image onto the canvas first
ctx.drawImage(originalImg, 0, 0, w, h);
// --- PIXEL MANIPULATION EFFECTS ---
const pixelEffects = ['grayscale', 'sepia', 'invert', 'posterize', 'noise'];
if (pixelEffects.includes(effect)) {
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];
if (effect === 'grayscale') {
const avg = 0.299 * r + 0.587 * g + 0.114 * b;
data[i] = data[i + 1] = data[i + 2] = avg;
} else if (effect === 'sepia') {
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;
data[i] = Math.min(255, tr);
data[i + 1] = Math.min(255, tg);
data[i + 2] = Math.min(255, tb);
} else if (effect === 'invert') {
data[i] = 255 - r;
data[i + 1] = 255 - g;
data[i + 2] = 255 - b;
} else if (effect === 'posterize') {
// Clamp intensity to a useful number of color levels (2-32)
const levels = Math.max(2, Math.min(32, Math.round(intensity)));
const step = 255 / (levels - 1);
data[i] = Math.round(r / step) * step;
data[i + 1] = Math.round(g / step) * step;
data[i + 2] = Math.round(b / step) * step;
} else if (effect === 'noise') {
// Clamp intensity to control noise amount (0-100)
const noiseAmount = Math.max(0, Math.min(100, intensity));
const noise = (Math.random() - 0.5) * noiseAmount;
data[i] = Math.max(0, Math.min(255, r + noise));
data[i + 1] = Math.max(0, Math.min(255, g + noise));
data[i + 2] = Math.max(0, Math.min(255, b + noise));
}
}
ctx.putImageData(imageData, 0, 0);
}
// --- OVERLAY / COMPOSITE EFFECTS ---
if (effect === 'scanlines') {
const lineGap = Math.max(2, Math.min(10, Math.round(intensity)));
const lineHeight = Math.floor(lineGap / 2);
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
for (let y = 0; y < h; y += lineGap) {
ctx.fillRect(0, y, w, lineHeight);
}
} else if (effect === 'vignette') {
// Clamp intensity to a valid alpha value (0.0 - 1.0)
const vignetteAmount = Math.max(0, Math.min(1, intensity / 10));
const outerRadius = Math.sqrt(w * w + h * h) / 2;
const gradient = ctx.createRadialGradient(w / 2, h / 2, h / 3, w / 2, h / 2, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(0.5, 'rgba(0,0,0,0)'); // Make the center clear
gradient.addColorStop(1, `rgba(0,0,0,${vignetteAmount})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
} else if (effect === 'vhs') {
// 1. Chromatic Aberration
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
const originalData = new Uint8ClampedArray(data); // Create a copy to read from
const offset = Math.round(intensity) * 4; // 1 pixel = 4 bytes (RGBA)
for (let i = 0; i < data.length; i += 4) {
// Set red channel from a pixel to the left
const rIndex = i - offset;
if (rIndex >= 0) {
data[i] = originalData[rIndex];
}
// Set blue channel from a pixel to the right
const bIndex = i + offset;
if (bIndex < data.length) {
data[i + 2] = originalData[bIndex + 2];
}
}
ctx.putImageData(imageData, 0, 0);
// 2. Scanlines
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
for (let y = 0; y < h; y += 4) {
ctx.fillRect(0, y, w, 2);
}
// 3. Random horizontal glitch
const glitchCount = 15;
const maxGlitchOffset = 40;
for (let i = 0; i < glitchCount; i++) {
const y = Math.random() * h;
const stripHeight = Math.random() * 20 + 1;
const xOffset = (Math.random() - 0.5) * maxGlitchOffset;
try {
const stripData = ctx.getImageData(0, y, w, stripHeight);
ctx.putImageData(stripData, xOffset, y);
} catch (e) {
// Ignore errors from getImageData if y/height are out of bounds
}
}
}
return canvas;
}
Apply Changes