You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, theme = "night", darknessLevel = "0.4", contrastLevel = "1.5", noiseLevel = "0.15", maskOverlay = "center", bloodSplatter = "yes") {
const canvas = document.createElement('canvas');
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Draw original image
ctx.drawImage(originalImg, 0, 0);
// Filter Parameters
const C = parseFloat(contrastLevel);
const dark = parseFloat(darknessLevel);
const effNoise = parseFloat(noiseLevel);
const effC = isNaN(C) ? 1.5 : C;
const effDark = isNaN(dark) ? 0.4 : dark;
const noiseVal = isNaN(effNoise) ? 0.15 : effNoise;
const adjustContrast = (c) => effC * (c - 128) + 128;
// Slasher Film Color Grading via ImageData
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
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
r = adjustContrast(r);
g = adjustContrast(g);
b = adjustContrast(b);
// 2. Base desaturation to create a grim look
const lum = 0.299 * r + 0.587 * g + 0.114 * b;
const normalizedLum = Math.min(1, Math.max(0, lum / 255));
r = r * 0.4 + lum * 0.6;
g = g * 0.4 + lum * 0.6;
b = b * 0.4 + lum * 0.6;
// 3. Theme Tint (Night Moonlight vs Blood Red)
if (theme === "blood") {
// Blood tint: heavy reds in shadows, keeps highlights mostly intact
r = r * (1.2 - 0.2 * normalizedLum);
g = g * (0.4 + 0.4 * normalizedLum);
b = b * (0.4 + 0.4 * normalizedLum);
} else {
// Night Camp Crystal Lake tint: cyan/blue shadows
r = r * (0.5 + 0.5 * normalizedLum);
g = g * (0.8 + 0.2 * normalizedLum);
b = b * (1.2 - 0.2 * normalizedLum);
}
// 4. Atmosphere Darkening
r *= (1 - effDark);
g *= (1 - effDark);
b *= (1 - effDark);
// 5. Film Grain / Noise
if (noiseVal > 0) {
const n = (Math.random() - 0.5) * 255 * noiseVal;
r += n;
g += n;
b += n;
}
// Auto-clamping applies when setting back to Uint8ClampedArray
data[i] = r;
data[i+1] = g;
data[i+2] = b;
}
ctx.putImageData(imgData, 0, 0);
// Helper: Draw Jason's Hockey Mask
function drawHockeyMask(ctx, x, y, size) {
ctx.save();
ctx.translate(x, y);
// Dirty Mask Base Gradient
const grad = ctx.createRadialGradient(0, 0, size * 0.1, 0, 0, size * 0.6);
grad.addColorStop(0, 'rgba(230, 225, 210, 1)'); // Bone white
grad.addColorStop(1, 'rgba(60, 50, 40, 1)'); // Filthy edges
ctx.fillStyle = grad;
ctx.strokeStyle = 'rgba(0,0,0,0.8)';
ctx.lineWidth = size * 0.01;
ctx.lineJoin = 'round';
ctx.beginPath();
// Outer Face
ctx.ellipse(0, 0, size * 0.45, size * 0.6, 0, 0, Math.PI * 2);
// Left Eye Hole (drawn to intersect fill)
let cxLeft = -size * 0.18, cyLeft = -size * 0.1, rx = size * 0.12, ry = size * 0.08, rotL = -0.2;
ctx.moveTo(cxLeft + rx * Math.cos(rotL), cyLeft + rx * Math.sin(rotL));
ctx.ellipse(cxLeft, cyLeft, rx, ry, rotL, 0, Math.PI * 2);
// Right Eye Hole
let cxRight = size * 0.18, cyRight = -size * 0.1, rotR = 0.2;
ctx.moveTo(cxRight + rx * Math.cos(rotR), cyRight + rx * Math.sin(rotR));
ctx.ellipse(cxRight, cyRight, rx, ry, rotR, 0, Math.PI * 2);
// Breathing Holes
const holes = [
[0, size * 0.2],
[-size * 0.1, size * 0.28], [size * 0.1, size * 0.28],
[-size * 0.18, size * 0.38], [0, size * 0.38], [size * 0.18, size * 0.38],
[-size * 0.1, -size * 0.45], [size * 0.1, -size * 0.45], [0, -size * 0.52]
];
for (let i = 0; i < holes.length; i++) {
const hr = size * 0.025;
ctx.moveTo(holes[i][0] + hr, holes[i][1]);
ctx.arc(holes[i][0], holes[i][1], hr, 0, Math.PI * 2);
}
ctx.fill("evenodd"); // Make eyes/breathing slots perfectly transparent holes!
ctx.stroke();
// Blood Red Chevrons
ctx.fillStyle = 'rgba(120, 15, 15, 0.85)';
// Forehead (pointing down towards nose)
ctx.beginPath();
ctx.moveTo(0, -size * 0.25);
ctx.lineTo(-size * 0.06, -size * 0.4);
ctx.lineTo(size * 0.06, -size * 0.4);
ctx.closePath();
ctx.fill();
// Left Cheek (pointing towards left)
ctx.beginPath();
ctx.moveTo(-size * 0.28, size * 0.1);
ctx.lineTo(-size * 0.18, size * 0.22);
ctx.lineTo(-size * 0.35, size * 0.24);
ctx.closePath();
ctx.fill();
// Right Cheek (pointing towards right)
ctx.beginPath();
ctx.moveTo(size * 0.28, size * 0.1);
ctx.lineTo(size * 0.18, size * 0.22);
ctx.lineTo(size * 0.35, size * 0.24);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Ghost Center Mask Overlay
if (maskOverlay === "center") {
ctx.globalAlpha = 0.15;
drawHockeyMask(ctx, w / 2, h / 2, Math.min(w, h) * 0.7);
ctx.globalAlpha = 1.0;
}
// Claustrophobic Vignette
const grad = ctx.createRadialGradient(
w / 2, h / 2, Math.min(w, h) * 0.3,
w / 2, h / 2, Math.min(w, h) * 0.85
);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, '#020202');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
// Corner Watermark Mask Overlay
if (maskOverlay === "corner") {
ctx.globalAlpha = 0.8;
const cornerSize = Math.min(w, h) * 0.25;
drawHockeyMask(ctx, w - cornerSize * 0.6, h - cornerSize * 0.65, cornerSize);
ctx.globalAlpha = 1.0;
}
// "Blood on the lens" Spratter Effect
if (bloodSplatter === "yes" || bloodSplatter === "true") {
ctx.fillStyle = 'rgba(180, 5, 5, 0.85)';
for (let i = 0; i < 35; i++) {
let bx, by;
// Push splatters mostly towards the edges so we don't completely cover the main subject
if (Math.random() > 0.5) {
bx = Math.random() * w;
by = Math.random() > 0.5 ? Math.random() * h * 0.15 : h - Math.random() * h * 0.15;
} else {
by = Math.random() * h;
bx = Math.random() > 0.5 ? Math.random() * w * 0.15 : w - Math.random() * w * 0.15;
}
const radius = Math.random() * Math.min(w, h) * 0.015 + 2;
ctx.beginPath();
ctx.arc(bx, by, radius, 0, Math.PI * 2);
ctx.fill();
// Sub-splats
for (let j = 0; j < 3; j++) {
const sr = radius * Math.random();
const sx = bx + (Math.random() - 0.5) * radius * 4;
const sy = by + (Math.random() - 0.5) * radius * 4;
ctx.beginPath();
ctx.arc(sx, sy, sr, 0, Math.PI * 2);
ctx.fill();
}
// Drips tapering off
if (Math.random() > 0.6) {
const dripLen = Math.random() * h * 0.1 + radius * 2;
ctx.beginPath();
ctx.moveTo(bx - radius * 0.6, by);
ctx.quadraticCurveTo(bx, by + dripLen / 2, bx, by + dripLen);
ctx.quadraticCurveTo(bx, by + dripLen / 2, bx + radius * 0.6, by);
ctx.fill();
}
}
}
return canvas;
}
Apply Changes