You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
caseNumber = "47-X / 1965",
objectName = "НЕОПОЗНАННЫЙ ЛЕТАЮЩИЙ АППАРАТ",
date = "01.08.2026",
location = "Сектор 7",
archiveStatus = "СЕКРЕТНО",
bodyText = "О ПОХИЩЕННЫХ ЛЕТУЧИХ МЫШАХ\n\nОбстоятельства инцидента:\nПод покровом ночи на специализированный полигон проник неизвестный, было похищено как минимум 17 редких особей и ядовитая летучая мышь. На месте преступления найдены лишь обрывки сети, надкушенный манго и странный высокочастотный излучатель.\n\nПодозреваемые лица и улики: К делу приобщен неизвестный фигурант, чей лик зафиксирован на фотокарточке подозреваемого: неестественная челюсть и подозрительные действия в последние недели.\n\nЗаключение следствия:\nВиновный подлежит немедленной изоляции и допросу с применением спецсредств.\n\nПРИЛОЖЕНИЕ: ФОТОФИКСАЦИЯ АНОМАЛИИ",
stampText = "СОВЕРШЕННО СЕКРЕТНО"
) {
const canvas = document.createElement('canvas');
const width = 850;
const height = 1200;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Generate Aged Paper Background
ctx.fillStyle = '#ebd6b7';
ctx.fillRect(0, 0, width, height);
// Apply Noise and Grain
const imgData = ctx.getImageData(0, 0, width, height);
for (let i = 0; i < imgData.data.length; i += 4) {
const noise = (Math.random() - 0.5) * 25;
imgData.data[i] = Math.max(0, Math.min(255, imgData.data[i] + noise)); // R
imgData.data[i+1] = Math.max(0, Math.min(255, imgData.data[i+1] + noise)); // G
imgData.data[i+2] = Math.max(0, Math.min(255, imgData.data[i+2] + noise)); // B
}
ctx.putImageData(imgData, 0, 0);
// Vignette Effect (Darken edges)
const vignette = ctx.createRadialGradient(width/2, height/2, 200, width/2, height/2, height * 0.8);
vignette.addColorStop(0, 'rgba(0,0,0,0)');
vignette.addColorStop(1, 'rgba(60, 40, 20, 0.45)');
ctx.fillStyle = vignette;
ctx.fillRect(0, 0, width, height);
// 2. Setup Typography & Top Header
ctx.fillStyle = '#222';
ctx.textAlign = 'center';
// Top headers
ctx.font = "bold 32px 'Courier New', Courier, monospace";
ctx.fillText("КГБ СССР", width / 2, 70);
ctx.font = "bold 26px 'Courier New', Courier, monospace";
ctx.fillText("МАТЕРИАЛЫ ДЕЛА", width / 2, 105);
// Line separator
ctx.beginPath();
ctx.moveTo(80, 125);
ctx.lineTo(width - 80, 125);
ctx.lineWidth = 2;
ctx.strokeStyle = '#333';
ctx.stroke();
// 3. Info Fields (Left Side)
ctx.textAlign = 'left';
ctx.font = "bold 20px 'Courier New', Courier, monospace";
const infoLines = [
`ДЕЛО №: ${caseNumber}`,
`ОБЪЕКТ: ${objectName}`,
`ДАТА КОНТАКТА: ${date}`,
`МЕСТО: ${location}`,
`АРХИВ: ${archiveStatus}`
];
let currentY = 180;
for (const line of infoLines) {
ctx.fillText(line, 80, currentY);
currentY += 45;
}
// 4. Attach Photo (Right Side)
const photoMaxWidth = 300;
const photoMaxHeight = 300;
const imgAspect = originalImg.width / originalImg.height;
let pWidth = photoMaxWidth;
let pHeight = pWidth / imgAspect;
if (pHeight > photoMaxHeight) {
pHeight = photoMaxHeight;
pWidth = pHeight * imgAspect;
}
const pX = width - 80 - pWidth;
const pY = 160;
// Draw photo border
ctx.save();
ctx.translate(pX + pWidth/2, pY + pHeight/2);
ctx.rotate(Math.random() * 0.06 - 0.03); // Slight random tilt
ctx.translate(-(pX + pWidth/2), -(pY + pHeight/2));
// White photo paper frame
ctx.fillStyle = '#f4f4f4';
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
const border = 12;
ctx.fillRect(pX - border, pY - border, pWidth + border * 2, pHeight + border * 2);
// Remove shadow for the image itself
ctx.shadowColor = 'transparent';
// Apply KGB gritty B&W filter
ctx.filter = 'grayscale(100%) contrast(160%) sepia(20%) brightness(90%)';
ctx.drawImage(originalImg, pX, pY, pWidth, pHeight);
ctx.filter = 'none';
// Draw Tape (Top Left, Bottom Right of the photo frame)
function drawTape(x, y, angle) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillStyle = 'rgba(230, 220, 180, 0.6)';
ctx.shadowColor = 'rgba(0,0,0,0.2)';
ctx.shadowBlur = 3;
ctx.fillRect(-30, -10, 60, 20);
ctx.restore();
}
drawTape(pX - border, pY - border, -Math.PI / 4);
drawTape(pX + pWidth + border, pY + pHeight + border, -Math.PI / 4);
ctx.restore();
// 5. Body Text (Typewriter Style)
ctx.font = "20px 'Courier New', Courier, monospace";
ctx.fillStyle = '#262626'; // Not pitch black, slightly faded ink
const contentYStart = Math.max(currentY + 20, pY + pHeight + 40);
currentY = contentYStart;
const textMaxWidth = width - 160;
// Function to wrap and draw text
function drawParagraph(text, x, y, maxWidth, lineHeight) {
const paragraphs = text.split('\n');
for (const para of paragraphs) {
const words = para.split(' ');
let line = '';
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
// Randomize alpha slightly for typewriter effect
ctx.globalAlpha = 0.8 + Math.random() * 0.2;
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
} else {
line = testLine;
}
}
if (line.trim().length > 0) {
ctx.globalAlpha = 0.8 + Math.random() * 0.2;
ctx.fillText(line, x, y);
y += lineHeight;
}
// Paragraph spacing
y += lineHeight * 0.5;
}
ctx.globalAlpha = 1.0;
return y;
}
currentY = drawParagraph(bodyText, 80, currentY, textMaxWidth, 28);
// Signatures Area
currentY += 40;
ctx.font = "bold 20px 'Courier New', Courier, monospace";
ctx.fillText("Начальник следственного комитета: _________________", 80, currentY);
// Fake hand-written squiggle
ctx.save();
ctx.strokeStyle = '#1b315e'; // Faded pen blue
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(500, currentY - 5);
ctx.bezierCurveTo(520, currentY - 20, 540, currentY + 10, 550, currentY - 5);
ctx.bezierCurveTo(570, currentY - 15, 590, currentY, 610, currentY - 8);
ctx.bezierCurveTo(620, currentY - 10, 630, currentY - 2, 640, currentY - 12);
ctx.stroke();
ctx.restore();
// 6. Stamps and Overlays (Multiply blend mode)
ctx.globalCompositeOperation = 'multiply';
// Rectangular Top Secret Stamp
ctx.save();
ctx.translate(width / 2 + 100, contentYStart - 40);
ctx.rotate(-15 * Math.PI / 180);
ctx.strokeStyle = '#b01111';
ctx.fillStyle = '#b01111';
ctx.lineWidth = 4;
ctx.font = "bold 42px 'Times New Roman', Times, serif";
// Add grit/stamp effect by making alpha 0.7
ctx.globalAlpha = 0.7;
const stampWidth = ctx.measureText(stampText).width + 30;
const stampHeight = 60;
// Outer box
ctx.strokeRect(-stampWidth/2, -stampHeight/2, stampWidth, stampHeight);
// Inner box
ctx.lineWidth = 1.5;
ctx.strokeRect(-stampWidth/2 + 6, -stampHeight/2 + 6, stampWidth - 12, stampHeight - 12);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(stampText, 0, 2);
ctx.restore();
// Circular Archive Stamp
ctx.save();
ctx.translate(220, currentY - 30);
ctx.rotate(Math.random() * Math.PI); // Random rotation
ctx.globalAlpha = 0.6;
ctx.strokeStyle = '#432d66'; // Purple-ish ink
ctx.fillStyle = '#432d66';
ctx.lineWidth = 3;
// Outer Circle
ctx.beginPath();
ctx.arc(0, 0, 60, 0, Math.PI * 2);
ctx.stroke();
// Inner Circle
ctx.beginPath();
ctx.arc(0, 0, 50, 0, Math.PI * 2);
ctx.stroke();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = "bold 16px Arial, sans-serif";
ctx.fillText("КГБ СССР", 0, -15);
ctx.fillText("АРХИВ", 0, 5);
ctx.font = "12px Arial, sans-serif";
ctx.fillText("★ СЕКРЕТНО ★", 0, 25);
ctx.restore();
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes