You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
idNum = "D7658681",
expDate = "03/30/2029",
dobDate = "03/30/1969",
lastName = "STEWART",
firstName = "RONALD",
addressLine1 = "10 PELICAN CT",
addressLine2 = "SACRAMENTO, CA 95833",
sexStat = "M",
hairStat = "BRN",
eyeStat = "BRO",
hgtStat = "5-06",
wgtStat = "142",
classStat = "C"
) {
// 1. Load Custom Fonts Dynamically
await new Promise(resolve => {
const link = document.createElement('link');
link.href = 'https://fonts.googleapis.com/css2?family=Caveat:wght@700&family=Pacifico&display=swap';
link.rel = 'stylesheet';
document.head.appendChild(link);
// Wait till fonts are parsed and ready
if (document.fonts) {
document.fonts.ready.then(() => setTimeout(resolve, 300));
} else {
setTimeout(resolve, 1000); // Fallback timeout for older browsers
}
});
// 2. Setup Canvas and Hard Edge Crop (Rounded Card Effect)
const canvas = document.createElement("canvas");
canvas.width = 800;
canvas.height = 500;
const ctx = canvas.getContext("2d");
function drawRoundRectCenter(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
}
// Clip the entirety of the canvas to a rounded card layout
drawRoundRectCenter(ctx, 0, 0, 800, 500, 20);
ctx.clip();
// 3. Guilloche Background Pattern Generation
const bgGrad = ctx.createLinearGradient(0, 0, 800, 500);
bgGrad.addColorStop(0, "#d5e8f7"); // light blue
bgGrad.addColorStop(0.5, "#fcf2d9"); // light yellow
bgGrad.addColorStop(1, "#d5e8f7"); // light blue
ctx.fillStyle = bgGrad;
ctx.fillRect(0, 0, 800, 500);
function drawGuilloche(ctx, yOffset, amplitude, freq, color, phases) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 1;
for (let x = 0; x <= 800; x += 2) {
let y = yOffset;
phases.forEach(p => {
y += Math.sin(x * p.f + p.p) * p.a;
});
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
// Overlapping wavy guilloche security lines
for (let i = 0; i < 80; i++) {
const color = i % 3 === 0 ? "rgba(255,255,255,0.7)" : i % 3 === 1 ? "rgba(180,210,240,0.5)" : "rgba(250,240,180,0.5)";
const phases = [
{ f: 0.01, a: 30, p: i * 0.1 },
{ f: 0.03, a: 15, p: i * 0.2 },
{ f: 0.005, a: 60, p: i * 0.05 }
];
drawGuilloche(ctx, (i / 80) * 500, 0, 0, color, phases);
}
for (let i = 0; i < 20; i++) {
drawGuilloche(ctx, 250, 0, 0, "rgba(255, 255, 255, 0.4)", [
{ f: 0.015, a: 150, p: i * 0.3 },
{ f: 0.02, a: -40, p: i * 0.1 }
]);
}
// Utility handler mimicking `object-fit: cover`
function drawCover(ctx, img, x, y, w, h) {
const imgRatio = img.width / img.height;
const boxRatio = w / h;
let sx = 0, sy = 0, sWidth = img.width, sHeight = img.height;
if (imgRatio > boxRatio) {
sWidth = img.height * boxRatio;
sx = (img.width - sWidth) / 2;
} else {
sHeight = img.width / boxRatio;
sy = (img.height - sHeight) / 2;
}
ctx.drawImage(img, sx, sy, sWidth, sHeight, x, y, w, h);
}
// 4. Ghost Photo Bottom Right (softly blended, faded, zero harsh borders via radial gradient mask)
const tmpCanvas = document.createElement("canvas");
tmpCanvas.width = 800;
tmpCanvas.height = 500;
const tmpCtx = tmpCanvas.getContext("2d");
tmpCtx.filter = "grayscale(50%) opacity(60%) contrast(150%)"; // Standard faded ID styling
drawCover(tmpCtx, originalImg, 590, 240, 160, 215);
tmpCtx.filter = "none";
tmpCtx.globalCompositeOperation = "destination-in";
const gx = 590 + 80;
const gy = 240 + 107;
const gGrad = tmpCtx.createRadialGradient(gx, gy, 10, gx, gy, 100);
gGrad.addColorStop(0, "rgba(0,0,0,1)");
gGrad.addColorStop(1, "rgba(0,0,0,0)");
tmpCtx.fillStyle = gGrad;
tmpCtx.fillRect(480, 130, 350, 450); // Large bounding box to not limit radial gradient falloff
ctx.drawImage(tmpCanvas, 0, 0);
// 5. Main Photo Left Bound
ctx.save();
drawRoundRectCenter(ctx, 35, 125, 240, 310, 5);
ctx.clip();
drawCover(ctx, originalImg, 35, 125, 240, 310);
ctx.restore();
// 6. Bold Headers ("California", "USA", "DRIVER LICENSE")
ctx.fillStyle = "#2a59a6";
ctx.font = "bold 20px Arial, sans-serif";
ctx.fillText("USA", 45, 40);
ctx.font = "normal 55px 'Pacifico', cursive, sans-serif";
ctx.fillText("California", 95, 75);
ctx.font = "bold 16px Arial, sans-serif";
ctx.fillText("DRIVER LICENSE", 115, 105);
// 7. Golden California Grizzly Bear Emblem (Top Right)
ctx.save();
ctx.translate(650, 20);
ctx.scale(2.5, 2.5);
const bearSvg = "M49.6,27.1c-0.2-1.9-1.9-2.9-3.4-3.1 c-10-1.8-21.7,0.3-25.5,1.1C18,25.6,15,28.6,13.7,29.8c-2,1.8-3.7,1.8-4.9,1.5c-1.3-0.3-2.6-1.5-3.1-2.9C5,25.6,3.6,26,1.4,27.3 c-2.2,1.3-2.8,2.7-2.3,4.4C0.2,35.9,2.4,41.4,5,44.2c2.6,2.8,7,3,9.4,1.4c2.4-1.5,2.4-3.5,2.2-4.1c-0.3-0.8-1.5-1.7-2-1.8 c-0.6-0.1-2.8,0.2-4-0.9c-1.1-1.1-1.1-3,0.3-4.1c0.7-0.5,1.7-0.5,2.7-0.4c1,0.1,2.8,1.4,5,1c5.2-1,8.3-4.5,10-6 c1.7-1.5,4-3.5,6-3.8c2-0.3,5,0.7,6,1c1,0.3,1.3,0.9,1.5,1.4c0.1,0.5,0.4,1.3,0.1,2c-0.3,0.7-1,1.9-2.2,2.7c-1.2,0.8-2,2.3-2.2,3.1 c-0.2,0.9,0.3,2.4,1.4,3.2c1.1,0.8,3,0.6,4.1,0.3c1.5-0.5,2.8-1.7,3-3c0.1-1-0.2-2.3-0.9-3.2C44,31.7,43.2,30.5,44,29 c0.6-1.1,2.6-1.3,3.8-1.3C49,27.7,49.8,28.5,49.6,27.1z";
ctx.fillStyle = "#cca833";
ctx.fill(new Path2D(bearSvg));
// White Star Outset
ctx.fillStyle = "#ffffff";
ctx.beginPath();
const sx = 25; const sy = 33; const r = 3.5;
for(let i = 0; i < 5; i++) {
let ang1 = (Math.PI / 180) * (270 + i * 72);
let ang2 = (Math.PI / 180) * (270 + i * 72 + 36);
let px1 = sx + r * Math.cos(ang1);
let py1 = sy + r * Math.sin(ang1);
let px2 = sx + (r / 2.5) * Math.cos(ang2);
let py2 = sy + (r / 2.5) * Math.sin(ang2);
if (i === 0) ctx.moveTo(px1, py1);
else ctx.lineTo(px1, py1);
ctx.lineTo(px2, py2);
}
ctx.closePath();
ctx.fill();
ctx.restore();
// 8. Printed Identifying Card Stats
const tLeft = 300;
let yT = 150;
function drawField(label, value, x, y, valColor = "#000") {
ctx.font = "12px Arial, sans-serif";
ctx.fillStyle = "#555";
ctx.fillText(label, x, y - 16);
ctx.font = "bold 20px Arial, sans-serif";
ctx.fillStyle = valColor;
ctx.fillText(value, x, y);
}
ctx.font = "bold 22px Arial, sans-serif";
ctx.fillStyle = "#d32228"; // Red requirement
ctx.fillText("ID " + idNum, tLeft, yT);
yT += 45;
ctx.fillText("DOB " + dobDate, tLeft, yT);
ctx.fillText("EXP " + expDate, tLeft + 180, yT);
yT += 50;
drawField("LN", lastName, tLeft, yT);
yT += 45;
drawField("FN", firstName, tLeft, yT);
yT += 45;
ctx.font = "12px Arial, sans-serif";
ctx.fillStyle = "#555";
ctx.fillText("ADDRESS", tLeft, yT - 16);
ctx.font = "bold 18px Arial, sans-serif";
ctx.fillStyle = "#000";
ctx.fillText(addressLine1, tLeft, yT);
ctx.fillText(addressLine2, tLeft, yT + 22);
yT += 55;
function drawStat(label, val, offsetX) {
ctx.font = "10px Arial, sans-serif";
ctx.fillStyle = "#555";
ctx.fillText(label, tLeft + offsetX, yT - 15);
ctx.font = "bold 16px Arial, sans-serif";
ctx.fillStyle = "#000";
ctx.fillText(val, tLeft + offsetX, yT);
}
drawStat("SEX", sexStat, 0);
drawStat("HAIR", hairStat, 45);
drawStat("EYES", eyeStat, 105);
drawStat("HGT", hgtStat, 165);
drawStat("WGT", wgtStat, 225);
drawStat("CLASS", classStat, 285);
// 9. Cursive Signature Overlap (Bottom Center)
ctx.textAlign = "center";
ctx.font = "bold 35px 'Caveat', cursive, sans-serif";
ctx.fillStyle = "#111111"; // Realistically dark black ink
ctx.fillText(firstName + " " + lastName, 530, 440);
ctx.textAlign = "left";
// 10. Accurate Linear Scannable Barcode (Bottom Edge)
ctx.fillStyle = "#000000";
let bX = 290;
while (bX <= 750) {
let w = Math.random() * 3 + 1; // Variation ranging 1 to 4px widths
if (bX + w > 750) w = 750 - bX;
ctx.fillRect(bX, 470, w, 20); // Pinched to the extreme solid bottom offset
bX += w + Math.random() * 3 + 1.5; // Natural scanner spacing logic
}
// Standard DOM Canvas export ready for final viewport insertion
return canvas;
}
Apply Changes