You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, state = 'CA') {
// Determine canvas dimensions (CR80 standard ratio equivalent roughly)
const canvas = document.createElement('canvas');
const w = 1000;
const h = 630;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Make sure we have a valid image before starting specific draws
if (!originalImg || !originalImg.width || !originalImg.height) {
console.error("Invalid or un-loaded image passed.");
return canvas;
}
// Process State ID
let requestedState = (state || 'CA').trim().toUpperCase();
let stateAbbr = requestedState.substring(0, 2);
// Round corner clipping for realistic physical card
ctx.beginPath();
const radius = 25;
ctx.moveTo(radius, 0);
ctx.arcTo(w, 0, w, h, radius);
ctx.arcTo(w, h, 0, h, radius);
ctx.arcTo(0, h, 0, 0, radius);
ctx.arcTo(0, 0, w, 0, radius);
ctx.closePath();
ctx.clip();
// 1. Draw Background & Security Patterns
// Base solid fill
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, w, h);
// Shared generic wavy security pattern base (Guilloche-style look)
ctx.save();
ctx.lineWidth = 1.5;
for (let i = -h; i < h * 2; i += 18) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.bezierCurveTo(w / 3, i + 180, w * 2 / 3, i - 180, w, i);
ctx.strokeStyle = 'rgba(150, 160, 200, 0.08)';
ctx.stroke();
}
ctx.restore();
// 2. Draw State-Specific Header/Background details
if (requestedState === 'CA' || requestedState === 'CALIFORNIA') {
stateAbbr = 'CA';
// Linear gradient background
const grad = ctx.createLinearGradient(0, 0, w, h);
grad.addColorStop(0, '#f2f7fb');
grad.addColorStop(0.5, '#fffdef');
grad.addColorStop(1, '#e3f1fb');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
// Header Text
ctx.font = 'bold 46px Georgia, serif';
ctx.fillStyle = '#C90000'; // CA Red
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText("CALIFORNIA", w / 2, 35);
ctx.font = 'bold 18px Arial';
ctx.fillStyle = '#003399';
ctx.textAlign = 'right';
ctx.fillText("DRIVER LICENSE", w - 40, 45);
} else if (requestedState === 'NY' || requestedState === 'NEW YORK') {
stateAbbr = 'NY';
// Faint map/lines grid pattern
ctx.beginPath();
for (let i = 120; i < h; i += 25) {
ctx.moveTo(0, i);
ctx.lineTo(w, i);
}
ctx.strokeStyle = '#e0ecf8';
ctx.lineWidth = 2;
ctx.stroke();
// Top Banner
ctx.fillStyle = '#002D72';
ctx.fillRect(0, 0, w, 90);
ctx.fillStyle = '#ffce00';
ctx.fillRect(0, 90, w, 6);
// Header Text
ctx.font = 'bold 42px Arial';
ctx.fillStyle = '#ffce00';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText("NEW YORK STATE", 40, 25);
} else if (requestedState === 'TX' || requestedState === 'TEXAS') {
stateAbbr = 'TX';
ctx.fillStyle = '#f6f9ff';
ctx.fillRect(0, 0, w, h);
// TX Top Split Banner
ctx.fillStyle = '#bf0a30'; // Red
ctx.fillRect(0, 0, w / 3, 90);
ctx.fillStyle = '#002868'; // Blue
ctx.fillRect(w / 3, 0, w * 2 / 3, 90);
// Draw basic white star
ctx.fillStyle = '#ffffff';
ctx.beginPath();
const cx = w / 6, cy = 45, outerR = 25, innerR = 10;
let rot = Math.PI / 2 * 3;
ctx.moveTo(cx, cy - outerR);
for (let i = 0; i < 5; i++) {
ctx.lineTo(cx + Math.cos(rot) * outerR, cy + Math.sin(rot) * outerR);
rot += Math.PI / 5;
ctx.lineTo(cx + Math.cos(rot) * innerR, cy + Math.sin(rot) * innerR);
rot += Math.PI / 5;
}
ctx.closePath();
ctx.fill();
// Header Text
ctx.font = 'bold 50px Arial';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText("TEXAS", w / 2 + w / 6, 25);
} else {
// Generic Template for any other state
// Procedurally generate unique theme color from string hash
let hash = 0;
for (let i = 0; i < requestedState.length; i++) hash = requestedState.charCodeAt(i) + ((hash << 5) - hash);
const hue = Math.abs(hash % 360);
ctx.fillStyle = '#f8f8f8';
ctx.fillRect(0, 0, w, h);
// Mesh background
ctx.lineWidth = 1;
ctx.strokeStyle = `hsla(${hue}, 40%, 70%, 0.2)`;
for (let x = 0; x < w; x += 30) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x + 150, h); ctx.stroke(); }
for (let y = 0; y < h; y += 30) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y + 150); ctx.stroke(); }
// Top Banner
ctx.fillStyle = `hsl(${hue}, 70%, 25%)`;
ctx.fillRect(0, 0, w, 90);
ctx.fillStyle = `hsl(${(hue + 45) % 360}, 80%, 55%)`; // Accent line
ctx.fillRect(0, 90, w, 8);
// Header Text
ctx.font = 'bold 45px Arial';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(requestedState, w / 2, 25);
}
// Reset common font and text properties
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// 3. Central Portrait and Photo Drawing
const pX = 40, pY = 130, pW = 250, pH = 330;
// Scale and crop the original image to strictly fit the ID portrait box aspect ratio
const aspect = originalImg.width / originalImg.height;
const targetAspect = pW / pH;
let sx = 0, sy = 0, sw = originalImg.width, sh = originalImg.height;
if (aspect > targetAspect) {
// Image is wider than needed
sw = originalImg.height * targetAspect;
sx = (originalImg.width - sw) / 2;
} else {
// Image is taller than needed
sh = originalImg.width / targetAspect;
sy = (originalImg.height - sh) / 2;
}
// Draw main portrait
ctx.drawImage(originalImg, sx, sy, sw, sh, pX, pY, pW, pH);
// Portrait border
ctx.strokeStyle = '#555';
ctx.lineWidth = 2;
ctx.strokeRect(pX, pY, pW, pH);
// Draw bottom-right ghost (faint duplicate image) for security look
ctx.globalAlpha = 0.2;
ctx.drawImage(originalImg, sx, sy, sw, sh, w - 210, h - 260, 160, 211);
ctx.globalAlpha = 1.0;
// 4. Fill ID Data Attributes
const rightColX = 720;
// Driver License Number Box
ctx.fillStyle = '#000';
ctx.font = 'bold 12px Arial';
ctx.fillText("4d DL No", rightColX, 130);
ctx.font = 'bold 28px Arial';
ctx.fillStyle = '#b00';
ctx.fillText("D12345678", rightColX + 60, 126);
// Dates block
ctx.font = 'bold 12px Arial'; ctx.fillStyle = '#000'; ctx.fillText("4b EXP", rightColX, 180);
ctx.font = 'bold 22px Arial'; ctx.fillStyle = '#b00'; ctx.fillText("01/01/2030", rightColX + 60, 175);
ctx.font = 'bold 12px Arial'; ctx.fillStyle = '#000'; ctx.fillText("3 DOB", rightColX, 220);
ctx.font = 'bold 22px Arial'; ctx.fillStyle = '#b00'; ctx.fillText("01/01/1990", rightColX + 60, 215);
ctx.font = 'bold 12px Arial'; ctx.fillStyle = '#000'; ctx.fillText("4a ISS", rightColX, 260);
ctx.font = 'bold 18px Arial'; ctx.fillStyle = '#000'; ctx.fillText("01/01/2020", rightColX + 60, 257);
// Person Data
const textX = 330;
let nY = 140;
ctx.font = '12px Arial'; ctx.fillStyle = '#777'; ctx.fillText("1", textX, nY);
ctx.font = 'bold 24px Arial'; ctx.fillStyle = '#000'; ctx.fillText("SAMPLE", textX + 20, nY - 3);
ctx.font = '12px Arial'; ctx.fillStyle = '#777'; ctx.fillText("2", textX, nY + 40);
ctx.font = 'bold 24px Arial'; ctx.fillStyle = '#000'; ctx.fillText("JANE A", textX + 20, nY + 37);
ctx.font = '12px Arial'; ctx.fillStyle = '#777'; ctx.fillText("8", textX, nY + 95);
ctx.font = 'bold 18px Arial'; ctx.fillStyle = '#000';
ctx.fillText("1234 ANYWHERE ST", textX + 20, nY + 93);
ctx.fillText(`CITY, ${stateAbbr} 12345-6789`, textX + 20, nY + 115);
// Physical details row
let attrY = nY + 175;
const physicalAttrs = [
{ l: "SEX", v: "F" },
{ l: "HAIR", v: "BRN" },
{ l: "EYES", v: "BRN" },
{ l: "HGT", v: "5'-06\"" },
{ l: "WGT", v: "140 lb" },
{ l: "CLASS", v: "C" }
];
let currX = textX;
physicalAttrs.forEach(attr => {
ctx.font = '12px Arial'; ctx.fillStyle = '#777'; ctx.fillText(attr.l, currX, attrY);
ctx.font = 'bold 18px Arial'; ctx.fillStyle = '#000'; ctx.fillText(attr.v, currX, attrY + 18);
currX += 75;
});
// Endorsements
ctx.font = '14px Arial'; ctx.fillStyle = '#777';
ctx.fillText("9 END NONE", textX, attrY + 60);
ctx.fillText("12 REST NONE", textX + 160, attrY + 60);
// 5. Holograms / Overlays for ultra realism
ctx.save();
// Repeating faint text globally
ctx.globalAlpha = 0.04;
ctx.font = 'bold 40px Arial';
ctx.fillStyle = '#000';
ctx.translate(w / 2, h / 2);
ctx.rotate(-Math.PI / 8);
for (let i = -700; i < 700; i += 220) {
for (let j = -600; j < 600; j += 100) {
ctx.fillText("AUTHENTIC", i, j);
}
}
ctx.restore();
// Circular Hologram overlaying the bottom right corner of portrait
ctx.save();
ctx.globalCompositeOperation = "color-dodge";
const hX = pX + pW, hY = pY + pH;
const gradSeal = ctx.createRadialGradient(hX, hY, 10, hX, hY, 140);
gradSeal.addColorStop(0, "rgba(255, 0, 0, 0.4)");
gradSeal.addColorStop(0.33, "rgba(0, 255, 0, 0.3)");
gradSeal.addColorStop(0.66, "rgba(0, 0, 255, 0.4)");
gradSeal.addColorStop(1, "rgba(255, 255, 255, 0)");
ctx.fillStyle = gradSeal;
ctx.beginPath();
ctx.arc(hX, hY, 140, 0, Math.PI * 2);
ctx.fill();
// Holographic text ring element
ctx.globalAlpha = 0.4;
ctx.font = "bold 20px Arial";
ctx.fillStyle = "rgba(255,255,255,0.7)";
ctx.translate(hX - 30, hY - 30);
ctx.rotate(-Math.PI / 4);
ctx.fillText("SECURE · ISSUANCE", -80, 0);
ctx.restore();
// 6. Draw Signature
ctx.save();
ctx.globalAlpha = 0.85;
ctx.font = 'normal 46px "Brush Script MT", "Cedarville Cursive", cursive';
ctx.fillStyle = '#000044'; // Signature ink color
ctx.translate(textX + 50, attrY + 120);
ctx.rotate(-0.06); // Slight signature slant
ctx.fillText("Jane A Sample", 0, 0);
ctx.restore();
// 7. Dummy Barcode / Front MRZ
let bcX = 40;
let bcY = 560;
ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
// A classic random 1D barcode looking block at the bottom
Math.seedrandom = 12345; // Just use math.random but deterministic style length
for (let i = 0; i < 90; i++) {
let barW = (Math.random() > 0.5) ? 2 : 5;
let gap = (Math.random() > 0.5) ? 2 : 4;
ctx.fillRect(bcX, bcY, barW, 35);
bcX += barW + gap;
}
return canvas;
}
Apply Changes