You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Applies cinematic "movie studio" effects to an image.
* This includes letterboxing (cinematic black bars), color grading, film grain, and a vignette effect.
*
* @param {Image} originalImg The original javascript Image object.
* @param {string} aspectRatio The target cinematic aspect ratio as a string, e.g., "2.35:1" or "16:9".
* @param {string} colorTint A CSS color string (e.g., "#FFA500" or "rgba(0, 50, 100, 1)") for the color grading overlay.
* @param {number} tintOpacity The opacity of the color tint, from 0 (transparent) to 1 (opaque). A value around 0.1-0.2 is subtle.
* @param {number} grainAmount The intensity of the film grain effect. Recommended values 0-50. A value of 0 disables the effect.
* @param {number} vignetteAmount The strength of the vignette (darkened corners), from 0 (none) to 1 (strong black corners).
* @returns {HTMLCanvasElement} A new canvas element with the movie effects applied.
*/
async function processImage(originalImg, aspectRatio = "2.35:1", colorTint = "#FFA500", tintOpacity = 0.15, grainAmount = 20, vignetteAmount = 0.6) {
// 1. Setup Canvas
const canvas = document.createElement('canvas');
// Using { willReadFrequently: true } is a performance hint for browsers when using getImageData repeatedly.
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// 2. Draw Original Image onto the canvas
ctx.drawImage(originalImg, 0, 0);
// 3. Apply Color Tint (Color Grading)
const opacity = Number(tintOpacity);
if (!isNaN(opacity) && opacity > 0 && colorTint) {
ctx.globalAlpha = Math.min(1, Math.max(0, opacity));
ctx.fillStyle = colorTint;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1.0; // Reset for subsequent drawing operations
}
// 4. Apply Film Grain
const grain = Number(grainAmount);
if (!isNaN(grain) && grain > 0) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
const grainValue = Math.abs(grain);
for (let i = 0; i < pixels.length; i += 4) {
// Don't apply grain to fully transparent pixels
if (pixels[i + 3] === 0) continue;
const noise = (Math.random() - 0.5) * grainValue;
pixels[i] = Math.max(0, Math.min(255, pixels[i] + noise)); // Red
pixels[i + 1] = Math.max(0, Math.min(255, pixels[i + 1] + noise)); // Green
pixels[i + 2] = Math.max(0, Math.min(255, pixels[i + 2] + noise)); // Blue
}
ctx.putImageData(imageData, 0, 0);
}
// 5. Apply Vignette Effect
const vignette = Number(vignetteAmount);
if (!isNaN(vignette) && vignette > 0) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// The inner radius where the effect starts to fade in
const innerRadius = Math.min(centerX, centerY) * 0.4;
// The outer radius should reach the corners to cover the whole image
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
const gradient = ctx.createRadialGradient(
centerX, centerY, innerRadius,
centerX, centerY, outerRadius
);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${Math.min(1, Math.max(0, vignette))})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 6. Add Letterbox Bars
try {
const ratioParts = String(aspectRatio).split(':').map(Number);
if (ratioParts.length === 2 && !isNaN(ratioParts[0]) && !isNaN(ratioParts[1]) && ratioParts[1] !== 0) {
const targetRatio = ratioParts[0] / ratioParts[1];
const currentRatio = canvas.width / canvas.height;
// Only add bars if the image's aspect ratio is "taller" than the target ratio
if (currentRatio < targetRatio) {
const targetHeight = canvas.width / targetRatio;
const barHeight = Math.round((canvas.height - targetHeight) / 2);
if (barHeight > 0) {
ctx.fillStyle = 'black';
// Draw Top bar
ctx.fillRect(0, 0, canvas.width, barHeight);
// Draw Bottom bar
ctx.fillRect(0, canvas.height - barHeight, canvas.width, barHeight);
}
}
}
} catch (e) {
// Silently fail if aspect ratio is malformed to avoid breaking the process.
console.error("Could not process aspect ratio:", e);
}
// 7. Return the final canvas element
return canvas;
}
Apply Changes