You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
scanlineOpacity = 0.15,
scanlineThickness = 2,
curvature = 0.08,
bloomSize = 0.005, // As a fraction of image diagonal for blur radius
bloomOpacity = 0.1,
vignetteStrength = 0.3, // 0 to 1, controls darkness and spread
noiseAmount = 0.04 // 0 to 1, e.g., 0.1 means noise can alter pixel component by +/- 12.8
) {
if (!originalImg || originalImg.width === 0 || originalImg.height === 0) {
console.error("Invalid image provided to processImage. Ensure it's loaded and has dimensions.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 1; // Minimal canvas
errorCanvas.height = 1;
// Returning a small canvas to avoid breaking downstream if expecting a canvas.
return errorCanvas;
}
const w = originalImg.width;
const h = originalImg.height;
// This will be the canvas we progressively build the effect on.
const workingCanvas = document.createElement('canvas');
workingCanvas.width = w;
workingCanvas.height = h;
const ctx = workingCanvas.getContext('2d');
if (!ctx) {
console.error("Could not get 2D context for the working canvas.");
return workingCanvas; // Return empty canvas, though this is highly unlikely
}
// Use image smoothing for effects like curvature and bloom for smoother results.
ctx.imageSmoothingEnabled = true;
// Initial draw of the original image onto our working canvas.
ctx.drawImage(originalImg, 0, 0, w, h);
// Step 1: Apply Curvature
// This effect redraws the image with barrel distortion.
// Curvature value should be positive for barrel, typically < 0.5 for reasonable effect.
if (curvature > 0 && curvature < 1) {
const tempCanvasForCurvature = document.createElement('canvas');
tempCanvasForCurvature.width = w;
tempCanvasForCurvature.height = h;
const tempCtx = tempCanvasForCurvature.getContext('2d');
if (!tempCtx) { // Should not happen
console.error("Could not get 2D context for temp curvature canvas.");
// Proceed without curvature if this unlikely event occurs
} else {
// Copy current state (original image) to temp canvas for source reading
tempCtx.drawImage(workingCanvas, 0, 0);
ctx.clearRect(0, 0, w, h); // Clear working canvas for redrawing with curvature
const centerX = w / 2;
const centerY = h / 2;
// Apply horizontal curvature (squeezing horizontal strips based on their vertical position)
for (let y = 0; y < h; y++) {
const normalizedY = (y - centerY) / centerY; // Normalize y: -1 (top) to 1 (bottom)
const scaleX = 1 - curvature * (normalizedY * normalizedY); // Parabolic scaling
const effectedWidth = w * scaleX;
const offsetX = (w - effectedWidth) / 2;
if (effectedWidth > 0) {
ctx.drawImage(tempCanvasForCurvature,
0, y, w, 1, // Source: 1px high strip from original image (on temp canvas)
offsetX, y, effectedWidth, 1 // Destination: Scaled width, centered horizontally
);
}
}
// Now apply vertical curvature to the already horizontally curved image.
// Update temp canvas to hold the horizontally curved image.
tempCtx.clearRect(0, 0, w, h);
tempCtx.drawImage(workingCanvas, 0, 0);
ctx.clearRect(0, 0, w, h); // Clear working canvas again
for (let x = 0; x < w; x++) {
const normalizedX = (x - centerX) / centerX; // Normalize x: -1 (left) to 1 (right)
const scaleY = 1 - curvature * (normalizedX * normalizedX); // Parabolic scaling
const effectedHeight = h * scaleY;
const offsetY = (h - effectedHeight) / 2;
if (effectedHeight > 0) {
ctx.drawImage(tempCanvasForCurvature,
x, 0, 1, h, // Source: 1px wide strip from horizontally curved image
x, offsetY, 1, effectedHeight // Destination: Scaled height, centered vertically
);
}
}
}
}
// Step 2: Apply Bloom (Phosphor Glow)
if (bloomSize > 0 && bloomOpacity > 0) {
const bloomCanvas = document.createElement('canvas');
bloomCanvas.width = w;
bloomCanvas.height = h;
const bloomCtx = bloomCanvas.getContext('2d');
if (!bloomCtx) { // Should not happen
console.error("Could not get 2D context for bloom canvas.");
} else {
const blurRadius = Math.hypot(w, h) * bloomSize;
if (blurRadius > 0) {
bloomCtx.filter = `blur(${blurRadius}px)`;
// Draw the current state of workingCanvas (with curvature) onto bloomCanvas, applying the blur.
bloomCtx.drawImage(workingCanvas, 0, 0, w, h);
bloomCtx.filter = 'none'; // Reset filter on bloomCtx
// Blend the blurred (bloomed) image back onto the workingCanvas.
ctx.save(); // Save current state (like globalAlpha, globalCompositeOperation)
ctx.globalAlpha = bloomOpacity;
ctx.globalCompositeOperation = 'lighter'; // Additive blending creates a glow effect
ctx.drawImage(bloomCanvas, 0, 0);
ctx.restore(); // Restore to previous globalAlpha and GCO
}
}
}
// Step 3: Apply Scanlines
// These are dark lines overlaid on the image.
if (scanlineOpacity > 0 && scanlineThickness > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity})`;
for (let y = 0; y < h; y += scanlineThickness * 2) { // Dark line, then gap of same thickness
ctx.fillRect(0, y, w, scanlineThickness);
}
}
// Step 4: Apply Noise
// Adds random pixel intensity variations.
if (noiseAmount > 0) {
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
// noiseAmount (0-1) scales noise up to +/- 128: e.g. 0.1 => +/- 12.8
const noiseRange = noiseAmount * 128;
for (let i = 0; i < data.length; i += 4) {
// Random value between -noiseRange and +noiseRange
const randomFactor = (Math.random() - 0.5) * 2 * noiseRange;
data[i] = Math.max(0, Math.min(255, data[i] + randomFactor));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + randomFactor));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + randomFactor));
// Alpha channel (data[i+3]) is not changed.
}
ctx.putImageData(imageData, 0, 0);
}
// Step 5: Apply Vignette
// Darkens the corners/edges of the image.
if (vignetteStrength > 0) {
const centerX = w / 2;
const centerY = h / 2;
const outerRadius = Math.hypot(centerX, centerY); // Radius to reach corners
// vignetteStrength (0-1): 0 means no vignette, 1 means vignette starts near center and is darker.
const innerRadiusFactor = Math.max(0, 1 - vignetteStrength * 1.25);
const effectiveInnerRadius = outerRadius * innerRadiusFactor;
const gradient = ctx.createRadialGradient(
centerX, centerY, effectiveInnerRadius,
centerX, centerY, outerRadius
);
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent center
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Dark edges
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
// The workingCanvas now holds the final processed image.
return workingCanvas;
}
Apply Changes