You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
dlNumber = "D7658681",
expDate = "03/30/2029",
dob = "03/30/1969",
lastName = "STEWART",
firstName = "RONALD",
address1 = "10 PELICAN CT",
address2 = "SACRAMENTO, CA 95833",
sex = "M",
hair = "BRN",
eyes = "BRO",
height = "5-06",
weight = "142",
dlClass = "C"
) {
// 1. Setup Canvas (High-resolution, ~standard card aspect ratio 8:5)
const cw = 1600;
const ch = 1008;
const canvas = document.createElement('canvas');
canvas.width = cw;
canvas.height = ch;
const ctx = canvas.getContext('2d');
// Polyfill roundRect for older canvas versions
if (!ctx.roundRect) {
ctx.roundRect = function (x, y, w, h, r) {
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
this.closePath();
return this;
};
}
// 2. Dynamically Load Required Google Fonts
const loadFonts = async () => {
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);
try {
await document.fonts.load("100px 'Pacifico'");
await document.fonts.load("80px 'Caveat'");
} catch (error) {
console.warn("Fonts loading failed or browser doesn't support API. Using fallbacks.");
}
};
await loadFonts();
// 3. Draw Background (Complex Wavy Guilloche Security Lines)
const drawBackground = () => {
// Base fill
ctx.fillStyle = '#FDFDFD';
ctx.fillRect(0, 0, cw, ch);
ctx.lineWidth = 1.2;
// Draw intersecting sine/bezier waves
for (let i = 0; i < 90; i += 2) {
ctx.beginPath();
if (i % 3 === 0) ctx.strokeStyle = `rgba(254, 240, 138, 0.45)`; // Yellow gradient mix
else if (i % 2 === 0) ctx.strokeStyle = `rgba(135, 206, 235, 0.45)`; // Light blue gradient mix
else ctx.strokeStyle = `rgba(255, 255, 255, 0.5)`; // White highlight
for (let x = 0; x <= cw; x += 20) {
let wave1 = Math.sin((x * 0.005) + i) * 200;
let wave2 = Math.cos((x * 0.01) + (i * 0.5)) * 100;
let y = (ch / 2) + wave1 + wave2;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
// Overlay faint gradient for photorealism
const bGrad = ctx.createLinearGradient(0, 0, cw, ch);
bGrad.addColorStop(0, 'rgba(255, 255, 255, 0.6)');
bGrad.addColorStop(0.5, 'rgba(255, 248, 200, 0.2)');
bGrad.addColorStop(1, 'rgba(200, 230, 245, 0.5)');
ctx.fillStyle = bGrad;
ctx.fillRect(0, 0, cw, ch);
};
// 4. Draw Header
const drawHeader = () => {
// USA and DRIVER LICENSE Text
ctx.font = 'bold 28px Arial, sans-serif';
ctx.fillStyle = '#1E3A8A';
ctx.fillText("USA", 650, 70);
ctx.font = 'bold 36px Arial, sans-serif';
ctx.fillStyle = '#2563EB'; // lighter blue
ctx.fillText("DRIVER LICENSE", 750, 70);
// California Script Logo
// Add white shadow to make script pop from background
ctx.shadowColor = 'rgba(255,255,255,0.9)';
ctx.shadowBlur = 10;
ctx.font = '110px "Pacifico", cursive';
ctx.fillStyle = '#1E3A8A'; // classic bold blue
ctx.fillText("California", 80, 115);
ctx.shadowBlur = 0; // reset shadow
};
// 5. Draw Golden Grizzly Bear with White Star
const drawBear = () => {
ctx.save();
ctx.translate(1250, 20); // Top right position
ctx.scale(1.4, 1.4);
// Golden Gradients
const goldGrad = ctx.createLinearGradient(0, 0, 180, 120);
goldGrad.addColorStop(0, '#FDE047');
goldGrad.addColorStop(0.5, '#CA8A04');
goldGrad.addColorStop(1, '#A16207');
ctx.fillStyle = goldGrad;
ctx.beginPath();
// Approximate vector shape of the California Grizzly Bear
ctx.moveTo(10, 45); // nose
ctx.lineTo(25, 30); // head
ctx.lineTo(40, 15); // ear
ctx.lineTo(55, 22); // back neck
ctx.bezierCurveTo(75, 10, 110, 10, 130, 25); // back
ctx.lineTo(150, 25); // hump
ctx.bezierCurveTo(165, 30, 175, 60, 160, 85); // rump
ctx.lineTo(155, 115); // back foot
ctx.lineTo(135, 115);
ctx.lineTo(135, 95);
ctx.lineTo(120, 95); // under tail
ctx.lineTo(115, 115);
ctx.lineTo(95, 115); // front-back foot
ctx.lineTo(95, 80); // belly
ctx.lineTo(60, 75); // belly
ctx.lineTo(45, 110); // front foot
ctx.lineTo(25, 110);
ctx.lineTo(35, 75); // chest
ctx.lineTo(15, 70);
ctx.lineTo(8, 55);
ctx.closePath();
ctx.fill();
// White cutout star in center
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.translate(90, 45); // Center of body
for (let i = 0; i < 5; i++) {
ctx.lineTo(0, -16);
ctx.translate(0, -16);
ctx.rotate((Math.PI * 2) / 10);
ctx.lineTo(0, 16);
ctx.translate(0, 16);
ctx.rotate(-(Math.PI * 6) / 10);
}
ctx.lineTo(0, -16);
ctx.fill();
// Re-draw star actual white to ensure it doesn't just cut to background lines
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = '#FFFFFF';
ctx.fill();
ctx.restore();
};
// 6. Draw Photos
const drawClippedPhoto = (img, x, y, w, h, opacity = 1.0) => {
ctx.save();
ctx.globalAlpha = opacity;
ctx.beginPath();
ctx.roundRect(x, y, w, h, 24); // smooth rounded corners
ctx.clip();
// Calculate dimensions to match aspect ratio (cover photo)
const tgtAspect = w / h;
const imgAspect = img.width / img.height;
let sWidth = img.width, sHeight = img.height, sx = 0, sy = 0;
if (imgAspect > tgtAspect) {
sWidth = img.height * tgtAspect;
sx = (img.width - sWidth) / 2;
} else {
sHeight = img.width / tgtAspect;
sy = (img.height - sHeight) / 2;
}
ctx.drawImage(img, sx, sy, sWidth, sHeight, x, y, w, h);
// Slight border line
ctx.lineWidth = 4;
ctx.lineJoin = "round";
ctx.strokeStyle = "rgba(0,0,0,0.15)";
ctx.stroke();
ctx.restore();
};
// 7. Draw Texts Helper
const drawLabel = (t, x, y, size = 18, align = "left", color = "#475569") => {
ctx.font = `600 ${size}px Arial, sans-serif`;
ctx.fillStyle = color;
ctx.textAlign = align;
ctx.fillText(t, x, y);
ctx.textAlign = "left"; // reset
};
const drawValue = (t, x, y, size = 36, color = "#020617") => {
ctx.font = `bold ${size}px Arial, sans-serif`;
ctx.fillStyle = color;
ctx.fillText(t, x, y);
};
// 8. Draw Linear Barcode
const drawBarcode = (x, y, w, h) => {
const seedStr = dlNumber + dob + lastName;
ctx.fillStyle = '#0F172A';
let cursor = x;
for (let i = 0; i < seedStr.length * 9; i++) {
let charC = seedStr.charCodeAt(i % seedStr.length);
let barW = ((charC * (i + 1)) % 5) + 1;
let gapW = ((charC * i) % 4) + 2;
if (cursor + barW > x + w) break;
ctx.fillRect(cursor, y, barW, h);
cursor += barW + gapW;
}
};
// Apply Drawing Commands
drawBackground();
drawHeader();
drawBear();
// Render Left Main Photo
drawClippedPhoto(originalImg, 70, 180, 440, 586);
// Render Bottom Right Ghost Photo (faded)
drawClippedPhoto(originalImg, 1280, 580, 230, 306, 0.25);
// Render Card Texts
const startX = 550;
const redColor = '#DC2626'; // Authentic red shade
// Group 1: ID, EXP
drawLabel("DL", startX, 210, 22);
drawValue(dlNumber, startX + 45, 212, 46, redColor);
drawLabel("EXP", startX + 400, 210, 22);
drawValue(expDate, startX + 460, 212, 46, redColor);
// Group 2: Name
drawLabel("LN", startX, 300, 20);
drawValue(lastName, startX, 345, 42);
drawLabel("FN", startX, 400, 20);
drawValue(firstName, startX, 445, 42);
drawLabel("DOB", startX + 460, 400, 20);
drawValue(dob, startX + 460, 445, 42, redColor);
// Group 3: Address
drawLabel("Address", startX, 510, 20);
drawValue(address1, startX, 555, 36);
drawValue(address2, startX, 600, 36);
// Group 4: Physical Stats Array
const statYLabel = 680;
const statYVal = 720;
const statW = 120; // column width spacing
drawLabel("Class", startX, statYLabel, 18);
drawValue(dlClass, startX, statYVal, 30);
drawLabel("Sex", startX + statW, statYLabel, 18);
drawValue(sex, startX + statW, statYVal, 30);
drawLabel("Hair", startX + (statW * 2), statYLabel, 18);
drawValue(hair, startX + (statW * 2), statYVal, 30);
drawLabel("Eyes", startX + (statW * 3), statYLabel, 18);
drawValue(eyes, startX + (statW * 3), statYVal, 30);
drawLabel("Hgt", startX + (statW * 4.2), statYLabel, 18);
drawValue(height, startX + (statW * 4.2), statYVal, 30);
drawLabel("Wgt", startX + (statW * 5.6), statYLabel, 18);
drawValue(weight, startX + (statW * 5.6), statYVal, 30);
// 9. Render User Signature
ctx.font = '80px "Caveat", cursive';
ctx.fillStyle = '#0F172A';
// Offset somewhat to simulate real handwritten positioning
ctx.fillText(`${firstName} ${lastName}`, startX, 830);
// 10. Render Barcode at bottom middle
drawBarcode(startX, 870, 700, 90);
return canvas;
}
Apply Changes