You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
missionName = "MISSION NAME",
bottomText = `${new Date().getFullYear()} - EXPLORERS`,
patchColor = "#002244", // Dark blue
borderColor = "#FFD700", // Gold
textColor = "#FFFFFF", // White
borderWidth = 30,
fontChoice = "ArialBlack", // Options: "ArialBlack", "Impact", "Orbitron"
topTextSize = 36,
bottomTextSize = 24
) {
let activeFontFamily = "Arial Black, Gadget, sans-serif"; // Default
const orbitronFontName = "Orbitron";
if (fontChoice === "Orbitron") {
let isOrbitronReady = false;
if (document.fonts) {
try {
// Check for specific weights if they are used, e.g., 'bold 1em Orbitron'
isOrbitronReady = await document.fonts.check(`bold 1em ${orbitronFontName}`);
} catch (e) {
console.warn("Error checking for Orbitron font:", e);
}
}
if (!isOrbitronReady) {
const orbitronNormalUrl = "https://fonts.gstatic.com/s/orbitron/v25/yMJRMIlzdpvBhQQL_OUhnw.woff2"; // Orbitron Regular 400
const orbitronBoldUrl = "https://fonts.gstatic.com/s/orbitron/v25/yMJWMIlzdpvBhQQL_QJXLJ3tZv4.woff2"; // Orbitron Bold 700
const styleId = "orbitron-font-style-patch-creator"; // Unique ID for the style tag
if (!document.getElementById(styleId)) {
const newStyle = document.createElement('style');
newStyle.id = styleId;
newStyle.textContent = `
@font-face {
font-family: '${orbitronFontName}';
src: url('${orbitronNormalUrl}') format('woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: '${orbitronFontName}';
src: url('${orbitronBoldUrl}') format('woff2');
font-weight: bold;
font-style: normal;
}
`;
document.head.appendChild(newStyle);
}
try {
if (document.fonts) {
await Promise.all([
document.fonts.load(`normal 1em ${orbitronFontName}`),
document.fonts.load(`bold 1em ${orbitronFontName}`)
]);
activeFontFamily = `"${orbitronFontName}", Arial Black, Gadget, sans-serif`;
} else {
// Fallback for environments without document.fonts
activeFontFamily = `"${orbitronFontName}", Arial Black, Gadget, sans-serif`;
await new Promise(resolve => setTimeout(resolve, 300)); // Small delay
}
} catch (e) {
console.warn(`${orbitronFontName} font could not be loaded, using fallback. Error:`, e);
// activeFontFamily remains the default
}
} else {
activeFontFamily = `"${orbitronFontName}", Arial Black, Gadget, sans-serif`;
}
} else if (fontChoice === "Impact") {
activeFontFamily = "Impact, Arial Black, Gadget, sans-serif";
}
// Else, "ArialBlack" uses the default activeFontFamily
const canvas = document.createElement('canvas');
const size = 600; // Patch size
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const centerX = size / 2;
const centerY = size / 2;
const outerRadius = size / 2 - 5; // Small margin from edge
// 1. Draw patch background color (main circle)
ctx.beginPath();
ctx.arc(centerX, centerY, outerRadius, 0, Math.PI * 2);
ctx.fillStyle = patchColor;
ctx.fill();
// 2. Draw border
if (borderWidth > 0) {
ctx.beginPath();
ctx.arc(centerX, centerY, outerRadius - borderWidth / 2, 0, Math.PI * 2); // Border centered on its width
ctx.lineWidth = borderWidth;
ctx.strokeStyle = borderColor;
ctx.stroke();
}
// Define radius for the image placement area (inside the border)
const imageAreaRadius = outerRadius - borderWidth - 5; // 5px padding from border's inner edge
// 3. Draw original image in the center, clipped to a circle
if (originalImg && imageAreaRadius > 0) {
ctx.save();
ctx.beginPath();
ctx.arc(centerX, centerY, imageAreaRadius, 0, Math.PI * 2);
ctx.clip();
const imgNatWidth = originalImg.naturalWidth || originalImg.width;
const imgNatHeight = originalImg.naturalHeight || originalImg.height;
const destDiameter = imageAreaRadius * 2;
let sourceX = 0, sourceY = 0, sourceWidth = imgNatWidth, sourceHeight = imgNatHeight;
// Crop to center square for a 'porthole' effect, maintaining aspect ratio
if (imgNatWidth / imgNatHeight > 1) { // Landscape
sourceWidth = imgNatHeight;
sourceX = (imgNatWidth - sourceWidth) / 2;
} else { // Portrait or square
sourceHeight = imgNatWidth;
sourceY = (imgNatHeight - sourceHeight) / 2;
}
ctx.drawImage(
originalImg,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
centerX - imageAreaRadius,
centerY - imageAreaRadius,
destDiameter,
destDiameter
);
ctx.restore(); // Release clipping path
}
// Radii for text paths (inside the border, outside the main image circle typically)
// Place text base just inside the border
const textRadiusTop = outerRadius - borderWidth - (topTextSize * 0.35);
const textRadiusBottom = outerRadius - borderWidth - (bottomTextSize * 0.35);
// Helper function for drawing curved text
function drawCurvedTextInternal(currentCtx, text, cX, cY, radius, color, isTop, currentFont, currentFontSize) {
currentCtx.save();
currentCtx.fillStyle = color;
currentCtx.font = `bold ${currentFontSize}px ${currentFont}`; // Set font here with size
currentCtx.textAlign = 'center';
const characters = text.toUpperCase().split('');
const N = characters.length;
if (N === 0) {
currentCtx.restore();
return;
}
let totalAngleSweep;
if (isTop) {
totalAngleSweep = (2 * Math.PI) / 3; // 120 degrees for top text
currentCtx.textBaseline = 'bottom'; // Text hangs below the arc path for top
} else { // Bottom text
totalAngleSweep = (5 * Math.PI) / 9; // 100 degrees for bottom text
// Small adjustment for very short bottom text to prevent wide spacing
if (N <= 3) totalAngleSweep = Math.PI / 3; // 60 degrees
else if (N <=5) totalAngleSweep = Math.PI / 2; // 90 degrees
currentCtx.textBaseline = 'top'; // Text sits above the arc path for bottom
}
const anglePerCharCenter = totalAngleSweep / N;
const baseAngle = isTop ? -Math.PI / 2 : Math.PI / 2; // Top or Bottom center of the patch
// Start angle for the center of the first character
let currentDisplayAngle = baseAngle - (totalAngleSweep / 2) + (anglePerCharCenter / 2);
for (let i = 0; i < N; i++) {
const char = characters[i];
const charAngle = currentDisplayAngle + i * anglePerCharCenter;
currentCtx.save();
currentCtx.translate(cX, cY);
currentCtx.rotate(charAngle);
if (isTop) {
currentCtx.translate(0, -radius); // Move out to radius on y-axis
} else { // Bottom text
currentCtx.translate(0, radius); // Move out to radius on y-axis
currentCtx.rotate(Math.PI); // Rotate character to be upright
}
currentCtx.fillText(char, 0, 0);
currentCtx.restore();
}
currentCtx.restore();
}
// 4. Draw Top Text (Mission Name)
if (missionName && missionName.trim() !== "" && topTextSize > 0) {
drawCurvedTextInternal(ctx, missionName, centerX, centerY, textRadiusTop, textColor, true, activeFontFamily, topTextSize);
}
// 5. Draw Bottom Text
if (bottomText && bottomText.trim() !== "" && bottomTextSize > 0) {
drawCurvedTextInternal(ctx, bottomText, centerX, centerY, textRadiusBottom, textColor, false, activeFontFamily, bottomTextSize);
}
return canvas;
}
Apply Changes