You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, tintColor = "rgba(40, 60, 100, 0.25)", contrast = 1.2, saturation = 0.85, grain = 0.05, vignetteDarkness = 0.7, vignetteClearRadius = 0.4) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
// 1. Apply contrast and saturation filters, then draw image
// These filters are applied to the drawing operation itself.
let filterString = "";
if (typeof contrast === 'number' && contrast !== 1) {
filterString += `contrast(${contrast * 100}%) `;
}
if (typeof saturation === 'number' && saturation !== 1) {
filterString += `saturate(${saturation * 100}%)`;
}
if (filterString.trim() !== "") {
ctx.filter = filterString.trim();
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Reset filter for subsequent operations on the canvas
if (ctx.filter !== 'none' && ctx.filter !== "") {
ctx.filter = 'none';
}
// 2. Apply Color Tint
// The 'color' composite mode takes the hue and saturation of the source (fillStyle)
// and the luma of the destination (current canvas content).
// The alpha of fillStyle moderates the effect.
if (typeof tintColor === 'string' && tintColor.trim() !== "") {
let applyTint = true;
// Check if tintColor is explicitly transparent or has alpha <= 0
if (tintColor.toLowerCase() === 'transparent') {
applyTint = false;
} else if (tintColor.startsWith('rgba(')) {
try {
const parts = tintColor.substring(tintColor.indexOf('(') + 1, tintColor.lastIndexOf(')')).split(',');
if (parts.length === 4) {
const alpha = parseFloat(parts[3].trim());
if (!isNaN(alpha) && alpha <= 0) {
applyTint = false;
}
}
} catch (e) {
// Issue parsing rgba, proceed with caution or log warning
console.warn("Could not parse alpha from tintColor:", tintColor, e);
}
}
if (applyTint) {
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = tintColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
}
}
// 3. Apply Film Grain
if (typeof grain === 'number' && grain > 0 && grain <= 1) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
// Adjust noiseFactor: e.g. grain * 25 gives noise range roughly +/-12.5 for grain=1
const noiseFactor = grain * 25;
for (let i = 0; i < pixels.length; i += 4) {
// Add independent noise to R, G, B channels
// Math.random() gives [0, 1), so (Math.random() - 0.5) is [-0.5, 0.5)
const rNoise = (Math.random() - 0.5) * noiseFactor;
const gNoise = (Math.random() - 0.5) * noiseFactor;
const bNoise = (Math.random() - 0.5) * noiseFactor;
pixels[i] = Math.max(0, Math.min(255, pixels[i] + rNoise));
pixels[i + 1] = Math.max(0, Math.min(255, pixels[i + 1] + gNoise));
pixels[i + 2] = Math.max(0, Math.min(255, pixels[i + 2] + bNoise));
// Alpha (pixels[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
}
// 4. Apply Vignette
// vignetteDarkness: 0 (no darkening) to 1 (edges are black).
// vignetteClearRadius: 0 (starts darkening from center) to 1 (no vignette).
if (typeof vignetteDarkness === 'number' && vignetteDarkness > 0 &&
typeof vignetteClearRadius === 'number' && vignetteClearRadius >= 0 && vignetteClearRadius < 1) {
ctx.globalCompositeOperation = 'multiply'; // Use multiply to darken the image
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// The outer radius of the gradient should cover the corners of the image
const outerActualRadius = Math.sqrt(centerX * centerX + centerY * centerY);
// The inner radius where the vignette effect begins (still white for multiply)
const innerClearActualRadius = outerActualRadius * vignetteClearRadius;
const gradient = ctx.createRadialGradient(
centerX, centerY, innerClearActualRadius,
centerX, centerY, outerActualRadius
);
// Determine the color for the edge of the vignette
// For 'multiply' mode: white (255,255,255) means no change.
// Darker colors will darken the image.
// vignetteDarkness = 1 means black (0,0,0).
// vignetteDarkness = 0 means white (255,255,255).
const edgeColorVal = Math.max(0, Math.min(255, Math.round(255 * (1 - vignetteDarkness))));
const vignetteEdgeRgbColor = `rgb(${edgeColorVal},${edgeColorVal},${edgeColorVal})`;
gradient.addColorStop(0, 'white'); // Center of gradient (up to innerClearActualRadius) is white
gradient.addColorStop(1, vignetteEdgeRgbColor); // Edge of gradient (at outerActualRadius) is the vignette color
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
}
return canvas;
}
Apply Changes