You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
name = "DOE, JANE",
idNumber = "123 456 789",
dob = "01/01/1990",
address1 = "123 EMPIRE BLVD",
address2 = "NEW YORK, NY 10001",
sex = "F",
heightVal = "5'-06\"",
eyes = "BRN",
issueDate = "08/15/2022",
expDate = "01/01/2030",
documentClass = "D"
) {
// Inject signature font
if (!document.getElementById("caveat-font-id-gen")) {
const style = document.createElement("style");
style.id = "caveat-font-id-gen";
style.innerHTML = `@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@700&display=swap');`;
document.head.appendChild(style);
}
// Wait for the font to load
await document.fonts.ready;
try {
await document.fonts.load('40px "Caveat"');
} catch (e) {
console.warn("Font loading issue, falling back to cursive.");
}
// Set up canvas
const canvas = document.createElement("canvas");
const canvasWidth = 860;
const canvasHeight = 540;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext("2d");
// Enable smooth rendering
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
// 1. Draw Background
// Base card color and border radius clipping
ctx.beginPath();
ctx.moveTo(15, 0);
ctx.lineTo(canvasWidth - 15, 0);
ctx.quadraticCurveTo(canvasWidth, 0, canvasWidth, 15);
ctx.lineTo(canvasWidth, canvasHeight - 15);
ctx.quadraticCurveTo(canvasWidth, canvasHeight, canvasWidth - 15, canvasHeight);
ctx.lineTo(15, canvasHeight);
ctx.quadraticCurveTo(0, canvasHeight, 0, canvasHeight - 15);
ctx.lineTo(0, 15);
ctx.quadraticCurveTo(0, 0, 15, 0);
ctx.clip();
// Gradient background to emulate the complex NY backdrop
const bgGradient = ctx.createLinearGradient(0, 0, canvasWidth, canvasHeight);
bgGradient.addColorStop(0, "#cbe3f5"); // Light blue
bgGradient.addColorStop(0.5, "#ffffff"); // White
bgGradient.addColorStop(1, "#f2eed3"); // Light yellow
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Guilloche / Wave Patterns
ctx.save();
ctx.strokeStyle = "rgba(0, 70, 150, 0.15)";
ctx.lineWidth = 1;
for (let y = 100; y < canvasHeight; y += 15) {
ctx.beginPath();
for (let x = 0; x <= canvasWidth; x += 30) {
ctx.quadraticCurveTo(x + 15, y + 10, x + 30, y);
}
ctx.stroke();
}
// Vertical subtle lines
ctx.strokeStyle = "rgba(0, 70, 150, 0.05)";
for (let x = 0; x < canvasWidth; x += 8) {
ctx.beginPath();
ctx.moveTo(x, 90);
ctx.lineTo(x + 20, canvasHeight);
ctx.stroke();
}
ctx.restore();
// Fake State Seal / Watermark Center Graphic
ctx.save();
ctx.globalAlpha = 0.2;
ctx.beginPath();
ctx.arc(460, 310, 140, 0, Math.PI * 2);
ctx.lineWidth = 4;
ctx.strokeStyle = "#e8b010";
ctx.stroke();
ctx.fillStyle = "#e8b010";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "bold 32px Arial";
ctx.fillText("EXCELSIOR", 460, 310);
ctx.beginPath();
ctx.arc(460, 310, 130, 0, Math.PI * 2);
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
// 2. Draw Top Headers
ctx.fillStyle = "#0c2340"; // Dark navy blue
ctx.fillRect(0, 0, canvasWidth, 80);
// Yellow stripe underneath header
ctx.fillStyle = "#fdb813";
ctx.fillRect(0, 80, canvasWidth, 8);
// Header Text
ctx.fillStyle = "#ffffff";
ctx.font = "bold 44px Arial, sans-serif";
ctx.textAlign = "left";
ctx.textBaseline = "alphabetic";
ctx.fillText("NEW YORK STATE", 30, 56);
ctx.font = "bold 16px Arial";
ctx.fillText("DRIVER LICENSE", 430, 52);
// 3. Draw Photos
const drawCroppedPhoto = (img, dx, dy, dw, dh, alpha = 1.0) => {
const scale = Math.max(dw / img.width, dh / img.height);
const cw = img.width * scale;
const ch = img.height * scale;
const cx = (dw - cw) / 2;
const cy = (dh - ch) / 2;
ctx.save();
ctx.globalAlpha = alpha;
ctx.beginPath();
ctx.rect(dx, dy, dw, dh);
ctx.clip();
ctx.drawImage(img, dx + cx, dy + cy, cw, ch);
ctx.restore();
};
// Main Photo (Left)
const photoWidth = 210;
const photoHeight = 280;
const photoX = 30;
const photoY = 110;
// Slight shadow and border for main photo
ctx.shadowColor = "rgba(0,0,0,0.3)";
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillStyle = "#ffffff";
ctx.fillRect(photoX - 2, photoY - 2, photoWidth + 4, photoHeight + 4);
ctx.shadowColor = "transparent"; // Reset shadow
drawCroppedPhoto(originalImg, photoX, photoY, photoWidth, photoHeight, 1.0);
// Ghost Photo (Right)
drawCroppedPhoto(originalImg, 680, 290, 130, 173, 0.35);
// 4. Draw Data Fields
// Helper function mapped to realistic NY layout
const drawField = (num, label, val, x, y, valYOffset = 18, highlightVal = false) => {
ctx.textAlign = "left";
// Number indicator
ctx.fillStyle = "#a81818"; // Cardinal red
ctx.font = "bold 11px Arial";
ctx.fillText(num, x, y);
// Label
ctx.fillStyle = "#0c2340"; // Navy blue
ctx.font = "bold 10px Arial";
ctx.fillText(label, x + 16, y);
// Value
ctx.fillStyle = highlightVal ? "#a81818" : "#000000";
if (num === "1" || num === "2") {
ctx.font = "bold 22px Arial"; // Name is larger
} else if (num === "9") {
ctx.font = "bold 20px Arial"; // ID is larger
} else {
ctx.font = "bold 16px Arial";
}
ctx.fillText(val, x + 16, y + valYOffset);
};
// Client ID & DOB row
drawField("9", "ID", idNumber, 270, 130);
drawField("3", "DOB", dob, 520, 130, 18, true); // DOB is often highlighted red
// Document Class
drawField("9a", "Class", documentClass, 720, 130);
// Name
drawField("1,2", "Name", name, 270, 190, 22);
// Address
// Custom multiline draw for address
ctx.fillStyle = "#a81818";
ctx.font = "bold 11px Arial";
ctx.fillText("8", 270, 255);
ctx.fillStyle = "#0c2340";
ctx.font = "bold 10px Arial";
ctx.fillText("Address", 286, 255);
ctx.fillStyle = "#000000";
ctx.font = "bold 16px Arial";
ctx.fillText(address1, 286, 273);
ctx.fillText(address2, 286, 292);
// Physical Traits Row
drawField("15", "Sex", sex, 270, 340);
drawField("16", "Ht", heightVal, 340, 340); // Updated to use renamed parameter to resolve global namespace conflict
drawField("17", "Eye", eyes, 420, 340);
// Dates Row
drawField("4a", "ISS", issueDate, 270, 395);
drawField("4b", "EXP", expDate, 420, 395, 18, true);
// Restrictions / Endorsements
drawField("12", "REST", "NONE", 270, 450);
drawField("9b", "ENDORSE", "NONE", 420, 450);
// 5. Signature
ctx.fillStyle = "#111111";
ctx.font = '42px "Caveat", cursive';
ctx.textAlign = "center";
// Randomize signature angle slightly for realism
ctx.save();
ctx.translate(460, 490);
ctx.rotate( -0.03 ); // Slight upward tilt
ctx.fillText(name, 0, 0);
ctx.restore();
// 6. Barcode Substitute (Front 2D mark often present in some IDs as a minor element)
// We'll draw a simulated small 2D square matrix
ctx.fillStyle = "#000000";
const qrX = 30;
const qrY = 410;
const qrSize = 60;
// Draw pseudo-random tiny blocks
for(let r=0; r<qrSize; r+=4) {
for(let c=0; c<qrSize; c+=4) {
// Predictable pseudo-random to keep output consistent
if ((r * 11 + c * 7) % 3 === 0 || (r<12 && c<12) || (r>qrSize-12 && c<12) || (r<12 && c>qrSize-12)) {
ctx.fillRect(qrX + c, qrY + r, 4, 4);
}
}
}
return canvas;
}
Apply Changes