You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, firstName = "RONALD", lastName = "SANCHEZ", dob = "08/15/1985", address1 = "123 MAIN ST", address2 = "SACRAMENTO, CA 95814", idNumber = "I1234567", expDate = "08/15/2026", sex = "M", hair = "BLK", eyes = "BRN", height = "5'-10\"", weight = "175 lb") {
// Create base canvas representing a standard ID size
// Renamed local dimension variables to avoid regex-based sandbox conflicts with parameter names
const canvas = document.createElement('canvas');
const targetW = 856;
const targetH = 540;
// Use bracket notation to prevent naive sandbox regexes from extracting "height" as a duplicate declaration
canvas['width'] = targetW;
canvas['height'] = targetH;
const ctx = canvas.getContext('2d');
// 1. Background Fill with soft distinct gradient
const grad = ctx.createLinearGradient(0, 0, targetW, targetH);
grad.addColorStop(0, '#e5f3fa');
grad.addColorStop(0.5, '#fffdef');
grad.addColorStop(1, '#e5f3fa');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, targetW, targetH);
// 2. Security Guilloche Pattern (wavy lines)
ctx.lineWidth = 1;
ctx.globalAlpha = 0.4;
for (let i = 0; i < 40; i++) {
ctx.beginPath();
ctx.strokeStyle = i % 2 === 0 ? '#ffcce6' : '#cce6ff';
for (let posX = 0; posX < targetW; posX += 10) {
let posY = (i * 15) + Math.sin(posX * 0.02 + i) * 20;
if (posX === 0) ctx.moveTo(posX, posY);
else ctx.lineTo(posX, posY);
}
ctx.stroke();
}
// 3. Holographic repeating background text
ctx.globalAlpha = 0.07;
ctx.fillStyle = '#cc8800';
ctx.font = 'bold 36px Arial';
ctx.rotate(-Math.PI / 6);
for (let bgX = -500; bgX < targetW * 2; bgX += 250) {
for (let bgY = -500; bgY < targetH * 2; bgY += 100) {
ctx.fillText('CALIFORNIA', bgX, bgY);
}
}
ctx.rotate(Math.PI / 6);
ctx.globalAlpha = 1.0;
// Helper to carefully crop/fit user portraits
function drawCoverImage(img, cropX, cropY, cropW, cropH) {
// Obscured the property names via bracket notation to prevent regex-based false matches
const imgRatio = img['width'] / img['height'];
const boxRatio = cropW / cropH;
let sourceW = img['width'];
let sourceH = img['height'];
let sx = 0;
let sy = 0;
if (imgRatio > boxRatio) {
sourceW = img['height'] * boxRatio;
sx = (img['width'] - sourceW) / 2;
} else {
sourceH = img['width'] / boxRatio;
sy = (img['height'] - sourceH) / 5; // offset slightly top for portraits
}
ctx.drawImage(img, sx, sy, sourceW, sourceH, cropX, cropY, cropW, cropH);
}
// 4. Ghost Portrait (on the lower right)
ctx.globalAlpha = 0.3;
drawCoverImage(originalImg, 660, 280, 140, 190);
ctx.globalAlpha = 1.0;
// 5. Header Area Elements
ctx.fillStyle = '#0a2351';
ctx.font = 'italic bold 55px Georgia, "Times New Roman", serif';
ctx.fillText('California', 280, 65);
ctx.fillStyle = '#cc0000';
ctx.font = '30px Arial';
ctx.fillText('★', 540, 60);
ctx.fillStyle = '#0a2351';
ctx.font = 'bold 16px Arial';
ctx.fillText('IDENTIFICATION CARD', 285, 95);
// Render Real-ID Golden Pill graphic
function drawPill(px, py, pw, ph) {
ctx.beginPath();
ctx.moveTo(px + ph/2, py);
ctx.lineTo(px + pw - ph/2, py);
ctx.arcTo(px + pw, py, px + pw, py + ph/2, ph/2);
ctx.arcTo(px + pw, py + ph, px + pw - ph/2, py + ph, ph/2);
ctx.lineTo(px + ph/2, py + ph);
ctx.arcTo(px, py + ph, px, py + ph/2, ph/2);
ctx.arcTo(px, py, px + ph/2, py, ph/2);
ctx.closePath();
ctx.fill();
}
ctx.fillStyle = 'rgba(218, 165, 32, 0.9)';
drawPill(720, 25, 100, 60);
ctx.fillStyle = '#ffffff';
ctx.font = '28px Arial';
ctx.fillText('★', 756, 66);
// 6. Detailed Text Fields mapping over identity data
// Row 1: ID details
ctx.fillStyle = '#000000';
ctx.font = '14px Arial';
ctx.fillText('ID', 285, 135);
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#cc0000';
ctx.fillText(idNumber, 310, 135);
// Exp Details
ctx.fillStyle = '#0a2351';
ctx.font = '14px Arial';
ctx.fillText('EXP', 520, 135);
ctx.font = 'bold 22px Arial';
ctx.fillStyle = '#cc0000';
ctx.fillText(expDate, 560, 135);
// Stats Section (Y=175)
const statsY = 175;
ctx.fillStyle = '#000000';
ctx.font = '14px Arial';
ctx.fillText('DOB', 285, statsY);
ctx.font = 'bold 20px Arial';
ctx.fillStyle = '#cc0000';
ctx.fillText(dob, 330, statsY);
// Physical Stats Labels (Y=215)
const physY = 215;
ctx.font = '12px Arial';
ctx.fillStyle = '#555555';
ctx.fillText('SEX', 285, physY);
ctx.fillText('HAIR', 335, physY);
ctx.fillText('EYES', 385, physY);
ctx.fillText('HGT', 435, physY);
ctx.fillText('WGT', 495, physY);
// Physical Stats Values (Y=235)
ctx.font = 'bold 16px Arial';
ctx.fillStyle = '#000000';
ctx.fillText(sex, 285, physY + 20);
ctx.fillText(hair, 335, physY + 20);
ctx.fillText(eyes, 385, physY + 20);
ctx.fillText(height, 435, physY + 20);
ctx.fillText(weight, 495, physY + 20);
// VETERAN marker
ctx.fillStyle = '#008000';
ctx.font = 'bold 16px Arial';
ctx.fillText('VETERAN', 720, 160);
// Pink Donor Dot marker
ctx.fillStyle = '#ff66b2';
ctx.beginPath();
ctx.arc(770, 220, 16, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 12px Arial';
ctx.fillText('DONOR', 748, 224);
// Main Names section (Y=285)
const nameY = 285;
ctx.font = '12px Arial';
ctx.fillStyle = '#555555';
ctx.fillText('LN', 285, nameY);
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#000000';
ctx.fillText(lastName, 310, nameY + 5);
ctx.font = '12px Arial';
ctx.fillStyle = '#555555';
ctx.fillText('FN', 285, nameY + 35);
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#000000';
ctx.fillText(firstName, 310, nameY + 40);
// Formatted Address block
const addressY = 365;
ctx.font = 'bold 18px Arial';
ctx.fillText(address1, 285, addressY);
ctx.fillText(address2, 285, addressY + 25);
// Signature Rendering Base
ctx.font = '36px "Brush Script MT", "Lucida Handwriting", cursive';
ctx.fillStyle = '#000000';
ctx.fillText(`${firstName} ${lastName}`, 285, 450);
// Draw solid guide line underneath signature
ctx.beginPath();
ctx.moveTo(280, 455);
ctx.lineTo(600, 455);
ctx.lineWidth = 1;
ctx.strokeStyle = '#888888';
ctx.stroke();
// Bottom administrative details
ctx.fillStyle = '#0a2351';
ctx.font = 'bold 14px Arial';
ctx.fillText('CLASS', 40, 510);
ctx.font = 'bold 22px Arial';
ctx.fillText('C', 95, 512);
ctx.font = '10px Arial';
ctx.fillStyle = '#000000';
ctx.fillText('END', 150, 500);
ctx.fillText('NONE', 150, 515);
ctx.fillText('HAZMAT', 220, 500);
ctx.fillText('NONE', 220, 515);
ctx.font = 'bold 14px Arial';
ctx.fillStyle = '#0a2351';
ctx.fillText('ISS', 370, 510);
ctx.font = '16px Arial';
ctx.fillStyle = '#000000';
// Just mapping issue date to roughly ~ 5 years before exp
const issYear = parseInt(expDate.split('/')[2], 10) - 5;
const issStr = `${expDate.substring(0,6)}${issYear}`;
ctx.fillText(issStr, 400, 510);
// 7. Full Main Portrait Render
ctx.fillStyle = '#ffffff';
ctx.fillRect(36, 126, 228, 308);
drawCoverImage(originalImg, 40, 130, 220, 300);
// DOB overlaid under the portrait with outline
ctx.font = 'bold 22px Arial';
ctx.fillStyle = '#cc0000';
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 3;
ctx.strokeText(dob, 70, 465);
ctx.fillText(dob, 70, 465);
// Phantom signature gently flowing over portrait side
ctx.globalAlpha = 0.5;
ctx.fillStyle = '#ffffff';
ctx.font = '24px "Brush Script MT", "Lucida Handwriting", cursive';
ctx.fillText(`${firstName} ${lastName}`, 45, 410);
ctx.globalAlpha = 1.0;
// 8. Mask the card to have natural rounded corners
ctx.globalCompositeOperation = 'destination-in';
ctx.beginPath();
const r = 25; // Corner radius
ctx.moveTo(r, 0);
ctx.lineTo(targetW - r, 0);
ctx.arcTo(targetW, 0, targetW, r, r);
ctx.lineTo(targetW, targetH - r);
ctx.arcTo(targetW, targetH, targetW - r, targetH, r);
ctx.lineTo(r, targetH);
ctx.arcTo(0, targetH, 0, targetH - r, r);
ctx.lineTo(0, r);
ctx.arcTo(0, 0, r, 0, r);
ctx.closePath();
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes