You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, saturationBoost = 1.3, contrastBoost = 1.2, brightnessBoost = 1.05, vignetteStrength = 0.4, grainOpacity = 0.07) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
// Handle invalid image dimensions gracefully.
// Return a 1x1 canvas or could throw an error.
canvas.width = 1;
canvas.height = 1;
console.warn("processImage: Original image has zero dimensions. Returning a 1x1 canvas.");
return canvas;
}
canvas.width = width;
canvas.height = height;
// 1. Apply core image adjustments: Saturation, Contrast, Brightness
// These filters are applied together for efficiency.
// Values around 1.0 are neutral. >1 increases effect, <1 decreases.
ctx.filter = `saturate(${Number(saturationBoost)}) contrast(${Number(contrastBoost)}) brightness(${Number(brightnessBoost)})`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none'; // Reset filter to prevent it affecting subsequent drawing operations
// 2. Add Film Grain effect
// This simulates the texture of photographic film.
if (grainOpacity > 0 && typeof grainOpacity === 'number') {
// Create a small canvas for the noise pattern
const noiseCanvas = document.createElement('canvas');
const noiseSize = 100; // Dimension of the grain tile.
noiseCanvas.width = noiseSize;
noiseCanvas.height = noiseSize;
// { willReadFrequently: true } is an optimization hint for a canvas you'll often use getImageData on.
const noiseCtx = noiseCanvas.getContext('2d', { willReadFrequently: true });
const noiseImageData = noiseCtx.createImageData(noiseSize, noiseSize);
const noisePixels = noiseImageData.data;
// Controls the intensity of individual grain particles.
// Noise is centered around 128 (neutral gray for 'overlay' blend mode).
const grainVariance = 40; // Pixels will vary by +/- grainVariance from 128.
for (let i = 0; i < noisePixels.length; i += 4) {
// Generate monochrome noise for classic film grain look
const noiseValue = 128 + (Math.random() - 0.5) * 2 * grainVariance;
const clampedNoise = Math.max(0, Math.min(255, noiseValue)); // Ensure value is 0-255
noisePixels[i] = clampedNoise; // Red
noisePixels[i+1] = clampedNoise; // Green
noisePixels[i+2] = clampedNoise; // Blue
noisePixels[i+3] = 255; // Alpha (fully opaque tile)
}
noiseCtx.putImageData(noiseImageData, 0, 0);
// Overlay the grain pattern onto the main canvas
ctx.globalAlpha = Math.max(0, Math.min(1, grainOpacity)); // Clamp opacity 0-1
ctx.globalCompositeOperation = 'overlay'; // 'overlay' blend mode works well for grain
const grainPattern = ctx.createPattern(noiseCanvas, 'repeat');
if (grainPattern) { // createPattern can return null if canvas is 0x0 (not the case here)
ctx.fillStyle = grainPattern;
ctx.fillRect(0, 0, width, height); // Tile the pattern over the whole image
}
// Reset global drawing settings
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
}
// 3. Add Vignette effect
// This darkens the corners of the image, drawing focus to the center.
if (vignetteStrength > 0 && typeof vignetteStrength === 'number') {
const centerX = width / 2;
const centerY = height / 2;
// The outer radius should extend to the corners of the image.
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// The inner radius defines where the vignette effect starts to become transparent.
// A common choice is a percentage of the outer radius.
const innerRadiusRatio = 0.3; // Vignette is fully transparent inside 30% of outerRadius
const innerRadius = outerRadius * innerRadiusRatio;
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
// Gradient: transparent center, darkening towards edges.
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent at innerRadius
gradient.addColorStop(1, `rgba(0,0,0,${Math.max(0, Math.min(1, vignetteStrength))})`); // vignetteStrength opaque black at outerRadius
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height); // Apply vignette over the image
}
return canvas;
}
Apply Changes