You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
caseTitle = "CONFIDENTIAL REPORT - CASE No. " + Math.floor(Math.random() * 8999 + 1001),
subjectName = "CLASSIFIED",
caseNotes = "Initial findings suggest a matter of considerable intricacy. The subject exhibits peculiar habits. Further surveillance is recommended before drawing definitive conclusions. Discretion is paramount.",
stampText = "CLASSIFIED",
textColor = "#3a2b20", // Dark Brown/Black
stampColor = "#8B0000", // Dark Red
paperColor = "#f3eadd", // A light, aged paper color (e.g., antique white, beige)
bodyFontFamily = "'Courier New', Courier, monospace",
headingFontFamily = "'Georgia', 'Times New Roman', serif",
applySepiaToImage = "true"
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Layout dimensions
const canvasWidth = 800;
const canvasHeight = 1130; // Approx A4 ratio, taller for content
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const sepiaActive = applySepiaToImage.toLowerCase() === 'true';
// 1. Background
ctx.fillStyle = paperColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Define margins and content areas
const margin = 50;
const contentWidth = canvasWidth - 2 * margin;
let currentY = margin;
// 2. Case Title Header
ctx.fillStyle = textColor;
ctx.font = `bold 32px ${headingFontFamily}`;
ctx.textAlign = 'center';
currentY += 40; // Top padding for title
ctx.fillText(caseTitle.toUpperCase(), canvasWidth / 2, currentY);
currentY += 25; // Space after title
// Horizontal line
ctx.beginPath();
ctx.moveTo(margin, currentY);
ctx.lineTo(canvasWidth - margin, currentY);
ctx.strokeStyle = textColor;
ctx.lineWidth = 1.5;
ctx.stroke();
currentY += 30; // Space after line
// 3. Image Area
const imgSectionYStart = currentY;
const imgMaxContainerHeight = 330; // Max height for the image and its matte
const imgMaxContainerWidth = contentWidth * 0.75; // Image container can take 75% of content width
let scale = 1;
if (originalImg.width > 0 && originalImg.height > 0) { // Ensure image has dimensions
scale = Math.min(imgMaxContainerWidth / originalImg.width, imgMaxContainerHeight / originalImg.height);
} else { // Handle cases where image might not be fully loaded or is invalid
scale = 0; // Effectively don't draw the image
}
const imgDisplayWidth = originalImg.width * scale;
const imgDisplayHeight = originalImg.height * scale;
const imgX = (canvasWidth - imgDisplayWidth) / 2; // Center the image
const imgY = currentY;
const mattePadding = 8;
if (imgDisplayWidth > 0 && imgDisplayHeight > 0) {
// Photo matte/background
ctx.fillStyle = '#E0E0E0'; // Light gray for photo matte
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillRect(imgX - mattePadding, imgY - mattePadding, imgDisplayWidth + 2 * mattePadding, imgDisplayHeight + 2 * mattePadding);
ctx.shadowColor = 'transparent'; // Reset shadow
ctx.strokeStyle = '#707070'; // Darker gray border for matte
ctx.lineWidth = 1;
ctx.strokeRect(imgX - mattePadding, imgY - mattePadding, imgDisplayWidth + 2 * mattePadding, imgDisplayHeight + 2 * mattePadding);
// Draw the image
if (sepiaActive) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = imgDisplayWidth;
tempCanvas.height = imgDisplayHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.filter = 'sepia(80%) grayscale(10%) contrast(110%) brightness(95%)';
tempCtx.drawImage(originalImg, 0, 0, imgDisplayWidth, imgDisplayHeight);
ctx.drawImage(tempCanvas, imgX, imgY, imgDisplayWidth, imgDisplayHeight);
} else {
ctx.drawImage(originalImg, imgX, imgY, imgDisplayWidth, imgDisplayHeight);
}
// Thin border directly on the image
ctx.strokeStyle = textColor;
ctx.lineWidth = 0.5;
ctx.strokeRect(imgX, imgY, imgDisplayWidth, imgDisplayHeight);
}
currentY += Math.max(imgDisplayHeight + 2 * mattePadding, 0) + 30; // Space after image block (includes matte)
// 4. Subject Name
ctx.fillStyle = textColor;
ctx.font = `bold 18px ${headingFontFamily}`;
ctx.textAlign = 'left';
const subjectLabel = "SUBJECT OF INQUIRY:";
ctx.fillText(subjectLabel, margin, currentY);
ctx.font = `18px ${bodyFontFamily}`;
const subjectNameX = margin + ctx.measureText(subjectLabel).width + 10;
ctx.fillText(subjectName, subjectNameX, currentY);
currentY += 30;
// Horizontal line
ctx.beginPath();
ctx.moveTo(margin, currentY);
ctx.lineTo(canvasWidth - margin, currentY);
ctx.strokeStyle = textColor;
ctx.globalAlpha = 0.6;
ctx.lineWidth = 0.5;
ctx.stroke();
ctx.globalAlpha = 1.0;
currentY += 30;
// 5. Case Notes heading
ctx.fillStyle = textColor;
ctx.font = `bold 18px ${headingFontFamily}`;
ctx.fillText("OBSERVATIONS & NOTES:", margin, currentY);
currentY += 30; // Space before notes content
// Case Notes content
ctx.font = `16px ${bodyFontFamily}`;
const lineHeight = 22;
const maxNoteLines = Math.floor((canvasHeight - currentY - 100) / lineHeight); // Dynamically calculate max lines based on remaining space
let lineCount = 0;
const words = caseNotes.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 > contentWidth && line.length > 0) { // Ensure 'line' is not empty before printing
if (lineCount === maxNoteLines - 1 && n < words.length) { // Check if this is the last allowed line
let textToPrint = line.trimRight();
if (ctx.measureText(textToPrint + "...").width > contentWidth) {
while(textToPrint.length > 0 && ctx.measureText(textToPrint + "...").width > contentWidth) {
textToPrint = textToPrint.slice(0,-1);
}
}
ctx.fillText(textToPrint + "...", margin, currentY);
line = ''; // Clear line as it's handled
} else {
ctx.fillText(line, margin, currentY);
}
line = words[n] + ' '; // Start new line with current word
currentY += lineHeight;
lineCount++;
if (lineCount >= maxNoteLines) {
if (lineCount === maxNoteLines && line.trim() !== '' && n < words.length -1) { // If we have more words after max lines
let lastBits = line.trimRight();
while(lastBits.length > 0 && ctx.measureText(lastBits + "...").width > contentWidth) {
lastBits = lastBits.slice(0,-1);
}
ctx.fillText(lastBits + "...", margin, currentY - lineHeight); // Overwrite previous line's end partially
}
line = ''; // Clear line as we've hit limit
break;
}
} else {
line = testLine;
}
}
if (line.trim() !== '' && lineCount < maxNoteLines) { // Print any remaining line if not over limit
ctx.fillText(line, margin, currentY);
currentY += lineHeight;
}
currentY += 20; // Space after notes
// 6. Stamp
if (stampText && stampText.trim() !== "") {
const stampWidth = 160;
const stampHeight = 60;
let stampBaseY = Math.max(imgSectionYStart + imgMaxContainerHeight + 100, currentY - stampHeight - 10); // Place after image or notes
stampBaseY = Math.min(stampBaseY, canvasHeight - margin - stampHeight - 100); // Not too close to signature
const stampX = canvasWidth - margin - stampWidth - 20;
const stampY = stampBaseY;
ctx.save();
ctx.translate(stampX + stampWidth / 2, stampY + stampHeight / 2);
ctx.rotate(-12 * Math.PI / 180);
ctx.strokeStyle = stampColor;
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(-stampWidth / 2 + (Math.random()-0.5)*4 , -stampHeight / 2 + (Math.random()-0.5)*4);
ctx.lineTo(stampWidth / 2 + (Math.random()-0.5)*4, -stampHeight / 2 + (Math.random()-0.5)*4);
ctx.lineTo(stampWidth / 2 + (Math.random()-0.5)*4, stampHeight / 2 + (Math.random()-0.5)*4);
ctx.lineTo(-stampWidth / 2 + (Math.random()-0.5)*4, stampHeight / 2 + (Math.random()-0.5)*4);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = stampColor;
ctx.font = `bold 24px ${headingFontFamily}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(stampText.toUpperCase(), 0, 2); // Slight Y offset for visual centering
ctx.restore();
}
// 7. Signature line
const sigY = canvasHeight - margin + 5;
ctx.fillStyle = textColor;
ctx.font = `italic 15px ${bodyFontFamily}`;
ctx.textAlign = 'right';
const signatureName = "Det. S.H. (Consulting)";
ctx.fillText(signatureName, canvasWidth - margin, sigY - 5);
ctx.beginPath();
ctx.moveTo(canvasWidth - margin - 220, sigY);
ctx.lineTo(canvasWidth - margin, sigY);
ctx.strokeStyle = textColor;
ctx.lineWidth = 0.75;
ctx.stroke();
return canvas;
}
Apply Changes