You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
grainAmount = 0.1,
vignetteStrength = 0.5,
colorCast = "rgba(255, 240, 200, 0.1)", // Default: Warm yellow tint, subtle
blurAmount = 0.3, // Default: Very slight blur
saturation = 0.85, // Default: Slightly desaturated
contrast = 1.15) { // Default: Slightly more contrast
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const w = originalImg.naturalWidth || originalImg.width;
const h = originalImg.naturalHeight || originalImg.height;
canvas.width = w;
canvas.height = h;
// 1. Apply base image adjustments (Blur, Saturation, Contrast) via CSS-like filters
// These filters are applied when drawImage is called.
let filterString = '';
if (typeof blurAmount === 'number' && blurAmount > 0) {
filterString += `blur(${blurAmount}px) `;
}
if (typeof saturation === 'number' && saturation !== 1) {
filterString += `saturate(${saturation}) `;
}
if (typeof contrast === 'number' && contrast !== 1) {
filterString += `contrast(${contrast}) `;
}
if (filterString.trim() !== '') {
ctx.filter = filterString.trim();
}
// Draw the original image, potentially filtered
ctx.drawImage(originalImg, 0, 0, w, h);
// Reset filter so it doesn't affect subsequent drawing operations (grain, overlays)
ctx.filter = 'none';
// 2. Add Film Grain
// This operates on the pixel data of the (potentially filtered) image.
if (typeof grainAmount === 'number' && grainAmount > 0 && grainAmount <= 1) {
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
const len = data.length;
for (let i = 0; i < len; i += 4) {
// Generate random noise value. (Math.random() - 0.5) produces range [-0.5, 0.5].
// Multiplied by 255 to scale to pixel value range.
// Multiplied by grainAmount to control intensity.
const noise = (Math.random() - 0.5) * 255 * grainAmount;
data[i] = Math.max(0, Math.min(255, data[i] + noise)); // Red
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + noise)); // Green
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + noise)); // Blue
// Alpha (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
}
// 3. Apply Color Cast (Tint)
// This is drawn over the (potentially grained) image.
if (typeof colorCast === 'string' && colorCast.trim() !== "" && colorCast.toLowerCase() !== "none") {
let applyCast = true;
const lowerColorCast = colorCast.toLowerCase();
if (lowerColorCast === 'transparent') {
applyCast = false;
} else {
// Check for explicit transparency like rgba(x,y,z,0) or hsla(x,y,z,0)
const alphaMatch = lowerColorCast.match(/(rgba|hsla)\s*\([\d\s%.,]+?([01]?\.?\d+)\s*\)/);
if (alphaMatch && parseFloat(alphaMatch[2]) === 0) {
applyCast = false;
}
}
if (applyCast) {
// 'overlay' or 'soft-light' are good for tinting effects.
// 'multiply' can be used for different kinds of color shifts but tends to darken.
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = colorCast; // The color string itself (e.g., "rgba(255,240,200,0.1)")
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
}
}
// 4. Apply Vignetting
// This is drawn on top of everything else.
if (typeof vignetteStrength === 'number' && vignetteStrength > 0 && vignetteStrength <= 1) {
const centerX = w / 2;
const centerY = h / 2;
// The outer radius goes to the corner of the image to ensure full coverage.
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// clearRadius defines the central area that remains unaffected (fully transparent) by the vignette.
// Making it relative to the smallest dimension of the image provides some consistency.
// e.g., 60% of half the smallest dimension.
const clearRadius = (Math.min(w, h) / 2) * 0.6;
const gradient = ctx.createRadialGradient(
centerX, centerY, clearRadius, // Inner circle: gradient starts here (transparent stop)
centerX, centerY, outerRadius // Outer circle: gradient ends here (dark stop)
);
// The gradient transitions from fully transparent at clearRadius to the target vignette color/opacity at outerRadius.
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Color at clearRadius (fully transparent black)
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Color at outerRadius (dark black with specified opacity)
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
return canvas;
}
Apply Changes