You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
citizenName = "SUBJECT UNKNOWN",
citizenId = "", // Will be auto-generated if empty string (which is the default)
status = "COMPLIANT",
issuingAuthority = "MINISTRY OF SOVEREIGNTY",
accentColor = "#c42121", // Red for alerts/negative status
backgroundColor = "#383838", // Dark gray card background
borderColor = "#1c1c1c", // Very dark card border
textColor = "#a0a0a0", // Muted gray text
photoBorderColor = "#222222", // Dark border for photo
sectionSeparatorColor = "#4a4a4a" // Color for separator lines
) {
// Ensure string types for parameters that might be passed as numbers by user
citizenName = String(citizenName);
// status, issuingAuthority, accentColor, etc., are defaulted as strings.
// If user passes number, String() will convert.
status = String(status);
issuingAuthority = String(issuingAuthority);
accentColor = String(accentColor);
backgroundColor = String(backgroundColor);
borderColor = String(borderColor);
textColor = String(textColor);
photoBorderColor = String(photoBorderColor);
sectionSeparatorColor = String(sectionSeparatorColor);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const cardWidth = 500;
const cardHeight = 315; // Standard ID card ratio approx 1.587
canvas.width = cardWidth;
canvas.height = cardHeight;
const padding = 15;
const mainFont = '12px "Courier New", Courier, monospace';
const labelFont = 'bold 10px "Courier New", Courier, monospace';
const titleFont = 'bold 16px "Courier New", Courier, monospace';
const headerFont = '10px "Courier New", Courier, monospace';
const idValueFont = 'bold 13px "Courier New", Courier, monospace';
const statusValueFont = 'bold 14px "Courier New", Courier, monospace';
// --- Utility Functions ---
function generateRandomId(length = 12) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
let actualCitizenId = String(citizenId).trim();
if (actualCitizenId === "") {
actualCitizenId = generateRandomId();
}
function getFormattedDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
const issueDate = new Date();
const expiryDate = new Date(issueDate.getFullYear() + 5, issueDate.getMonth(), issueDate.getDate());
const formattedIssueDate = getFormattedDate(issueDate);
const formattedExpiryDate = getFormattedDate(expiryDate);
// --- Draw Background and Border ---
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, cardWidth, cardHeight);
ctx.strokeStyle = borderColor;
ctx.lineWidth = 2;
ctx.strokeRect(1, 1, cardWidth - 2, cardHeight - 2);
// --- Header Section ---
const headerHeight = 45;
ctx.fillStyle = textColor;
ctx.font = headerFont;
ctx.textAlign = 'left';
ctx.fillText(issuingAuthority.toUpperCase(), padding, padding + 10);
function drawDystopianLogo(logoCtx, x, y, size, color, bgColor) {
logoCtx.save();
logoCtx.strokeStyle = color;
logoCtx.fillStyle = color;
logoCtx.lineWidth = Math.max(1, size / 12);
logoCtx.beginPath();
logoCtx.ellipse(x, y, size / 2, size / 3.5, 0, 0, 2 * Math.PI);
logoCtx.stroke();
logoCtx.beginPath();
logoCtx.arc(x, y, size / 4.5, 0, 2 * Math.PI);
logoCtx.fill();
logoCtx.beginPath();
logoCtx.arc(x, y, size / 10, 0, 2 * Math.PI);
logoCtx.fillStyle = bgColor; // Use card background for pupil cutout
logoCtx.fill();
logoCtx.restore();
}
const logoSize = 28;
drawDystopianLogo(ctx, cardWidth - padding - logoSize / 2, padding + logoSize / 2 - 2, logoSize, textColor, backgroundColor);
ctx.font = titleFont;
ctx.textAlign = 'center';
ctx.fillStyle = textColor;
ctx.fillText("CITIZEN AUTHENTICATION", cardWidth / 2, padding + 30);
ctx.strokeStyle = sectionSeparatorColor;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, headerHeight);
ctx.lineTo(cardWidth - padding, headerHeight);
ctx.stroke();
// --- Photo Area ---
const photoAreaY = headerHeight + padding / 2;
const photoWidth = 120;
const photoHeight = 150;
const photoX = padding;
const photoY = photoAreaY;
if (!originalImg || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
ctx.fillStyle = '#111';
ctx.fillRect(photoX, photoY, photoWidth, photoHeight);
ctx.fillStyle = textColor;
ctx.font = '10px "Courier New", Courier, monospace';
ctx.textAlign = 'center';
ctx.fillText("NO IMAGE", photoX + photoWidth / 2, photoY + photoHeight / 2 - 5);
ctx.fillText("SIGNAL LOST", photoX + photoWidth / 2, photoY + photoHeight / 2 + 10);
} else {
const tempPhotoCanvas = document.createElement('canvas');
tempPhotoCanvas.width = photoWidth;
tempPhotoCanvas.height = photoHeight;
const tempPhotoCtx = tempPhotoCanvas.getContext('2d');
const imgAspectRatio = originalImg.naturalWidth / originalImg.naturalHeight;
const photoAspectRatio = photoWidth / photoHeight;
let renderableWidth, renderableHeight, xStart, yStart;
if (imgAspectRatio > photoAspectRatio) {
renderableHeight = photoHeight;
renderableWidth = originalImg.naturalWidth * (photoHeight / originalImg.naturalHeight);
xStart = (photoWidth - renderableWidth) / 2;
yStart = 0;
} else {
renderableWidth = photoWidth;
renderableHeight = originalImg.naturalHeight * (photoWidth / originalImg.naturalWidth);
yStart = (photoHeight - renderableHeight) / 2;
xStart = 0;
}
tempPhotoCtx.drawImage(originalImg, xStart, yStart, renderableWidth, renderableHeight);
const imageData = tempPhotoCtx.getImageData(0, 0, photoWidth, photoHeight);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
let gray = (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114);
gray = (gray - 128) * 1.3 + 128; // Basic contrast
gray = Math.max(0, Math.min(255, gray));
data[i] = gray; data[i + 1] = gray; data[i + 2] = gray;
}
tempPhotoCtx.putImageData(imageData, 0, 0);
tempPhotoCtx.fillStyle = 'rgba(0, 0, 0, 0.15)'; // Scanlines
for (let yScan = 0; yScan < photoHeight; yScan += 3) {
tempPhotoCtx.fillRect(0, yScan, photoWidth, 1);
}
ctx.drawImage(tempPhotoCanvas, photoX, photoY);
}
ctx.strokeStyle = photoBorderColor;
ctx.lineWidth = 2;
ctx.strokeRect(photoX, photoY, photoWidth, photoHeight);
// --- Text Details Area ---
const textX = photoX + photoWidth + padding;
let currentTextY = photoY + 10; // Initial Y for the text block (aligned with top of photo + small offset)
const fieldSpacing = 20;
const labelWidth = 70; // Width allocated for labels like "ID TAG:"
ctx.textAlign = 'left';
function drawStyledField(label, value, yPos, labelFontApplied, valueFontApplied, valueColorApplied = textColor) {
ctx.font = labelFontApplied;
ctx.fillStyle = textColor; // Labels are always standard text color
ctx.fillText(label.toUpperCase(), textX, yPos);
ctx.font = valueFontApplied;
ctx.fillStyle = valueColorApplied;
ctx.fillText(String(value).toUpperCase(), textX + labelWidth, yPos);
}
drawStyledField("ID TAG:", actualCitizenId, currentTextY, labelFont, idValueFont, textColor);
currentTextY += fieldSpacing + 4; // More space after ID
drawStyledField("NAME:", citizenName, currentTextY, labelFont, mainFont, textColor);
currentTextY += fieldSpacing;
// Status (special handling for color and font weight)
ctx.font = labelFont;
ctx.fillStyle = textColor;
ctx.fillText("STATUS:", textX, currentTextY);
let statusColor = textColor;
const upperStatus = status.toUpperCase();
const negativeStatuses = ["NON-COMPLIANT", "DEFECTIVE", "TERMINATE", "ALERT", "UNSTABLE", "EXPIRED", "REVOKED", "WANTED"];
if (negativeStatuses.some(negStatus => upperStatus.includes(negStatus))) {
statusColor = accentColor;
}
ctx.font = statusValueFont;
ctx.fillStyle = statusColor;
ctx.fillText(upperStatus, textX + labelWidth, currentTextY);
currentTextY += fieldSpacing + 6; // More space after important Status field
drawStyledField("ISSUED:", formattedIssueDate, currentTextY, labelFont, mainFont, textColor);
currentTextY += fieldSpacing - 2; // Dates can be a bit closer
drawStyledField("EXPIRES:", formattedExpiryDate, currentTextY, labelFont, mainFont, textColor);
// --- Barcode-like element ---
const barcodeHeight = 30;
const barcodeY = cardHeight - padding - barcodeHeight - 10; // Leave space for number below
const barcodeX = photoX + photoWidth + padding;
const barcodeWidth = cardWidth - barcodeX - padding;
ctx.fillStyle = '#000000'; // Barcode stripes are black
for (let i = 0; i < barcodeWidth; i += (Math.random() * 3 + 1.5)) { // Varying stripe widths and gaps
if (Math.random() > 0.2) { // Create gaps
const stripeWidth = Math.random() * 1.5 + 0.5; // Thin stripes
ctx.fillRect(barcodeX + i, barcodeY, stripeWidth, barcodeHeight);
}
}
ctx.font = '8px "Courier New", Courier, monospace';
ctx.fillStyle = textColor;
ctx.textAlign = 'left';
let barcodeNumber = "";
for(let i=0; i<20; i++) barcodeNumber += Math.floor(Math.random()*1_0); // Corrected random number generation
ctx.fillText(barcodeNumber.split('').join(' '), barcodeX, barcodeY + barcodeHeight + 8); // Spaced numbers
// --- Signature Area / Biometric Placeholder ---
const sigAreaX = padding;
const sigAreaY = photoY + photoHeight + padding / 2;
const sigAreaWidth = photoWidth;
const sigAreaHeight = cardHeight - sigAreaY - padding;
if (sigAreaHeight > 25) { // Only draw if there's enough space
ctx.strokeStyle = sectionSeparatorColor;
ctx.lineWidth = 0.5;
ctx.strokeRect(sigAreaX + 0.5, sigAreaY + 0.5, sigAreaWidth -1, sigAreaHeight - 10.5); // -10 to not touch bottom, pixel align
ctx.font = 'italic 8px "Times New Roman", Times, serif';
ctx.fillStyle = textColor;
ctx.textAlign = 'center';
ctx.fillText("BIOMETRIC SIGNATURE VERIFIED", sigAreaX + sigAreaWidth / 2, sigAreaY + sigAreaHeight / 2 - 2); // Adjusted text position
}
return canvas;
}
Apply Changes