You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, darknessLevel = "0.85", contrastLevel = "1.6", addPosterText = "true") {
// Parse parameters
const darkness = Math.min(1, Math.max(0, parseFloat(darknessLevel) || 0.85));
const contrast = parseFloat(contrastLevel) || 1.6;
const includeText = String(addPosterText).toLowerCase() === "true";
// Setup canvas
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw original image
ctx.drawImage(originalImg, 0, 0);
// Get pixel data
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Apply "Lights Out" Color Grading & Contrast
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 (Center around 128 mid-gray)
r = ((r / 255 - 0.5) * contrast + 0.5) * 255;
g = ((g / 255 - 0.5) * contrast + 0.5) * 255;
b = ((b / 255 - 0.5) * contrast + 0.5) * 255;
// Clamp values
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
// 2. Calculate Luminance
let lum = 0.299 * r + 0.587 * g + 0.114 * b;
// 3. Cinematic Horror Color Grading (Teal Shadows, Pale Yellow Highlights)
// Deepen shadows and tint them cyan/teal
if (lum < 128) {
let shadowIntensity = 1 - (lum / 128); // 1 at pitch black, 0 at mid-gray
r -= shadowIntensity * 40;
g += shadowIntensity * 15;
b += shadowIntensity * 40;
}
// Tint highlights slightly sickly warm
else {
let highlightIntensity = (lum - 128) / 128; // 0 at mid-gray, 1 at pure white
r += highlightIntensity * 10;
g += highlightIntensity * 10;
b -= highlightIntensity * 20;
}
// Clamp again after color grading
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
// 4. Overall Darkening
const darkeningFactor = 1 - (darkness * 0.4);
r *= darkeningFactor;
g *= darkeningFactor;
b *= darkeningFactor;
// 5. Add Film Grain / Noise
const noise = (Math.random() - 0.5) * 25; // Random value between -12.5 and +12.5
r += noise;
g += noise;
b += noise;
// Assign back to data array
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
}
// Put modified pixels back
ctx.putImageData(imgData, 0, 0);
// Apply Heavy "Lights Out" Vignette / Spotlight Effect
const centerX = width / 2;
// Spotlight slightly higher than center (usually where faces are)
const centerY = height * 0.4;
const maxRadius = Math.max(width, height);
const gradient = ctx.createRadialGradient(centerX, centerY, maxRadius * 0.1, centerX, centerY, maxRadius * 0.8);
gradient.addColorStop(0, 'rgba(0, 0, 20, 0)'); // Clear/slight blue tint in center
gradient.addColorStop(0.4, `rgba(0, 5, 15, ${darkness * 0.7})`); // Mid-darkness
gradient.addColorStop(0.8, 'rgba(0, 0, 0, 0.98)'); // Almost pitch black
gradient.addColorStop(1, '#000000'); // Pitch black edges
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Add subtle film scratch overlays
ctx.lineWidth = 1;
const numScratches = Math.floor(width * 0.05); // Density based on image width
for(let i = 0; i < numScratches; i++) {
let x = Math.random() * width;
// Mostly dark scratches, occasionally light
ctx.strokeStyle = Math.random() > 0.85 ? 'rgba(255, 255, 255, 0.03)' : 'rgba(0, 0, 0, 0.15)';
ctx.beginPath();
ctx.moveTo(x, 0);
let y = 0;
while(y < height) {
y += Math.random() * 50;
ctx.lineTo(x + (Math.random() - 0.5) * 3, y); // Slight horizontal waving
}
ctx.stroke();
}
// Add Movie Poster Text
if (includeText) {
// Prepare text dimensions
const mainTitleSize = Math.max(24, Math.floor(width * 0.09));
const subTitleSize = Math.max(12, Math.floor(width * 0.025));
ctx.textAlign = "center";
// Attempt to use letter spacing if supported natively by the canvas API in modern browsers
if ('letterSpacing' in ctx) {
ctx.letterSpacing = `${Math.floor(mainTitleSize * 0.25)}px`;
}
// Draw Subtitle / Tagline
ctx.textBaseline = "bottom";
ctx.font = `bold ${subTitleSize}px Arial, sans-serif`;
ctx.fillStyle = "rgba(220, 230, 240, 0.7)"; // Ghostly cool-white
ctx.shadowColor = "rgba(100, 150, 255, 0.3)";
ctx.shadowBlur = 5;
const taglineY = height - (mainTitleSize * 1.5) - (height * 0.03);
ctx.fillText("YOU WERE RIGHT TO BE AFRAID OF THE DARK.", width / 2, taglineY);
// Draw Main Title
ctx.font = `bold ${mainTitleSize}px "Helvetica Neue", Helvetica, Arial, sans-serif`;
ctx.fillStyle = "#FFFFFF"; // Stark white
// Apply a glowing/light bleed effect from the dark
ctx.shadowColor = "rgba(255, 255, 255, 0.6)";
ctx.shadowBlur = 15;
const titleY = height - (height * 0.05);
ctx.fillText("LIGHTS OUT", width / 2, titleY);
// Overlay a second layer for the title with slightly dark gradient to mimic rough lighting
const textGradient = ctx.createLinearGradient(0, titleY - mainTitleSize, 0, titleY);
textGradient.addColorStop(0, "rgba(255, 255, 255, 1)");
textGradient.addColorStop(1, "rgba(150, 150, 160, 1)");
ctx.fillStyle = textGradient;
ctx.shadowBlur = 0; // Disable blur for sharp overlay
ctx.fillText("LIGHTS OUT", width / 2, titleY);
// Reset letter spacing
if ('letterSpacing' in ctx) {
ctx.letterSpacing = "0px";
}
}
return canvas;
}
Apply Changes