You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = 100, addBlackBars = "yes", addVignette = "yes", addGrain = "yes") {
// Parse strings and normalize intensity
const strength = Math.max(0, (parseFloat(intensity) || 100) / 100);
const bars = String(addBlackBars).toLowerCase().trim();
const vignette = String(addVignette).toLowerCase().trim();
const grain = String(addGrain).toLowerCase().trim();
const canvas = document.createElement('canvas');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (!width || !height) return canvas;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, width, height);
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Cinematic look involves applying an S-curve for contrast,
// Teal & Orange split-toning, slight desaturation, lifting the blacks, and film grain.
for (let i = 0; i < data.length; i += 4) {
let a = data[i+3];
// Skip fully transparent pixels
if (a === 0) continue;
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Contrast boost via Luminance S-Curve
let luma = 0.299 * r + 0.587 * g + 0.114 * b;
let lumNorm = luma / 255;
let contrastNorm = lumNorm < 0.5
? 2 * lumNorm * lumNorm
: 1 - 2 * Math.pow(1 - lumNorm, 2);
let contrastRatio = lumNorm > 0 ? contrastNorm / lumNorm : 1;
let cStrength = 0.4 * strength;
let cr = r * (1 - cStrength) + (r * contrastRatio) * cStrength;
let cg = g * (1 - cStrength) + (g * contrastRatio) * cStrength;
let cb = b * (1 - cStrength) + (b * contrastRatio) * cStrength;
// 2. Teal and Orange Split Toning Look
let currentLuma = 0.299 * cr + 0.587 * cg + 0.114 * cb;
let cLumNorm = Math.max(0, Math.min(1, currentLuma / 255));
// Create fluid masks that peak in the shadows and highlights respectively.
// Using optimized parabolas instead of slower trigonometric functions.
let shadowMix = 0;
let highlightMix = 0;
if (cLumNorm < 0.5) {
let x = cLumNorm / 0.5;
shadowMix = 4 * x * (1 - x); // Peaks at 0.25
} else {
let x = (cLumNorm - 0.5) / 0.5;
highlightMix = 4 * x * (1 - x); // Peaks at 0.75
}
// Apply distinct color shifts per tonal range
let rShift = (highlightMix * 40 - shadowMix * 20) * strength;
let gShift = (highlightMix * 10 + shadowMix * 10) * strength;
let bShift = (shadowMix * 30 - highlightMix * 30) * strength;
cr += rShift;
cg += gShift;
cb += bShift;
// 3. Cinematic desaturation
let finalLuma = 0.299 * cr + 0.587 * cg + 0.114 * cb;
let satFactor = Math.max(0, 1.0 - (0.15 * strength));
cr = finalLuma + (cr - finalLuma) * satFactor;
cg = finalLuma + (cg - finalLuma) * satFactor;
cb = finalLuma + (cb - finalLuma) * satFactor;
// 4. Lift blacks for the classical "film fade" look
let fadeLift = 12 * strength;
cr = fadeLift + cr * ((255 - fadeLift) / 255);
cg = fadeLift + cg * ((255 - fadeLift) / 255);
cb = fadeLift + cb * ((255 - fadeLift) / 255);
// 5. Film Grain
if (grain === "yes" || grain === "true" || grain === "1") {
let noise = (Math.random() - 0.5) * 20 * strength;
cr += noise;
cg += noise;
cb += noise;
}
// Output clamping correctly to 8-bit color space
data[i] = Math.min(255, Math.max(0, cr));
data[i+1] = Math.min(255, Math.max(0, cg));
data[i+2] = Math.min(255, Math.max(0, cb));
}
ctx.putImageData(imgData, 0, 0);
// Make sure overlaid graphics only apply to the opaque parts of images with transparent backgrounds
ctx.globalCompositeOperation = "source-atop";
// Add visual vignette
if (vignette === "yes" || vignette === "true" || vignette === "1") {
let cx = width / 2;
let cy = height / 2;
let radius = Math.max(width, height) * 0.75;
let gradient = ctx.createRadialGradient(cx, cy, Math.max(width, height) * 0.25, cx, cy, radius);
gradient.addColorStop(0, "rgba(0,0,0,0)");
gradient.addColorStop(1, `rgba(0,0,0,${Math.min(1, 0.5 * strength)})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
// Add cinematic letterbox (Widescreen 2.35:1 visual simulation bounds)
if (bars === "yes" || bars === "true" || bars === "1") {
let barHeight = height * 0.12;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, barHeight);
ctx.fillRect(0, height - barHeight, width, barHeight);
}
// Reset blend mode just to be clean
ctx.globalCompositeOperation = "source-over";
return canvas;
}
Apply Changes