You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
lastName = "PUBLIC",
firstName = "JOHN",
address1 = "123 MAIN ST",
address2 = "MINNEAPOLIS, MN 55401",
dob = "01/01/1990",
dlNumber = "A123-456-789-123",
exp = "01/01/2028",
sex = "M",
height = "5'-10\"",
eyes = "BLU",
weight = "180",
cardClass = "D"
) {
// Normalize string inputs to uppercase as typical for IDs
lastName = lastName.toUpperCase();
firstName = firstName.toUpperCase();
address1 = address1.toUpperCase();
address2 = address2.toUpperCase();
dlNumber = dlNumber.toUpperCase();
dob = dob.toUpperCase();
exp = exp.toUpperCase();
sex = sex.toUpperCase();
height = height.toUpperCase();
eyes = eyes.toUpperCase();
weight = weight.toUpperCase();
cardClass = cardClass.toUpperCase();
// Dynamically load Dancing Script font for the signature
const fontId = 'google-font-dancing-script';
if (!document.getElementById(fontId)) {
const link = document.createElement('link');
link.id = fontId;
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Dancing+Script:wght@700&display=swap';
document.head.appendChild(link);
await new Promise(resolve => {
link.onload = resolve;
setTimeout(resolve, 1500); // 1.5s fallback to ensure execution continues
});
}
// Attempt to strictly wait for font readiness
try {
await document.fonts.load('28px "Dancing Script"');
} catch (e) {
console.warn("Font loading gracefully failed.", e);
}
// Initialize Canvas (Standard ID Card Aspect Ratio, high-res)
const cw = 800;
const ch = 500;
const canvas = document.createElement("canvas");
canvas.width = cw;
canvas.height = ch;
const ctx = canvas.getContext("2d");
// Helper: Rounded Rectangle Path Builder
function buildRoundRectPath(x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.arcTo(x + w, y, x + w, y + r, r);
ctx.lineTo(x + w, y + h - r);
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
ctx.lineTo(x + r, y + h);
ctx.arcTo(x, y + h, x, y + h - r, r);
ctx.lineTo(x, y + r);
ctx.arcTo(x, y, x + r, y, r);
ctx.closePath();
}
// Step 1: Base Card Clip (Round the external borders)
buildRoundRectPath(0, 0, cw, ch, 15);
ctx.clip();
// Step 2: Base Background Gradient
let cardGrd = ctx.createLinearGradient(0, 100, 0, 500);
cardGrd.addColorStop(0, "#ffffff");
cardGrd.addColorStop(0.5, "#eef6fa"); // subtle blueish tint
cardGrd.addColorStop(1, "#f4eee0"); // subtle yellowish tint
ctx.fillStyle = cardGrd;
ctx.fillRect(0, 0, cw, ch);
// Step 3: Draw Wavy Header Motif (like MN Real ID)
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(cw, 0);
ctx.lineTo(cw, 140);
ctx.bezierCurveTo(600, 100, 300, 160, 0, 110);
ctx.closePath();
let headGrd = ctx.createLinearGradient(0, 0, cw, 0);
headGrd.addColorStop(0, "#8cd731"); // nature green
headGrd.addColorStop(0.5, "#3d97e2"); // lake blue
headGrd.addColorStop(1, "#f7df2b"); // sun yellow
ctx.fillStyle = headGrd;
ctx.fill();
// Step 4: Watermark & Guilloche Pattern (MN shape with repeated text overlaying)
ctx.save();
ctx.translate(450, 280);
const scale = 320;
const mnPoints = [
[-0.15, -0.45], [0.0, -0.45], [0.0, -0.3], [0.35, -0.15], [0.15, 0.0],
[0.1, 0.15], [0.3, 0.3], [0.3, 0.45], [-0.3, 0.45],
[-0.3, 0.05], [-0.4, -0.15], [-0.4, -0.35], [-0.15, -0.35]
];
ctx.beginPath();
ctx.moveTo(mnPoints[0][0] * scale, mnPoints[0][1] * scale);
for (let i = 1; i < mnPoints.length; i++) {
ctx.lineTo(mnPoints[i][0] * scale, mnPoints[i][1] * scale);
}
ctx.closePath();
// Fill slightly and clip for inner micro-text watermark
ctx.fillStyle = "rgba(100, 150, 200, 0.08)";
ctx.fill();
ctx.lineWidth = 3;
ctx.strokeStyle = "rgba(100, 150, 200, 0.15)";
ctx.stroke();
ctx.clip(); // Limit text to map boundaries
ctx.fillStyle = "rgba(100, 150, 200, 0.15)";
ctx.font = "bold 12px Arial";
for(let y = -250; y < 250; y += 20) {
for(let x = -250; x < 250; x += 100) {
ctx.fillText("MINNESOTA", x + ((y / 20) % 2) * 40, y);
}
}
ctx.restore();
// Step 5: Draw Top Header Text
ctx.font = "bold 56px Helvetica, Arial";
ctx.fillStyle = "#ffffff";
ctx.textAlign = "center";
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 1;
ctx.shadowOffsetY = 1;
ctx.fillText("MINNESOTA", cw / 2, 65);
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Sub-header Text
ctx.font = "bold 17px Arial";
ctx.fillStyle = "#0c2858";
ctx.fillText("DRIVER'S LICENSE", cw / 2, 95);
// USA Circular Stamp
ctx.beginPath();
ctx.arc(50, 50, 22, 0, Math.PI * 2);
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
ctx.fill();
ctx.fillStyle = "#0c2858";
ctx.font = "bold 15px Arial";
ctx.fillText("USA", 50, 55);
// Real ID Star Stamp
ctx.save();
ctx.translate(740, 60);
ctx.beginPath();
ctx.arc(0, 0, 24, 0, Math.PI * 2);
ctx.fillStyle = "#d3a625"; // metallic gold
ctx.fill();
ctx.lineWidth = 1.5;
ctx.strokeStyle = "#a37c15";
ctx.stroke();
ctx.beginPath();
let spikes = 5, outerRadius = 14, innerRadius = 5.5;
let rot = Math.PI / 2 * 3, step = Math.PI / spikes;
ctx.moveTo(0, -outerRadius);
for (let i = 0; i < spikes; i++) {
ctx.lineTo(Math.cos(rot) * outerRadius, Math.sin(rot) * outerRadius);
rot += step;
ctx.lineTo(Math.cos(rot) * innerRadius, Math.sin(rot) * innerRadius);
rot += step;
}
ctx.closePath();
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.restore();
// Step 6: Text Field Details Helpers
function drawField(labelNo, labelText, value, x, y, isRed = false, customFont = "bold 19px Arial") {
ctx.textAlign = "left";
// Red numbering color for field prefixes in MN style
ctx.font = "bold 10px Arial";
ctx.fillStyle = "#d32f2f";
ctx.fillText(labelNo, x, y - 20);
let noWidth = ctx.measureText(labelNo).width + 3;
// Blue descriptive text
ctx.fillStyle = "#0c2858";
ctx.font = "10px Arial";
ctx.fillText(labelText, x + noWidth, y - 20);
// Final Value mapping
ctx.font = customFont;
ctx.fillStyle = isRed ? "#d32f2f" : "#000000";
ctx.fillText(value, x, y);
}
// Map out the fields
drawField("4d", "DL NO.", dlNumber, 245, 155, false, "bold 26px Arial");
drawField("3", "DOB", dob, 245, 205, true);
drawField("4b", "EXP", exp, 395, 205, true);
drawField("4a", "ISS", "08/15/2023", 535, 205); // Simulated issuance date
drawField("1", "NAME", lastName, 245, 260);
drawField("", "", firstName, 245, 283);
drawField("8", "ADDRESS", address1, 245, 335);
drawField("", "", address2, 245, 358);
drawField("15", "SEX", sex, 245, 410);
drawField("16", "HGT", height, 315, 410);
drawField("17", "WGT", weight, 395, 410);
drawField("18", "EYES", eyes, 475, 410);
drawField("9", "CLASS", cardClass, 245, 465);
drawField("9a", "END", "NONE", 325, 465);
drawField("12", "REST", "NONE", 415, 465);
// Document Discriminator Vertical Text
ctx.save();
ctx.translate(225, 420);
ctx.rotate(-Math.PI / 2);
ctx.font = "10px Arial";
ctx.fillStyle = "#666666";
ctx.fillText("DD: 494532882190A", 0, 0);
ctx.restore();
// Step 7: Draw Image Profiles (Main and Ghost)
function drawImgWithinRect(img, x, y, w, h, isGhost = false) {
ctx.save();
if (isGhost) ctx.globalAlpha = 0.35;
buildRoundRectPath(x, y, w, h, 8);
ctx.clip();
const imgAspect = img.width / img.height;
const rectAspect = w / h;
let sx, sy, sw, sh;
if (imgAspect > rectAspect) {
sh = img.height;
sw = img.height * rectAspect;
sx = (img.width - sw) / 2;
sy = 0;
} else {
sw = img.width;
sh = img.width / rectAspect;
sx = 0;
sy = (img.height - sh) / 2;
}
ctx.drawImage(img, sx, sy, sw, sh, x, y, w, h);
ctx.restore();
// Draw thin boundary line for the portrait box
ctx.lineWidth = 1;
ctx.strokeStyle = isGhost ? "rgba(100,100,100,0.2)" : "#aaaaaa";
buildRoundRectPath(x, y, w, h, 8);
ctx.stroke();
}
if (originalImg && originalImg.width && originalImg.height) {
// Main Photo
drawImgWithinRect(originalImg, 35, 130, 180, 240, false);
// Ghost Photo
drawImgWithinRect(originalImg, 630, 290, 135, 180, true);
}
// Step 8: Client Signature
ctx.textAlign = "center";
ctx.font = '28px "Dancing Script", cursive';
ctx.fillStyle = "#000000";
ctx.fillText(`${firstName} ${lastName}`, 125, 420);
// Thin line under signature
ctx.beginPath();
ctx.moveTo(40, 425);
ctx.lineTo(210, 425);
ctx.lineWidth = 1;
ctx.strokeStyle = "#999999";
ctx.stroke();
// Tiny signature label text
ctx.font = "8px Arial";
ctx.fillStyle = "#0c2858";
ctx.fillText("SIGNATURE", 125, 435);
return canvas;
}
Apply Changes