You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
sharpenIntensity = "1.5",
colorRestore = "1",
addVintageFrame = "1"
) {
const intensity = parseFloat(sharpenIntensity);
const doColor = parseInt(colorRestore) === 1;
const doFrame = parseInt(addVintageFrame) === 1;
let width = originalImg.width;
let height = originalImg.height;
// Scale down image to a manageable max size for performance
const MAX_SIZE = 1200;
if (width > MAX_SIZE || height > MAX_SIZE) {
const ratio = Math.min(MAX_SIZE / width, MAX_SIZE / height);
width = Math.floor(width * ratio);
height = Math.floor(height * ratio);
}
// Set up framing dimensions
const paddingX = doFrame ? Math.max(width * 0.06, 25) : 0;
const paddingYTop = doFrame ? Math.max(height * 0.06, 25) : 0;
const paddingYBot = doFrame ? Math.max(height * 0.18, 90) : 0;
const mainCanvas = document.createElement('canvas');
mainCanvas.width = width + paddingX * 2;
mainCanvas.height = height + paddingYTop + paddingYBot;
const ctx = mainCanvas.getContext('2d');
// 1. Draw the Vintage Frame
if (doFrame) {
// Base off-white color
ctx.fillStyle = '#fcfcf7';
ctx.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
// Add subtle paper grain/noise
const frameImgData = ctx.getImageData(0, 0, mainCanvas.width, mainCanvas.height);
for (let i = 0; i < frameImgData.data.length; i += 4) {
const noise = (Math.random() - 0.5) * 8;
frameImgData.data[i] += noise; // R
frameImgData.data[i + 1] += noise; // G
frameImgData.data[i + 2] += noise; // B
}
ctx.putImageData(frameImgData, 0, 0);
}
// 2. Prepare the Image Processing Canvas
const imgCanvas = document.createElement('canvas');
imgCanvas.width = width;
imgCanvas.height = height;
const imgCtx = imgCanvas.getContext('2d');
imgCtx.drawImage(originalImg, 0, 0, width, height);
let imgData = imgCtx.getImageData(0, 0, width, height);
const data = imgData.data;
// 3. Color Restoration (Auto-leveling faded colors)
if (doColor) {
let rVals = new Int32Array(256);
let gVals = new Int32Array(256);
let bVals = new Int32Array(256);
let validPixels = 0;
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] === 0) continue; // Skip transparency
rVals[data[i]]++;
gVals[data[i + 1]]++;
bVals[data[i + 2]]++;
validPixels++;
}
const threshold = validPixels * 0.01; // Clip bottom and top 1%
const getBounds = (hist) => {
let min = 0, max = 255;
let sum = 0;
for (let i = 0; i < 256; i++) {
sum += hist[i];
if (sum > threshold) { min = i; break; }
}
sum = 0;
for (let i = 255; i >= 0; i--) {
sum += hist[i];
if (sum > threshold) { max = i; break; }
}
return [min, max];
};
const [rMin, rMax] = getBounds(rVals);
const [gMin, gMax] = getBounds(gVals);
const [bMin, bMax] = getBounds(bVals);
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] === 0) continue;
data[i] = Math.min(255, Math.max(0, (data[i] - rMin) * 255 / (rMax - rMin || 1)));
data[i + 1] = Math.min(255, Math.max(0, (data[i + 1] - gMin) * 255 / (gMax - gMin || 1)));
data[i + 2] = Math.min(255, Math.max(0, (data[i + 2] - bMin) * 255 / (bMax - bMin || 1)));
}
}
imgCtx.putImageData(imgData, 0, 0);
// 4. Sharpening (Unsharp Mask for hand-held blur removal)
if (intensity > 0) {
const blurAmt = 3;
const blurCanvas = document.createElement('canvas');
blurCanvas.width = width;
blurCanvas.height = height;
const blurCtx = blurCanvas.getContext('2d');
blurCtx.filter = `blur(${blurAmt}px)`;
blurCtx.drawImage(imgCanvas, 0, 0);
const blurredData = blurCtx.getImageData(0, 0, width, height).data;
imgData = imgCtx.getImageData(0, 0, width, height);
for (let i = 0; i < imgData.data.length; i += 4) {
if (imgData.data[i + 3] === 0) continue;
for (let c = 0; c < 3; c++) {
const orig = imgData.data[i + c];
const blurred = blurredData[i + c];
const diff = orig - blurred;
imgData.data[i + c] = Math.min(255, Math.max(0, orig + (diff * intensity)));
}
}
imgCtx.putImageData(imgData, 0, 0);
}
// 5. Apply Faded Nostalgic Edges
if (doFrame) {
const grad = imgCtx.createRadialGradient(
width / 2, height / 2, Math.min(width, height) * 0.4,
width / 2, height / 2, Math.max(width, height) * 0.8
);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, 'rgba(110, 70, 30, 0.2)');
imgCtx.fillStyle = grad;
imgCtx.fillRect(0, 0, width, height);
}
// 6. Draw Processed Image onto Main Canvas
ctx.drawImage(imgCanvas, paddingX, paddingYTop);
// 7. Render "A Hand Once Held" Embellishments
if (doFrame) {
// Inner recessed stroke
ctx.strokeStyle = "rgba(0, 0, 0, 0.15)";
ctx.lineWidth = 1;
ctx.strokeRect(paddingX, paddingYTop, width, height);
// Faded Fingerprint Smudge (Bottom Right)
const fpCenterY = mainCanvas.height - (paddingYBot / 2);
const fpCenterX = mainCanvas.width - paddingX - (paddingYBot * 0.8);
ctx.lineWidth = 1.5;
ctx.strokeStyle = "rgba(100, 80, 60, 0.07)";
for (let r = 8; r < paddingYBot * 0.4; r += 5) {
ctx.beginPath();
ctx.ellipse(fpCenterX, fpCenterY, r * 1.1, r * 1.4, Math.PI / 6, 0, Math.PI * 2);
ctx.stroke();
}
// Faint Poetic Inscription (Bottom Left)
ctx.font = `italic ${Math.max(16, paddingYBot * 0.2)}px "Georgia", serif`;
ctx.fillStyle = "rgba(60, 50, 40, 0.65)";
ctx.fillText("A hand once held...", paddingX + 5, mainCanvas.height - (paddingYBot * 0.35));
}
return mainCanvas;
}
Apply Changes