You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
fullName = "John Doe",
idNumber = "ID-12345",
title = "Employee",
department = "General Department",
issueDate = "01/01/2024",
expiryDate = "01/01/2028",
companyName = "Secure Identity Corp.",
signatureText = "", // Default to empty, user can pass fullName or custom.
cardWidth = 400,
cardHeight = 240,
backgroundColor = "#FFFFFF",
textColor = "#2C3E50",
accentColor = "#3498DB",
headerTextColor = "#FFFFFF",
fontFamily = "Helvetica, Arial, sans-serif",
signatureFontFamily = "Brush Script MT, cursive, sans-serif",
cardBorderRadius = 10,
profilePicSize = 80,
profilePicStyle = "circle", // "square" or "circle"
headerFontSize = 20,
nameFontSize = 18,
detailFontSize = 15, // For title
labelFontSize = 12, // For ID, Dept, Dates and their labels
signatureTextSize = 15
) {
// Helper function to draw rounded rectangles
function drawRoundedRect(ctx, x, y, width, height, radiusInput) {
let radius = {tl: 0, tr: 0, br: 0, bl: 0};
if (typeof radiusInput === 'number') {
radius = {tl: radiusInput, tr: radiusInput, br: radiusInput, bl: radiusInput};
} else if (typeof radiusInput === 'object') {
for (let side in radius) {
radius[side] = radiusInput[side] || 0;
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
}
const canvas = document.createElement('canvas');
canvas.width = cardWidth;
canvas.height = cardHeight;
const ctx = canvas.getContext('2d');
// Margins and layout constants
const padding = Math.min(cardWidth, cardHeight) * 0.045; // General padding
const headerHeight = headerFontSize + padding * 1.8; // Adjusted for better font fit
// 1. Card Background
ctx.fillStyle = backgroundColor;
if (cardBorderRadius > 0) {
drawRoundedRect(ctx, 0, 0, cardWidth, cardHeight, cardBorderRadius);
ctx.fill();
} else {
ctx.fillRect(0, 0, cardWidth, cardHeight);
}
// Clip subsequent drawings to the card's rounded shape
if (cardBorderRadius > 0) {
ctx.save(); // Save context state before applying clip
drawRoundedRect(ctx, 0, 0, cardWidth, cardHeight, cardBorderRadius);
ctx.clip();
}
// 2. Header
ctx.fillStyle = accentColor;
ctx.fillRect(0, 0, cardWidth, headerHeight);
ctx.fillStyle = headerTextColor;
ctx.font = `bold ${headerFontSize}px ${fontFamily}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Basic text fitting for company name in header
let currentHeaderFontSize = headerFontSize;
const maxHeaderTextWidth = cardWidth - padding * 4; // More padding for header text
while(ctx.measureText(companyName).width > maxHeaderTextWidth && currentHeaderFontSize > 8) {
currentHeaderFontSize--;
ctx.font = `bold ${currentHeaderFontSize}px ${fontFamily}`;
}
ctx.fillText(companyName, cardWidth / 2, headerHeight / 2);
// 3. Profile Picture
const profilePicActualSize = profilePicSize;
const profilePicX = padding * 2;
const profilePicY = headerHeight + padding;
// Calculate cropping for the profile picture
let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;
const imgAspectRatio = originalImg.width / originalImg.height;
const targetAspectRatio = 1; // For square/circle
if (imgAspectRatio > targetAspectRatio) {
sWidth = originalImg.height * targetAspectRatio;
sx = (originalImg.width - sWidth) / 2;
} else if (imgAspectRatio < targetAspectRatio) {
sHeight = originalImg.width / targetAspectRatio;
sy = (originalImg.height - sHeight) / 2;
}
ctx.save(); // Save context for potential profile picture clipping
if (profilePicStyle === 'circle') {
ctx.beginPath();
ctx.arc(profilePicX + profilePicActualSize / 2, profilePicY + profilePicActualSize / 2, profilePicActualSize / 2, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
}
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, profilePicX, profilePicY, profilePicActualSize, profilePicActualSize);
ctx.restore(); // Restore context (removes clipping for picture)
// Border around profile picture
ctx.strokeStyle = accentColor;
ctx.lineWidth = 2; // Border thickness
if (profilePicStyle === 'circle') {
ctx.beginPath();
ctx.arc(profilePicX + profilePicActualSize / 2, profilePicY + profilePicActualSize / 2, profilePicActualSize / 2, 0, Math.PI * 2, true);
ctx.stroke();
} else {
ctx.strokeRect(profilePicX, profilePicY, profilePicActualSize, profilePicActualSize);
}
// 4. Text Details
const textStartX = profilePicX + profilePicActualSize + padding * 1.5;
let currentY = headerHeight + padding;
const lineSpacingMultiplier = 1.45; // Increased for better readability
const textAvailableWidth = cardWidth - textStartX - padding * 2; // Ensure padding on right side
ctx.fillStyle = textColor;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// Full Name
ctx.font = `bold ${nameFontSize}px ${fontFamily}`;
ctx.fillText(fullName, textStartX, currentY, textAvailableWidth);
currentY += nameFontSize * lineSpacingMultiplier;
// Title
ctx.font = `${detailFontSize}px ${fontFamily}`;
ctx.fillText(title, textStartX, currentY, textAvailableWidth);
currentY += detailFontSize * lineSpacingMultiplier;
currentY += padding * 0.3; // Smaller space before details block
// Helper to draw label and value pairs
function drawLabelValue(label, value, yPos) {
const labelText = label + " "; // Add space after label
ctx.font = `bold ${labelFontSize}px ${fontFamily}`;
ctx.fillText(labelText, textStartX, yPos, textAvailableWidth);
const labelWidth = ctx.measureText(labelText).width;
ctx.font = `${labelFontSize}px ${fontFamily}`;
// Check if value text fits, otherwise it will be drawn outside or compressed by fillText's maxWidth
const valueMaxWidth = textAvailableWidth - labelWidth - (padding * 0.2);
ctx.fillText(value, textStartX + labelWidth, yPos, valueMaxWidth > 0 ? valueMaxWidth : 0);
return yPos + labelFontSize * lineSpacingMultiplier;
}
currentY = drawLabelValue("ID:", idNumber, currentY);
currentY = drawLabelValue("Dept:", department, currentY);
currentY = drawLabelValue("Issued:", issueDate, currentY);
currentY = drawLabelValue("Expires:", expiryDate, currentY);
// 5. Signature (if signatureText is provided)
if (signatureText && signatureText.trim() !== "") {
const signatureLineYOffset = signatureTextSize * 0.8;
const signatureAreaMinY = profilePicY + profilePicActualSize + padding; // Below profile picture
const signatureAreaMaxY = cardHeight - padding - signatureTextSize - signatureLineYOffset; // Above card bottom
// Position signature: attempt after text details, but ensure it's within bounds
let signatureTextBaselineY = Math.max(currentY + padding, signatureAreaMinY);
signatureTextBaselineY = Math.min(signatureTextBaselineY, signatureAreaMaxY);
ctx.font = `italic ${signatureTextSize}px ${signatureFontFamily}`;
ctx.fillStyle = textColor;
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic'; // Better for precise line placement under text
ctx.fillText(signatureText, textStartX, signatureTextBaselineY + signatureTextSize * 0.8, textAvailableWidth);
ctx.strokeStyle = textColor;
ctx.lineWidth = 1;
ctx.beginPath();
const signatureDrawLineY = signatureTextBaselineY + signatureTextSize * 0.8 + ctx.lineWidth + 1;
ctx.moveTo(textStartX, signatureDrawLineY);
const signatureLineWidth = Math.min(ctx.measureText(signatureText).width * 1.1, textAvailableWidth);
ctx.lineTo(textStartX + signatureLineWidth, signatureDrawLineY);
ctx.stroke();
}
// Restore context if it was saved for card border clipping
if (cardBorderRadius > 0) {
ctx.restore();
}
return canvas;
}
Apply Changes