You can edit the below JavaScript code to customize the image tool.
Apply Changes
// Manages the state of font loading globally to avoid redundant operations.
let cyberpunkIdFontsLoaded = false;
let cyberpunkIdFontsLoadingPromise = null;
async function ensureCyberpunkIdFonts() {
if (cyberpunkIdFontsLoaded) {
return; // Fonts are already loaded and ready.
}
if (cyberpunkIdFontsLoadingPromise) {
return cyberpunkIdFontsLoadingPromise; // A loading operation is already in progress.
}
// Defines the actual font loading logic.
const loadFonts = async () => {
const fontFamilies = [
{ name: 'Orbitron', weights: '700' }, // For corporation name, prominent headers.
{ name: 'Share Tech Mono', weights: '400' } // For body text, IDs, status messages.
];
// Construct the Google Fonts API URL.
let fontUrl = 'https://fonts.googleapis.com/css2?';
fontUrl += fontFamilies.map(f => `family=${f.name.replace(/ /g, '+')}${f.weights ? `:wght@${f.weights}` : ''}`).join('&');
fontUrl += '&display=swap'; // Use font-display: swap for better UX.
// Add the <style> tag to the document head if it doesn't exist.
if (!document.getElementById('cyberpunk-id-fonts-style-container')) {
const style = document.createElement('style');
style.id = 'cyberpunk-id-fonts-style-container';
style.textContent = `@import url('${fontUrl}');`;
document.head.appendChild(style);
}
// Use document.fonts API if available to wait for fonts to be ready.
if (document.fonts) {
try {
const fontPromises = [
document.fonts.load("bold 30px Orbitron"),
document.fonts.load("bold 20px 'Share Tech Mono'"),
document.fonts.load("16px 'Share Tech Mono'"),
document.fonts.load("14px 'Share Tech Mono'"),
document.fonts.load("12px 'Share Tech Mono'")
];
await Promise.all(fontPromises);
cyberpunkIdFontsLoaded = true; // Mark fonts as successfully loaded.
} catch (e) {
console.warn("Cyberpunk ID font loading failed or timed out. Fallback fonts may be used.", e);
cyberpunkIdFontsLoaded = true; // Mark as attempted to avoid retrying indefinitely.
// Don't re-throw, allow to proceed with fallback.
}
} else {
// Fallback for browsers without document.fonts API (e.g., older browsers).
// Wait a fixed amount of time, hoping @import completes.
await new Promise(resolve => setTimeout(resolve, 800));
cyberpunkIdFontsLoaded = true; // Mark as attempted.
}
};
// Store the promise to ensure concurrent calls wait for the same loading operation.
cyberpunkIdFontsLoadingPromise = loadFonts().finally(() => {
cyberpunkIdFontsLoadingPromise = null; // Clear promise once loading is complete (success or fail).
});
return cyberpunkIdFontsLoadingPromise;
}
async function processImage(
originalImg,
employeeName = "UNIT 731",
employeeTitle = "FIELD OPERATIVE",
employeeId = "ID: SX-808-9B",
corporationName = "ARASAKA LTD.",
accentColor = "#FF00FF", // Default: Magenta
backgroundColor = "#0A0A15" // Default: Very dark blue/near black
) {
try {
await ensureCyberpunkIdFonts(); // Wait for fonts to be loaded or loading attempt to finish.
} catch (e) {
// This catch is mostly for the promise chain if ensureCyberpunkIdFonts itself threw.
// The internal error handling in ensureCyberpunkIdFonts usually prevents this.
console.warn("Font loading encountered an issue. Proceeding with potentially unstyled fonts for Cyberpunk ID.");
}
const canvas = document.createElement('canvas');
const width = 450;
const height = 280;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
// Subtle background horizontal lines (simulates a digital screen)
ctx.save();
ctx.strokeStyle = `${accentColor}1A`; // Very faint accent color (alpha 1A hex = approx 0.1)
ctx.lineWidth = 0.5; // Thin lines
for (let y = 0; y < height; y += 4) {
ctx.beginPath();
ctx.moveTo(0, y + 0.5); // Offset by 0.5 for crisp 1px lines if lineWidth is 1, good practice for sub-pixel too.
ctx.lineTo(width, y + 0.5);
ctx.stroke();
}
ctx.restore();
// --- Layout Constants ---
const padding = 20;
const photoX = padding;
const photoY = 60;
const photoWidth = 100;
const photoHeight = 120;
const textStartX = photoX + photoWidth + padding; // Where text elements to the right of photo begin
// 2. Corporation Name
ctx.font = "bold 30px Orbitron, sans-serif"; // Use Orbitron for a futuristic feel
ctx.fillStyle = accentColor;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
const corpNameText = corporationName.toUpperCase();
ctx.fillText(corpNameText, padding, padding - 5);
// Decorative line under Corporation Name
ctx.strokeStyle = accentColor;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, padding + 28); // Positioned below the corporation name
ctx.lineTo(width - padding, padding + 28);
ctx.stroke();
// 3. Draw Employee Photo
// Calculate cropping to make the image "cover" the photo area while maintaining aspect ratio
const photoAspect = photoWidth / photoHeight;
const imgAspect = originalImg.width / originalImg.height;
let sx = 0, sy = 0, sWidthOriginal = originalImg.width, sHeightOriginal = originalImg.height;
if (imgAspect > photoAspect) { // Image is wider than the target photo area
sWidthOriginal = originalImg.height * photoAspect; // Crop width
sx = (originalImg.width - sWidthOriginal) / 2; // Center horizontally
} else { // Image is taller than or has the same aspect ratio as the target area
sHeightOriginal = originalImg.width / photoAspect; // Crop height
sy = (originalImg.height - sHeightOriginal) / 2; // Center vertically
}
ctx.drawImage(originalImg, sx, sy, sWidthOriginal, sHeightOriginal, photoX, photoY, photoWidth, photoHeight);
// Photo border
ctx.strokeStyle = accentColor;
ctx.lineWidth = 3; // A noticeable border
ctx.strokeRect(photoX, photoY, photoWidth, photoHeight);
// Scanlines effect on the photo
ctx.save();
ctx.beginPath();
ctx.rect(photoX, photoY, photoWidth, photoHeight); // Clip scanlines to the photo area
ctx.clip();
ctx.lineWidth = 1;
// Darker scanlines
ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)';
for (let yScan = photoY; yScan < photoY + photoHeight; yScan += 4) {
ctx.beginPath();
ctx.moveTo(photoX, yScan + 0.5);
ctx.lineTo(photoX + photoWidth, yScan + 0.5);
ctx.stroke();
}
// Fainter accent-colored scanlines, offset from the dark ones
ctx.strokeStyle = `${accentColor}33`; // Accent color with low opacity (33 hex = approx 0.2 alpha)
for (let yScan = photoY + 2; yScan < photoY + photoHeight; yScan += 4) {
ctx.beginPath();
ctx.moveTo(photoX, yScan + 0.5);
ctx.lineTo(photoX + photoWidth, yScan + 0.5);
ctx.stroke();
}
ctx.restore();
// 4. Employee Information (Name, Title, ID)
const infoYstart = photoY + 5; // Align text slightly below top edge of photo frame
ctx.font = "bold 20px 'Share Tech Mono', monospace"; // Share Tech Mono for a techy look
ctx.fillStyle = "#E0E0E0"; // Light grey/white for good readability
ctx.fillText(employeeName.toUpperCase(), textStartX, infoYstart);
ctx.font = "16px 'Share Tech Mono', monospace";
ctx.fillStyle = accentColor; // Title in accent color
ctx.fillText(employeeTitle.toUpperCase(), textStartX, infoYstart + 28);
ctx.font = "14px 'Share Tech Mono', monospace";
ctx.fillStyle = "#B0B0B0"; // Medium grey for less prominent info
ctx.fillText(employeeId.toUpperCase(), textStartX, infoYstart + 28 + 22);
// 5. "Barcode" like decorative element
const barcodeY = infoYstart + 28 + 22 + 30;
const barcodeHeight = 30;
const barcodeWidth = width - textStartX - padding; // Fill available width
ctx.font = "10px 'Share Tech Mono', monospace";
ctx.fillStyle = accentColor;
ctx.fillText("ACCESS SIGNATURE:", textStartX, barcodeY - 12); // Label for the barcode
ctx.fillStyle = '#FFFFFF'; // Barcode lines in white
for (let i = 0; i < barcodeWidth; i += (Math.random() * 3 + 1.5)) { // Random width and spacing for lines
if (Math.random() > 0.2) { // Some sparsity
ctx.fillRect(textStartX + i, barcodeY, Math.random() * 1.5 + 0.5, barcodeHeight);
}
}
// Small text under the pseudo-barcode
ctx.font = "10px 'Share Tech Mono', monospace";
ctx.fillStyle = "#888888"; // Darker grey for subtle text
ctx.fillText(`AUTH LVL: ${Math.floor(Math.random()*5)+5} // CLASS: ${String.fromCharCode(65+Math.floor(Math.random()*5))}${Math.floor(Math.random()*3)+1}`, textStartX, barcodeY + barcodeHeight + 10);
// 6. Bottom Status Bar
const statusBarHeight = 25;
const statusBarY = height - statusBarHeight - padding / 2; // Position near bottom
ctx.fillStyle = `${accentColor}CC`; // Semi-transparent accent color (CC hex = approx 0.8 alpha)
ctx.fillRect(padding, statusBarY, width - 2 * padding, statusBarHeight);
ctx.font = "12px 'Share Tech Mono', monospace";
ctx.fillStyle = "#FFFFFF"; // Bright white text for contrast on the accent-colored bar
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; // Vertically center text in the bar
const firstCorpWord = corporationName.split(" ")[0] || "CORP";
ctx.fillText(`SYSTEM ONLINE // BIOMETRICS: STABLE // ${firstCorpWord.toUpperCase()}-NET ACCESS`, width / 2, statusBarY + statusBarHeight / 2);
ctx.textAlign = 'left'; // Reset alignment
ctx.textBaseline = 'top'; // Reset baseline
// 7. Decorative corner elements for the main card
function drawCardCorners(ctx, x, y, w, h, size, lineWidth) {
ctx.strokeStyle = `${accentColor}AA`; // Accent color with some transparency (AA hex = approx 0.66 alpha)
ctx.lineWidth = lineWidth;
const R = x + w; // Right edge
const B = y + h; // Bottom edge
// Top-left
ctx.beginPath(); ctx.moveTo(x + size, y); ctx.lineTo(x, y); ctx.lineTo(x, y + size); ctx.stroke();
// Top-right
ctx.beginPath(); ctx.moveTo(R - size, y); ctx.lineTo(R, y); ctx.lineTo(R, y + size); ctx.stroke();
// Bottom-left
ctx.beginPath(); ctx.moveTo(x, B - size); ctx.lineTo(x, B); ctx.lineTo(x + size, B); ctx.stroke();
// Bottom-right
ctx.beginPath(); ctx.moveTo(R - size, B); ctx.lineTo(R, B); ctx.lineTo(R, B - size); ctx.stroke();
}
drawCardCorners(ctx, padding/2, padding/2, width - padding, height - padding, 15, 2);
return canvas;
}
Apply Changes