You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
intensity = 100,
tone = "sepia", // options: "sepia", "grayscale", "warm", "cyanotype"
noiseLevel = 30, // 0 - 100
scratchCount = 15, // 0 - 100
vignetteStrength = 50, // 0 - 100
blurAmount = 1, // 0 - 10
contrast = -10, // -50 to 50
brightness = 10, // -50 to 50
addBorder = "yes", // "yes" or "no"
dustLevel = 20 // 0 - 100
) {
// Create a new canvas to process the image
const canvas = document.createElement("canvas");
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
// Calculate effect intensity factor (0 to 1)
const intFactor = Math.max(0, Math.min(100, Number(intensity))) / 100;
// If intensity is 0, just return the original image exactly as is
if (intFactor === 0) {
ctx.drawImage(originalImg, 0, 0);
return canvas;
}
// 1. Initial Image Pass (Filters: Blur, Brightness, Contrast, and Base Tone)
const blurVal = Math.max(0, Number(blurAmount)) * intFactor;
const brVal = 100 + Number(brightness) * intFactor;
const ctVal = 100 + Number(contrast) * intFactor;
let filters = `blur(${blurVal}px) brightness(${brVal}%) contrast(${ctVal}%)`;
const selectedTone = String(tone).toLowerCase();
if (selectedTone === "sepia") {
filters += ` sepia(${100 * intFactor}%)`;
} else if (
selectedTone === "grayscale" ||
selectedTone === "cyanotype" ||
selectedTone === "warm"
) {
filters += ` grayscale(${100 * intFactor}%)`;
}
ctx.filter = filters;
ctx.drawImage(originalImg, 0, 0, w, h);
ctx.filter = "none";
// 2. Specialized Tonal Tints (Cyanotype/Warm)
if (selectedTone === "cyanotype") {
// Cyanotype is a dark blue print
ctx.globalCompositeOperation = "color";
ctx.fillStyle = `rgba(15, 76, 129, ${intFactor})`;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "overlay";
ctx.fillStyle = `rgba(15, 76, 129, ${0.4 * intFactor})`;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "source-over";
} else if (selectedTone === "warm") {
// Deep saddle brown tint over grayscale
ctx.globalCompositeOperation = "color";
ctx.fillStyle = `rgba(139, 90, 43, ${0.8 * intFactor})`;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "source-over";
}
// 3. Vignette Effect (Darkened Corners)
const parsedVignette = Number(vignetteStrength);
if (parsedVignette > 0) {
const cx = w / 2;
const cy = h / 2;
const radius = Math.sqrt(cx * cx + cy * cy);
const gradient = ctx.createRadialGradient(cx, cy, radius * 0.3, cx, cy, radius);
gradient.addColorStop(0, "rgba(0,0,0,0)");
gradient.addColorStop(1, `rgba(0,0,0,${(parsedVignette / 100) * intFactor})`);
ctx.globalCompositeOperation = "multiply";
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = "source-over";
}
// 4. Draw Physical Damage (Film Scratches)
const parsedScratches = Number(scratchCount);
if (parsedScratches > 0) {
const numScratches = Math.floor(parsedScratches * intFactor * (w / 800));
for (let i = 0; i < numScratches; i++) {
ctx.beginPath();
const sx = Math.random() * w;
const sy = Math.random() * h * 0.1;
const ex = sx + (Math.random() * 60 - 30);
const ey = h - Math.random() * h * 0.1;
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.lineWidth = Math.random() * 1.5 + 0.5;
// Randomize scratch color (light or dark)
if (Math.random() > 0.4) {
ctx.strokeStyle = `rgba(255, 255, 255, ${Math.random() * 0.4 * intFactor})`;
} else {
ctx.strokeStyle = `rgba(0, 0, 0, ${Math.random() * 0.4 * intFactor})`;
}
ctx.stroke();
}
}
// 5. Draw Physical Damage (Dust and Specks)
const parsedDust = Number(dustLevel);
if (parsedDust > 0) {
const numDust = Math.floor(parsedDust * intFactor * ((w * h) / 50000));
for (let i = 0; i < numDust; i++) {
const x = Math.random() * w;
const y = Math.random() * h;
const size = Math.random() * 2.5 + 0.5;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
if (Math.random() > 0.3) {
ctx.fillStyle = `rgba(0,0,0,${Math.random() * 0.6 * intFactor})`;
} else {
ctx.fillStyle = `rgba(255,255,255,${Math.random() * 0.5 * intFactor})`;
}
ctx.fill();
}
}
// 6. Draw 1880s Paper/Card Border Over the Edges
if (String(addBorder).trim().toLowerCase() === "yes") {
const bt = Math.max(12, w * 0.04); // Base thickness of the border
ctx.fillStyle = `rgba(220, 210, 190, ${intFactor})`; // Pale aged paper color
// Draw the 4 sides of the border over the photo
ctx.fillRect(0, 0, w, bt); // Top
ctx.fillRect(0, h - bt, w, bt); // Bottom
ctx.fillRect(0, bt, bt, h - 2 * bt); // Left
ctx.fillRect(w - bt, bt, bt, h - 2 * bt); // Right
// Outer dark burnt edge
ctx.strokeStyle = `rgba(0,0,0,${0.3 * intFactor})`;
ctx.lineWidth = 1;
ctx.strokeRect(0, 0, w, h);
ctx.strokeRect(bt, bt, w - 2 * bt, h - 2 * bt);
// Typical inset ornamental border line often found on old cabinet cards
const inset = bt + w * 0.015;
ctx.lineWidth = Math.max(1, w * 0.003);
ctx.strokeStyle = `rgba(60, 40, 20, ${0.7 * intFactor})`;
ctx.strokeRect(inset, inset, w - 2 * inset, h - 2 * inset);
}
// 7. Apply Film Grain (Noise) via direct pixel manipulation
const parsedNoise = Number(noiseLevel);
if (parsedNoise > 0) {
const idata = ctx.getImageData(0, 0, w, h);
const data = idata.data;
const noiseAmt = parsedNoise * 1.2 * intFactor; // Scale noise to a reasonable 0-255 delta
for (let i = 0; i < data.length; i += 4) {
// Apply monochromatic grain only if the pixel isn't fully transparent
if (data[i + 3] > 0) {
const noise = (Math.random() - 0.5) * noiseAmt;
data[i] += noise; // Red
data[i + 1] += noise; // Green
data[i + 2] += noise; // Blue
}
}
ctx.putImageData(idata, 0, 0);
}
return canvas;
}
Apply Changes