You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies various "video editor" style effects to an image.
* This function emulates effects often found in mobile video editing apps.
*
* @param {Image} originalImg The original JavaScript Image object.
* @param {string} effectType The type of effect to apply.
* Available effects: 'glitch', 'vhs', 'scanlines', 'posterize', 'grayscale', 'sepia', 'invert'.
* Defaults to 'glitch'.
* @param {number} intensity A value from 0.0 to 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, effectType = 'glitch', intensity = 0.5) {
// 1. Setup Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas to start with
ctx.drawImage(originalImg, 0, 0, width, height);
// Clamp intensity to a valid 0-1 range
const clampedIntensity = Math.max(0, Math.min(1, intensity));
// Helper function to get a pixel's starting index in the ImageData array
const getIndex = (x, y, w) => (y * w + x) * 4;
// 2. Apply Effects based on effectType
switch (effectType.toLowerCase()) {
case 'grayscale': {
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
// Blend original color with grayscale based on intensity
data[i] = (1 - clampedIntensity) * r + clampedIntensity * gray;
data[i + 1] = (1 - clampedIntensity) * g + clampedIntensity * gray;
data[i + 2] = (1 - clampedIntensity) * b + clampedIntensity * gray;
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'sepia': {
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const sr = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189);
const sg = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168);
const sb = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131);
// Blend original color with sepia based on intensity
data[i] = (1 - clampedIntensity) * r + clampedIntensity * sr;
data[i + 1] = (1 - clampedIntensity) * g + clampedIntensity * sg;
data[i + 2] = (1 - clampedIntensity) * b + clampedIntensity * sb;
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'invert': {
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Blend original color with inverted color based on intensity
data[i] = (1 - clampedIntensity) * r + clampedIntensity * (255 - r);
data[i + 1] = (1 - clampedIntensity) * g + clampedIntensity * (255 - g);
data[i + 2] = (1 - clampedIntensity) * b + clampedIntensity * (255 - b);
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'posterize': {
// Intensity controls the number of color levels (from 2 to 16)
const levels = Math.round(clampedIntensity * 14) + 2;
const step = 255 / (levels - 1);
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.round(data[i] / step) * step;
data[i + 1] = Math.round(data[i + 1] / step) * step;
data[i + 2] = Math.round(data[i + 2] / step) * step;
}
ctx.putImageData(imageData, 0, 0);
break;
}
case 'scanlines': {
// This effect just draws lines over the existing image.
ctx.fillStyle = `rgba(0, 0, 0, ${clampedIntensity * 0.3})`;
for (let y = 0; y < height; y += 3) {
ctx.fillRect(0, y, width, 1);
}
break;
}
case 'vhs': {
// 1. Chromatic Aberration
const originalData = ctx.getImageData(0, 0, width, height).data;
const newData = ctx.createImageData(width, height);
const offset = Math.floor(clampedIntensity * 15);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = getIndex(x, y, width);
// R from left, G from center, B from right
const rIndex = getIndex(Math.max(0, x - offset), y, width);
const bIndex = getIndex(Math.min(width - 1, x + offset), y, width);
newData.data[i] = originalData[rIndex]; // Red
newData.data[i + 1] = originalData[i + 1]; // Green
newData.data[i + 2] = originalData[bIndex + 2]; // Blue
newData.data[i + 3] = 255; // Alpha
}
}
ctx.putImageData(newData, 0, 0);
// 2. Noise
const vhsImageData = ctx.getImageData(0, 0, width, height);
const data = vhsImageData.data;
const noiseAmount = clampedIntensity * 40;
for (let i = 0; i < data.length; i += 4) {
const randomNoise = (Math.random() - 0.5) * noiseAmount;
data[i] = Math.max(0, Math.min(255, data[i] + randomNoise));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + randomNoise));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + randomNoise));
}
ctx.putImageData(vhsImageData, 0, 0);
// 3. Scanlines on top
ctx.fillStyle = `rgba(0, 0, 0, ${clampedIntensity * 0.15})`;
for (let y = 0; y < height; y += 4) {
ctx.fillRect(0, y, width, 2);
}
break;
}
case 'glitch':
default: {
// Intensity controls the number of glitch slices and their max offset
const numSlices = Math.floor(clampedIntensity * 40);
const maxOffsetX = width * 0.15 * clampedIntensity;
for (let i = 0; i < numSlices; i++) {
const startY = Math.random() * height;
const sliceHeight = Math.random() * (height / 15) + 1;
const offsetX = (Math.random() - 0.5) * 2 * maxOffsetX;
if (startY + sliceHeight > height) continue;
// Grab a slice of the canvas and draw it back with an offset
const sliceData = ctx.getImageData(0, startY, width, sliceHeight);
ctx.putImageData(sliceData, offsetX, startY);
}
break;
}
}
// 3. Return the modified canvas
return canvas;
}
Apply Changes