You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Enhances an image with a CRT lens effect featuring barrel distortion, vignette,
* soft edge blur, scanlines, chromatic aberration, and luminous bloom to simulate
* a retro display experience.
*
* @param {HTMLImageElement} originalImg The source image object.
* @param {number} [distortion=0.2] The amount of barrel distortion (range ~0 to 1). Higher values create a more pronounced curve.
* @param {number} [aberrationAmount=3.0] The strength of the chromatic aberration (color fringing) at the edges (range ~0 to 10).
* @param {number} [bloomIntensity=0.3] The intensity of the luminous bloom effect for bright areas (range 0 to 1).
* @param {number} [scanlineOpacity=0.1] The opacity of the horizontal scanlines (range 0 to 1). Set to 0 to disable.
* @param {number} [vignetteIntensity=0.8] The darkness of the vignette at the corners (range 0 to 1).
* @param {number} [vignetteSmoothness=0.7] The smoothness of the vignette's fade towards the center (range 0 to 1).
* @returns {HTMLCanvasElement} A new canvas element displaying the processed image.
*/
function processImage(originalImg, distortion = 0.2, aberrationAmount = 3.0, bloomIntensity = 0.3, scanlineOpacity = 0.1, vignetteIntensity = 0.8, vignetteSmoothness = 0.7) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const w = originalImg.naturalWidth;
const h = originalImg.naturalHeight;
canvas.width = w;
canvas.height = h;
// --- Pass 1: Barrel Distortion & Chromatic Aberration ---
// This pass redraws the image with a lens distortion effect.
// It's performance-intensive as it processes pixels one by one.
const sourceCanvas = document.createElement('canvas');
sourceCanvas.width = w;
sourceCanvas.height = h;
const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
sourceCtx.drawImage(originalImg, 0, 0, w, h);
const sourceData = sourceCtx.getImageData(0, 0, w, h);
const distortedData = ctx.createImageData(w, h);
const cx = w / 2;
const cy = h / 2;
const maxDist = Math.sqrt(cx * cx + cy * cy);
const aberration = aberrationAmount / 500.0;
const getPixel = (data, x, y) => {
x = Math.floor(x);
y = Math.floor(y);
if (x < 0 || x >= w || y < 0 || y >= h) {
return [0, 0, 0, 0];
}
const i = (y * w + x) * 4;
return [data.data[i], data.data[i + 1], data.data[i + 2], data.data[i + 3]];
};
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const dx = x - cx;
const dy = y - cy;
const d = Math.sqrt(dx * dx + dy * dy);
const normDist = d / maxDist;
const k = distortion * Math.pow(normDist, 2);
const mag = 1 / (1 + k);
const sx = cx + dx * mag;
const sy = cy + dy * mag;
const sx_r = cx + dx * (mag - aberration);
const sy_r = cy + dy * (mag - aberration);
const sx_b = cx + dx * (mag + aberration);
const sy_b = cy + dy * (mag + aberration);
const r = getPixel(sourceData, sx_r, sy_r)[0];
const g = getPixel(sourceData, sx, sy)[1];
const b = getPixel(sourceData, sx_b, sy_b)[2];
const a = getPixel(sourceData, sx, sy)[3];
const dest_i = (y * w + x) * 4;
distortedData.data[dest_i] = r;
distortedData.data[dest_i + 1] = g;
distortedData.data[dest_i + 2] = b;
distortedData.data[dest_i + 3] = a;
}
}
ctx.putImageData(distortedData, 0, 0);
// --- Soft Edge Blur ---
const tempCanvas = document.createElement('canvas');
tempCanvas.width = w; tempCanvas.height = h;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.filter = 'blur(0.5px)';
tempCtx.drawImage(canvas, 0, 0);
ctx.clearRect(0, 0, w, h);
ctx.drawImage(tempCanvas, 0, 0);
// --- Pass 2: Luminous Bloom ---
if (bloomIntensity > 0) {
const bloomCanvas = document.createElement('canvas');
bloomCanvas.width = w; bloomCanvas.height = h;
const bloomCtx = bloomCanvas.getContext('2d', { willReadFrequently: true });
bloomCtx.drawImage(canvas, 0, 0);
const bloomData = bloomCtx.getImageData(0, 0, w, h);
const lumaThreshold = 180;
for (let i = 0; i < bloomData.data.length; i += 4) {
const luma = 0.2126 * bloomData.data[i] + 0.7152 * bloomData.data[i+1] + 0.0722 * bloomData.data[i+2];
if (luma < lumaThreshold) {
bloomData.data[i] = bloomData.data[i+1] = bloomData.data[i+2] = 0;
}
}
bloomCtx.putImageData(bloomData, 0, 0);
bloomCtx.filter = `blur(${bloomIntensity * 20}px)`;
bloomCtx.drawImage(bloomCanvas, 0, 0);
ctx.globalCompositeOperation = 'screen';
ctx.globalAlpha = bloomIntensity;
ctx.drawImage(bloomCanvas, 0, 0);
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1.0;
}
// --- Pass 3: Scanlines ---
if (scanlineOpacity > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity})`;
for (let y = 0; y < h; y += 3) {
ctx.fillRect(0, y, w, 1);
}
}
// --- Pass 4: Vignette ---
if (vignetteIntensity > 0) {
const outerRadius = Math.sqrt(cx * cx + cy * cy);
const gradient = ctx.createRadialGradient(cx, cy, outerRadius * (1 - vignetteSmoothness), cx, cy, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${vignetteIntensity})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
// --- Pass 5: Curved Screen Bezel ---
const cornerRadius = w * 0.04;
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.rect(0, 0, w, h);
ctx.moveTo(cornerRadius, 0);
ctx.lineTo(w - cornerRadius, 0);
ctx.arcTo(w, 0, w, cornerRadius, cornerRadius);
ctx.lineTo(w, h - cornerRadius);
ctx.arcTo(w, h, w - cornerRadius, h, cornerRadius);
ctx.lineTo(cornerRadius, h);
ctx.arcTo(0, h, 0, h - cornerRadius, cornerRadius);
ctx.lineTo(0, cornerRadius);
ctx.arcTo(0, 0, cornerRadius, 0, cornerRadius);
ctx.closePath();
ctx.fill('evenodd');
return canvas;
}
Apply Changes