You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, lang = "RU", addUfo = 1, dirtLevel = 70) {
// Standard A4-like proportions for the document folder
const docW = 850;
const docH = 1100;
// Create main document canvas
const docCanvas = document.createElement('canvas');
docCanvas.width = docW;
docCanvas.height = docH;
const ctx = docCanvas.getContext('2d');
// 1. Draw Vintage Paper Background with Vignette
const gradient = ctx.createRadialGradient(docW/2, docH/2, 200, docW/2, docH/2, docW);
gradient.addColorStop(0, '#dad2bf');
gradient.addColorStop(1, '#a69e8b');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, docW, docH);
// 2. Add organic paper grain to the document
const docDataObj = ctx.getImageData(0, 0, docW, docH);
const dData = docDataObj.data;
for (let i = 0; i < dData.length; i += 4) {
const noise = (Math.random() - 0.5) * 15;
dData[i] = Math.max(0, Math.min(255, dData[i] + noise));
dData[i+1] = Math.max(0, Math.min(255, dData[i+1] + noise));
dData[i+2] = Math.max(0, Math.min(255, dData[i+2] + noise));
}
ctx.putImageData(docDataObj, 0, 0);
// 3. Process Original Image into a UFO Photographic Evidence
// Calculate dimensions to fit inside the polaroid frame
const maxPhotoW = 660;
const maxPhotoH = 500;
const aspect = originalImg.height / originalImg.width;
let pW = maxPhotoW;
let pH = pW * aspect;
if (pH > maxPhotoH) {
pH = maxPhotoH;
pW = pH / aspect;
}
const pCanvas = document.createElement('canvas');
pCanvas.width = pW;
pCanvas.height = pH;
const pCtx = pCanvas.getContext('2d');
// Draw original image
pCtx.drawImage(originalImg, 0, 0, pW, pH);
// 4. Add the UFO Anomaly if requested (Drawn BEFORE the vintage filter so it blends perfectly)
if (Number(addUfo) === 1) {
// Position it somewhat randomly in the upper area
const cx = pW * (0.2 + 0.6 * Math.random());
const cy = pH * (0.05 + 0.35 * Math.random());
pCtx.save();
pCtx.filter = 'blur(3px)';
pCtx.fillStyle = 'rgba(80, 80, 80, 0.9)'; // Dark gray that will be contrasted later
// Main saucer body
pCtx.beginPath();
pCtx.ellipse(cx, cy, pW * 0.05, pW * 0.012, Math.random() * 0.4 - 0.2, 0, 2 * Math.PI);
pCtx.fill();
// Saucer dome
pCtx.beginPath();
pCtx.ellipse(cx, cy - pW * 0.007, pW * 0.018, pW * 0.01, 0, Math.PI, 0);
pCtx.fill();
pCtx.restore();
}
// 5. Apply Vintage BW, High Contrast, and Heavy Film Grain to the photo
const pDataObj = pCtx.getImageData(0, 0, pW, pH);
const pData = pDataObj.data;
const contrastFactor = 2.0;
const contrastPoint = 130;
for (let i = 0; i < pData.length; i += 4) {
// Luminance math out of RGB
const avg = 0.3 * pData[i] + 0.59 * pData[i+1] + 0.11 * pData[i+2];
// Push contrast to make it look like a harsh old duplicate
let val = contrastFactor * (avg - contrastPoint) + contrastPoint;
val = Math.max(0, Math.min(255, val));
// Add chunky film grain based on dirtLevel string/number
const grain = (Math.random() - 0.5) * Number(dirtLevel);
val += grain;
// Assign with a tiny sepia tint to match vintage look
pData[i] = Math.max(0, Math.min(255, val + 15)); // R
pData[i+1] = Math.max(0, Math.min(255, val + 8)); // G
pData[i+2] = Math.max(0, Math.min(255, val - 10)); // B
}
pCtx.putImageData(pDataObj, 0, 0);
// 6. Draw the Photo Base (White Polaroid-style frame)
const framePad = 18;
const bottomPad = 50;
const photoX = Math.floor((docW - pW) / 2);
const photoY = 300;
ctx.save();
// Shadow for the photo
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.fillStyle = '#f2ecd8'; // Aged white
ctx.fillRect(photoX - framePad, photoY - framePad, pW + framePad * 2, pH + framePad + bottomPad);
ctx.restore();
// 7. Draw the processed Photo onto the document
ctx.drawImage(pCanvas, photoX, photoY);
// 8. Helper function to draw sticky tape
function drawTape(x, y, angle) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillStyle = 'rgba(230, 230, 210, 0.7)';
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
ctx.shadowBlur = 3;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
ctx.fillRect(-45, -12, 90, 24);
// Add tape wrinkles/stripes
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
ctx.lineWidth = 1;
ctx.beginPath();
for(let i = -40; i < 40; i+= 6) {
ctx.moveTo(i, -12);
ctx.lineTo(i + (Math.random()*4-2), 12);
}
ctx.stroke();
ctx.restore();
}
// Attach corners with tape
drawTape(photoX - 5, photoY - 5, -Math.PI / 6);
drawTape(photoX + pW + 5, photoY - 5, Math.PI / 5);
drawTape(photoX - 5, photoY + pH + 30, Math.PI / 7);
drawTape(photoX + pW + 5, photoY + pH + 30, -Math.PI / 8);
// 9. Typography and Text details
const textData = String(lang).toUpperCase() === "EN" ? {
title: "D.O.D. ARCHIVAL RECORD",
caseNum: "CASE #: 71-X / " + Math.floor(1960 + Math.random()*20),
subject: "SUBJECT: UNIDENTIFIED FLYING OBJECT",
locPre: "COORDINATES: ",
locRedacted: "[REDACTED]",
datePre: "ENCOUNTER DATE: ",
dateRedacted: "[ CLASSIFIED ]",
attachment: "ATTACHMENT A: PHOTOGRAPHIC EVIDENCE",
stamp: " T O P S E C R E T "
} : {
title: "КГБ СССР: МАТЕРИАЛЫ ДЕЛА",
caseNum: "ДЕЛО №: 47-X / " + Math.floor(1960 + Math.random()*20),
subject: "ОБЪЕКТ: НЕОПОЗНАННЫЙ ЛЕТАЮЩИЙ АППАРАТ",
locPre: "МЕСТО: ",
locRedacted: "[ЗАСЕКРЕЧЕНО]",
datePre: "ДАТА КОНТАКТА: ",
dateRedacted: "[ ИЗЪЯТО ДЛЯ АНАЛИЗА ]",
attachment: "ПРИЛОЖЕНИЕ: ФОТОФИКСАЦИЯ АНОМАЛИИ",
stamp: "СОВЕРШЕННО СЕКРЕТНО"
};
ctx.fillStyle = 'rgba(30, 30, 30, 0.9)'; // Slightly faded typewriter ink
ctx.textBaseline = "middle";
// Header block
ctx.font = 'bold 36px "Courier New", Courier, monospace';
ctx.fillText(textData.title, 80, 80);
// Details block
ctx.font = 'bold 22px "Courier New", Courier, monospace';
let currentY = 140;
ctx.fillText(textData.caseNum, 80, currentY); currentY += 35;
ctx.fillText(textData.subject, 80, currentY); currentY += 35;
// Helper for Redaction Box
function fillRedaction(prefix, redacted, x, y) {
ctx.fillStyle = 'rgba(30, 30, 30, 0.9)';
ctx.fillText(prefix + redacted, x, y);
const w1 = ctx.measureText(prefix).width;
const w2 = ctx.measureText(redacted).width;
ctx.fillStyle = '#111'; // Pure marker black
// Slightly unaligned marker box for realism
ctx.fillRect(x + w1 - 4, y - 14 + (Math.random()*4-2), w2 + 8, 28);
}
fillRedaction(textData.datePre, textData.dateRedacted, 80, currentY); currentY += 35;
fillRedaction(textData.locPre, textData.locRedacted, 80, currentY); currentY += 50;
// Bottom attachment note
ctx.fillStyle = 'rgba(30, 30, 30, 0.9)';
ctx.fillText(textData.attachment, photoX - framePad + 10, photoY + pH + framePad + 15);
// 10. Giant Red Stamp (Rotated and Blended)
ctx.save();
ctx.globalCompositeOperation = 'multiply';
ctx.translate(docW - 270, 150);
ctx.rotate(0.2 - Math.random() * 0.4);
const stampColor = 'rgba(180, 20, 20, 0.7)';
ctx.strokeStyle = stampColor;
ctx.fillStyle = stampColor;
ctx.lineWidth = 6;
// Outer border
ctx.strokeRect(0, 0, 380, 64);
// Inner border
ctx.lineWidth = 2;
ctx.strokeRect(6, 6, 368, 52);
// Text inside stamp
ctx.font = 'bold 32px "Arial", sans-serif';
ctx.textAlign = 'center';
ctx.fillText(textData.stamp, 190, 32);
// Draw some scratches to "distress" the stamp
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 2;
for(let i = 0; i < 30; i++) {
ctx.beginPath();
let sx = Math.random() * 380;
let sy = Math.random() * 64;
ctx.moveTo(sx, sy);
ctx.lineTo(sx + Math.random() * 10 - 5, sy + Math.random() * 10 - 5);
ctx.stroke();
}
ctx.restore();
return docCanvas;
}
Apply Changes