You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, chromaticAberrationOffset = 5, scanlineIntensity = 0.2, noiseAmount = 0.05, waveFrequency = 0.02, waveAmplitude = 2, tintColor = '#ff00ff', tintIntensity = 0.3, vignetteIntensity = 0.8) {
// 1. Initialization
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
// Create a source canvas to get initial pixel data without affecting the original image object
const sourceCanvas = document.createElement('canvas');
sourceCanvas.width = width;
sourceCanvas.height = height;
const sourceCtx = sourceCanvas.getContext('2d', {
willReadFrequently: true
});
sourceCtx.drawImage(originalImg, 0, 0);
const sourceData = sourceCtx.getImageData(0, 0, width, height);
const pixels = sourceData.data;
// 2. Chromatic Aberration + Noise
// This step manipulates pixels to create color channel separation and grain.
const outputData = ctx.createImageData(width, height);
const outputPixels = outputData.data;
for (let i = 0; i < pixels.length; i += 4) {
const y = Math.floor((i / 4) / width);
const x = (i / 4) % width;
// Helper function to get a pixel index from coordinates, clamping to image bounds
const getIndex = (x, y) => {
x = Math.max(0, Math.min(width - 1, Math.round(x)));
y = Math.max(0, Math.min(height - 1, Math.round(y)));
return (y * width + x) * 4;
};
const rIndex = getIndex(x - chromaticAberrationOffset, y);
const gIndex = getIndex(x, y);
const bIndex = getIndex(x + chromaticAberrationOffset, y);
// Add random noise for a grainy VHS effect
const noise = (Math.random() - 0.5) * 255 * noiseAmount;
outputPixels[i] = pixels[rIndex] + noise; // Red channel
outputPixels[i + 1] = pixels[gIndex + 1] + noise; // Green channel
outputPixels[i + 2] = pixels[bIndex + 2] + noise; // Blue channel
outputPixels[i + 3] = pixels[i + 3]; // Alpha channel
}
// Create a temporary canvas with the modified image data for the wave effect
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.putImageData(outputData, 0, 0);
// 3. Low Ondulation (Wave Effect)
// This simulates VHS tracking issues by drawing the image in horizontal slices with a sinusoidal offset.
ctx.clearRect(0, 0, width, height);
const phase = Math.random() * Math.PI * 2; // Random phase for a unique wave pattern each time
const sliceHeight = 2; // Drawing in 2px slices is a good balance of quality and performance
for (let y = 0; y < height; y += sliceHeight) {
const xOffset = Math.sin(y * waveFrequency + phase) * waveAmplitude;
// A try-catch block can prevent rare errors with drawImage on some browsers
try {
ctx.drawImage(tempCanvas, 0, y, width, sliceHeight, xOffset, y, width, sliceHeight);
} catch (e) {
// Fallback to drawing without offset if an error occurs
ctx.drawImage(tempCanvas, 0, y, width, sliceHeight, 0, y, width, sliceHeight);
}
}
// 4. Color Tinting
// Apply a magenta/cyan tint common in vaporwave aesthetics.
if (tintIntensity > 0) {
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = tintColor;
ctx.globalAlpha = tintIntensity;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1.0;
}
// 5. Scanlines
// Overlay thin horizontal lines to mimic a CRT screen.
if (scanlineIntensity > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineIntensity})`;
for (let y = 0; y < height; y += 3) {
ctx.fillRect(0, y, width, 1);
}
}
// 6. CRT Vignette
// Darken the corners to simulate the look of a curved CRT monitor.
if (vignetteIntensity > 0) {
const centerX = width / 2;
const centerY = height / 2;
const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY);
const gradient = ctx.createRadialGradient(centerX, centerY, outerRadius * 0.3, centerX, centerY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${vignetteIntensity})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
return canvas;
}
Apply Changes