You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
grainAmount = 15, // Integer, 0-50. Max noise value added/subtracted. Higher is more grain.
vignetteStrength = 0.4, // Float, 0-1. Strength of vignette. 0 is none, 1 results in black at extreme corners.
vignetteFalloff = 2.0, // Float, e.g., 1.0-4.0. Controls how gradually the vignette fades. Higher values mean a more focused center, sharper falloff.
contrast = 1.1, // Float, e.g., 0.5 (low contrast) to 2.0 (high contrast). 1.0 is no change.
saturation = 0.9, // Float, e.g., 0.0 (grayscale) to 2.0 (super-saturated). 1.0 is original saturation.
tintColor = "#FAEBD7", // String, hex color for tinting (e.g., "#RRGGBB"). Empty string or null for no tint. Default is AntiqueWhite for a subtle warm cast.
tintOpacity = 0.1 // Float, 0-1. Opacity of the tint. 0 is no tint, 1 is fully tinted.
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // willReadFrequently for performance with getImageData/putImageData
// Ensure image is loaded and has dimensions
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (!imgWidth || !imgHeight) {
// Return an empty canvas or handle error if image is not valid
console.error("Image has no dimensions or is not loaded.");
canvas.width = 1; // Minimal canvas
canvas.height = 1;
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
const centerX = width / 2;
const centerY = height / 2;
let parsedTintColorR = 0, parsedTintColorG = 0, parsedTintColorB = 0;
let applyTint = false;
if (tintColor && typeof tintColor === 'string' && tintOpacity > 0 && /^#[0-9A-F]{6}$/i.test(tintColor)) {
parsedTintColorR = parseInt(tintColor.substring(1, 3), 16);
parsedTintColorG = parseInt(tintColor.substring(3, 5), 16);
parsedTintColorB = parseInt(tintColor.substring(5, 7), 16);
applyTint = true;
}
// Calculate max distance from center to a corner for vignette normalization.
// This makes vignette independent of aspect ratio.
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Contrast adjustment
if (contrast !== 1.0) {
// Convert to 0-1 range, adjust, then back to 0-255
r = (r / 255 - 0.5) * contrast + 0.5;
g = (g / 255 - 0.5) * contrast + 0.5;
b = (b / 255 - 0.5) * contrast + 0.5;
r = Math.max(0, Math.min(1, r)) * 255;
g = Math.max(0, Math.min(1, g)) * 255;
b = Math.max(0, Math.min(1, b)) * 255;
}
// 2. Saturation adjustment
if (saturation !== 1.0) {
const lum = 0.299 * r + 0.587 * g + 0.114 * b; // Luminance
r = lum + saturation * (r - lum);
g = lum + saturation * (g - lum);
b = lum + saturation * (b - lum);
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
}
// 3. Tint application
if (applyTint) {
r = r * (1 - tintOpacity) + parsedTintColorR * tintOpacity;
g = g * (1 - tintOpacity) + parsedTintColorG * tintOpacity;
b = b * (1 - tintOpacity) + parsedTintColorB * tintOpacity;
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
}
// 4. Vignette
if (vignetteStrength > 0 && maxDist > 0) { // maxDist > 0 to prevent division by zero for 1x1 images etc.
const currentX = (i / 4) % width;
const currentY = Math.floor((i / 4) / width);
const dx = currentX - centerX;
const dy = currentY - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
let vigAmount = Math.pow(dist / maxDist, vignetteFalloff);
let vignetteFactor = 1.0 - vignetteStrength * vigAmount;
vignetteFactor = Math.max(0, Math.min(1, vignetteFactor)); // Clamp factor
r *= vignetteFactor;
g *= vignetteFactor;
b *= vignetteFactor;
// No need to clamp here if r,g,b were 0-255 and factor is 0-1
}
// 5. Film Grain (monochromatic)
if (grainAmount > 0) {
const grain = (Math.random() * 2 - 1) * grainAmount; // Random value between -grainAmount and +grainAmount
r += grain;
g += grain;
b += grain;
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
}
data[i] = Math.round(r);
data[i+1] = Math.round(g);
data[i+2] = Math.round(b);
// Alpha (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes