You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies a visual effect to an image, metaphorically named after vocal/audio effects.
*
* @param {HTMLImageElement} originalImg The original image object.
* @param {string} effectType The type of effect to apply. Options: 'reverb', 'echo', 'distortion', 'chorus', 'vibrato', 'autotune'. Defaults to 'reverb'.
* @param {number} intensity The strength of the effect, from 1 to 10. Defaults to 5.
* @returns {HTMLCanvasElement} A canvas element with the processed image.
*/
function processImage(originalImg, effectType = 'reverb', intensity = 5) {
// 1. SETUP
// Clamp intensity to a safe range of 1-10
const cleanIntensity = Math.max(1, Math.min(10, Number(intensity)));
// Create a canvas to draw on
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;
// 2. APPLY EFFECT
switch (effectType.toLowerCase()) {
/**
* REVERB: Simulates the sense of space and decay from reverb using a blur filter.
* Higher intensity creates a larger, more pronounced blur.
*/
case 'reverb':
{
ctx.filter = `blur(${cleanIntensity * 0.75}px)`;
ctx.drawImage(originalImg, 0, 0);
break;
}
/**
* ECHO: Creates repeating, fading copies of the image, like an audio echo.
* Higher intensity creates more copies that are spaced further apart.
*/
case 'echo':
{
const steps = Math.floor(cleanIntensity / 2) + 1;
const offsetFactor = cleanIntensity * 2;
ctx.save();
// Draw echoes from back to front
for (let i = steps; i >= 1; i--) {
ctx.globalAlpha = 0.5 / i;
ctx.drawImage(originalImg, i * offsetFactor, 0, width, height);
}
// Draw the primary image on top
ctx.globalAlpha = 1.0;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.restore();
break;
}
/**
* DISTORTION: Adds digital grit and harshness using noise and contrast.
* Higher intensity increases the noise and contrast level.
*/
case 'distortion':
{
ctx.filter = `contrast(${100 + cleanIntensity * 8}%) saturate(${100 + cleanIntensity * 3}%)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none'; // remove filter to not affect putImageData
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
const noiseAmount = cleanIntensity * 10;
for (let i = 0; i < data.length; i += 4) {
const noise = (Math.random() - 0.5) * noiseAmount;
data[i] = Math.max(0, Math.min(255, data[i] + noise));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + noise));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + noise));
}
ctx.putImageData(imageData, 0, 0);
break;
}
/**
* CHORUS: Simulates multiple voices by splitting and shifting the color channels (Chromatic Aberration).
* Higher intensity increases the separation distance of the color channels.
*/
case 'chorus':
{
// Helper to create a canvas with data from a single color channel
const createChannelCanvas = (imgData, channelIndex) => {
const c = document.createElement('canvas');
c.width = width;
c.height = height;
const cCtx = c.getContext('2d');
const newData = cCtx.createImageData(width, height);
for (let i = 0; i < imgData.data.length; i += 4) {
newData.data[i + channelIndex] = imgData.data[i + channelIndex];
newData.data[i + 3] = imgData.data[i + 3]; // Alpha
}
cCtx.putImageData(newData, 0, 0);
return c;
};
ctx.drawImage(originalImg, 0, 0);
const imgData = ctx.getImageData(0, 0, width, height);
const rCanvas = createChannelCanvas(imgData, 0); // Red channel
const gCanvas = createChannelCanvas(imgData, 1); // Green channel
const bCanvas = createChannelCanvas(imgData, 2); // Blue channel
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.globalCompositeOperation = 'lighter'; // Additive blending
const offset = cleanIntensity * 1.5;
ctx.drawImage(rCanvas, -offset, 0);
ctx.drawImage(gCanvas, 0, 0);
ctx.drawImage(bCanvas, offset, 0);
ctx.restore();
break;
}
/**
* VIBRATO: Creates a wavy effect, like the modulation of vocal pitch.
* Higher intensity increases the amplitude (waviness) and frequency of the waves.
*/
case 'vibrato':
{
ctx.drawImage(originalImg, 0, 0);
const originalData = ctx.getImageData(0, 0, width, height);
const newData = ctx.createImageData(width, height);
const amp = cleanIntensity * 1.5;
const period = Math.max(20, 200 - cleanIntensity * 15);
for (let y = 0; y < height; y++) {
const dx = amp * Math.sin(2 * Math.PI * y / period);
for (let x = 0; x < width; x++) {
const sourceX = Math.round(x - dx);
const destIdx = (y * width + x) * 4;
if (sourceX >= 0 && sourceX < width) {
const sourceIdx = (y * width + sourceX) * 4;
newData.data[destIdx] = originalData.data[sourceIdx];
newData.data[destIdx + 1] = originalData.data[sourceIdx + 1];
newData.data[destIdx + 2] = originalData.data[sourceIdx + 2];
newData.data[destIdx + 3] = originalData.data[sourceIdx + 3];
}
}
}
ctx.putImageData(newData, 0, 0);
break;
}
/**
* AUTOTUNE: Mimics the pitch-snapping of autotune by reducing the number of colors (posterization).
* Higher intensity drastically reduces the color palette, creating a more "quantized" look.
*/
case 'autotune':
{
ctx.drawImage(originalImg, 0, 0);
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
const levels = Math.floor(17 - cleanIntensity * 1.5); // Map intensity 1-10 to levels 16-2
const step = 255 / (levels - 1);
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(imgData, 0, 0);
break;
}
/**
* DEFAULT: If the effect type is unknown, just return the original image.
*/
default:
ctx.drawImage(originalImg, 0, 0, width, height);
break;
}
// 3. RETURN RESULT
return canvas;
}
Apply Changes