You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
ufoType = "disc",
ufoXPercent = 50,
ufoYPercent = 30,
ufoSizePercent = 10,
reportTitle = "INCIDENT LOG XR-7",
overlayColorTheme = "green",
addVisualEffects = 1
) {
// Ensure image is loaded
if (!originalImg || typeof originalImg.naturalWidth === 'undefined' || !originalImg.complete || originalImg.naturalWidth === 0) {
console.error("Image not loaded, incomplete, or dimensions are zero.");
constผู้ชมCanvas = document.createElement('canvas');
ผู้ชมCanvas.width = 300;ผู้ชมCanvas.height = 150;
constผู้ชมCtx =ผู้ชมCanvas.getContext('2d');
if (ผู้ชมCtx) {
ผู้ชมCtx.fillStyle = '#333';
ผู้ชมCtx.fillRect(0, 0, 300, 150);
ผู้ชมCtx.fillStyle = 'red';
ผู้ชมCtx.font = '16px Arial';
ผู้ชมCtx.textAlign = 'center';
ผู้ชมCtx.textBaseline = 'middle';
ผู้ชมCtx.fillText('Error: Image not loaded.', 150, 75);
}
returnผู้ชมCanvas;
}
// Font loading
const fontName = 'Share Tech Mono';
const fontFamily = `"${fontName}", 'Courier New', Courier, monospace`;
if (typeof document !== 'undefined' && !document.getElementById('google-font-sharetechmono-link')) {
const link = document.createElement('link');
link.id = 'google-font-sharetechmono-link';
link.rel = 'stylesheet';
link.href = `https://fonts.googleapis.com/css2?family=${fontName.replace(' ', '+')}:wght@400&display=swap`;
document.head.appendChild(link);
try {
if (document.fonts && typeof document.fonts.load === 'function') {
await document.fonts.load(`12px "${fontName}"`).catch(e => console.warn(`Font load check failed for ${fontName}:`, e));
} else {
// Fallback for older browsers or non-browser environments: small delay.
await new Promise(resolve => setTimeout(resolve, 300));
}
} catch (e) {
console.warn(`Failed to load font "${fontName}":`, e);
// Font might not load, fallback to generic monospace will occur via CSS font stack
}
}
const canvas = document.createElement('canvas');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
// Parameter sanitization
const cleanUfoXPercent = Number(ufoXPercent) || 50;
const cleanUfoYPercent = Number(ufoYPercent) || 30;
const cleanUfoSizePercent = Math.max(1, Number(ufoSizePercent) || 10);
const cleanAddVisualEffects = Number(addVisualEffects) === 1;
// Parse theme colors
let themeColors;
const colorThemeLower = String(overlayColorTheme).toLowerCase();
switch (colorThemeLower) {
case "green": themeColors = { main: "rgba(100, 255, 100, 0.9)", border: "rgba(80, 200, 80, 0.7)", ufoGlow: "rgba(150, 255, 150, 0.5)" }; break;
case "amber": themeColors = { main: "rgba(255, 170, 0, 0.9)", border: "rgba(200, 130, 0, 0.7)", ufoGlow: "rgba(255, 190, 100, 0.5)" }; break;
case "blue": themeColors = { main: "rgba(100, 180, 255, 0.9)", border: "rgba(80, 150, 200, 0.7)", ufoGlow: "rgba(150, 200, 255, 0.5)" }; break;
case "white": themeColors = { main: "rgba(220, 220, 220, 0.9)", border: "rgba(180, 180, 180, 0.7)", ufoGlow: "rgba(230, 230, 230, 0.5)" }; break;
default: themeColors = { main: overlayColorTheme, border: overlayColorTheme, ufoGlow: "rgba(200,200,255,0.4)"}; // Custom color string
}
// Draw original image
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// UFO Parameters
const ufoX = canvas.width * (cleanUfoXPercent / 100);
const ufoY = canvas.height * (cleanUfoYPercent / 100);
const ufoBaseSize = Math.min(canvas.width, canvas.height) * (cleanUfoSizePercent / 100);
const ufoActualColor = "#666666";
// Draw UFO
ctx.save();
ctx.shadowColor = themeColors.ufoGlow;
ctx.shadowBlur = ufoBaseSize * 0.7;
ctx.fillStyle = ufoActualColor;
ctx.beginPath();
const ufoShapeType = String(ufoType).toLowerCase();
if (ufoShapeType === "disc") {
ctx.ellipse(ufoX, ufoY, ufoBaseSize / 2, ufoBaseSize / 4, 0, 0, 2 * Math.PI);
} else if (ufoShapeType === "cigar") {
ctx.ellipse(ufoX, ufoY, ufoBaseSize * 0.75, ufoBaseSize / 3.5, Math.PI / 12, 0, 2 * Math.PI);
} else if (ufoShapeType === "triangle") {
const side = ufoBaseSize;
const triHeight = side * (Math.sqrt(3)/2);
// Centroid at (ufoX, ufoY) for equilateral triangle
ctx.moveTo(ufoX, ufoY - (2/3) * triHeight); // Top vertex
ctx.lineTo(ufoX - side/2, ufoY + (1/3) * triHeight); // Bottom-left vertex
ctx.lineTo(ufoX + side/2, ufoY + (1/3) * triHeight); // Bottom-right vertex
ctx.closePath();
} else { // Default to sphere / circle
ctx.arc(ufoX, ufoY, ufoBaseSize / 2.5, 0, 2 * Math.PI);
}
ctx.fill();
ctx.restore();
// --- Text and Overlay Elements ---
const baseFontSize = Math.max(10, Math.min(canvas.width / 65, canvas.height / 40));
const padding = baseFontSize * 1.2;
const lineHeight = baseFontSize * 1.3;
// Reticle for UFO
ctx.strokeStyle = themeColors.main;
ctx.lineWidth = Math.max(1, Math.round(canvas.width / 900));
ctx.setLineDash([baseFontSize * 0.3, baseFontSize * 0.3]);
const reticleOuterSize = ufoBaseSize * 1.1; // Size of the cross lines
const reticleBoxSize = ufoBaseSize * 0.9; // Size of the inner box
ctx.beginPath();
ctx.moveTo(ufoX - reticleOuterSize, ufoY); ctx.lineTo(ufoX + reticleOuterSize, ufoY); // Horizontal
ctx.moveTo(ufoX, ufoY - reticleOuterSize); ctx.lineTo(ufoX, ufoY + reticleOuterSize); // Vertical
ctx.stroke();
ctx.strokeRect(ufoX - reticleBoxSize/2, ufoY - reticleBoxSize/2, reticleBoxSize, reticleBoxSize);
ctx.setLineDash([]);
ctx.fillStyle = themeColors.main;
ctx.font = `${Math.round(baseFontSize * 0.8)}px ${fontFamily}`;
ctx.textAlign = "center";
ctx.fillText(`ID:${Math.floor(ufoX)},${Math.floor(ufoY)}`, ufoX, ufoY + reticleOuterSize * 0.7 + baseFontSize);
// Top Header Text
ctx.font = `bold ${Math.round(baseFontSize * 1.1)}px ${fontFamily}`;
ctx.textAlign = "center";
ctx.fillText(String(reportTitle).toUpperCase(), canvas.width / 2, padding * 1.5);
// Info text (timestamp, coords, telemetry)
ctx.font = `${Math.round(baseFontSize * 0.9)}px ${fontFamily}`;
ctx.textAlign = "left";
let currentY_top = padding * 2.8;
const now = new Date();
const timestampStr = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')}T${String(now.getUTCHours()).padStart(2, '0')}:${String(now.getUTCMinutes()).padStart(2, '0')}:${String(now.getUTCSeconds()).padStart(2, '0')}Z`;
ctx.fillText(`LOG_DT: ${timestampStr}`, padding, currentY_top);
currentY_top += lineHeight;
const lat = (Math.random() * 180 - 90).toFixed(4);
const lon = (Math.random() * 360 - 180).toFixed(4);
const alt = (Math.random() * 15000 + 500).toFixed(0);
ctx.fillText(`EST_POS: ${lat}° ${lon}° ${alt}m AGL`, padding, currentY_top);
currentY_top += lineHeight;
const azm = (Math.random() * 360).toFixed(1);
const elv = (Math.random() * 90).toFixed(1);
const sig = (Math.random() * 75 + 25).toFixed(0);
ctx.fillText(`TRACK: AZ ${azm}° EL ${elv}° SIG ${sig}%`, padding, currentY_top);
// Case ID (bottom right)
const caseID = `CASE #${String(Math.floor(Math.random() * 8999) + 1000)}-${String.fromCharCode(65 + Math.floor(Math.random() * 26))}`;
ctx.font = `bold ${Math.round(baseFontSize * 0.9)}px ${fontFamily}`;
ctx.textAlign = "right";
ctx.fillText(caseID, canvas.width - padding, canvas.height - padding);
// Record Label (bottom left)
ctx.textAlign = "left";
ctx.fillText(`REC:${String(Math.floor(Date.now()/1000)).slice(-7)}`, padding, canvas.height - padding);
// Border Elements (Corners)
ctx.strokeStyle = themeColors.border;
ctx.lineWidth = Math.max(1, Math.round(canvas.width / 600));
const cornerSize = baseFontSize * 1.5;
const bPad = padding * 0.5;
// Top-left
ctx.beginPath(); ctx.moveTo(bPad + cornerSize, bPad); ctx.lineTo(bPad, bPad); ctx.lineTo(bPad, bPad + cornerSize); ctx.stroke();
// Top-right
ctx.beginPath(); ctx.moveTo(canvas.width - bPad - cornerSize, bPad); ctx.lineTo(canvas.width - bPad, bPad); ctx.lineTo(canvas.width - bPad, bPad + cornerSize); ctx.stroke();
// Bottom-left
ctx.beginPath(); ctx.moveTo(bPad + cornerSize, canvas.height - bPad); ctx.lineTo(bPad, canvas.height - bPad); ctx.lineTo(bPad, canvas.height - bPad - cornerSize); ctx.stroke();
// Bottom-right
ctx.beginPath(); ctx.moveTo(canvas.width - bPad - cornerSize, canvas.height - bPad); ctx.lineTo(canvas.width - bPad, canvas.height - bPad); ctx.lineTo(canvas.width - bPad, canvas.height - bPad - cornerSize); ctx.stroke();
// Visual Effects (Noise & Scanlines)
if (cleanAddVisualEffects) {
// Noise - apply carefully to ensure text readability
const noiseIntensity = 0.07;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const noiseAmount = Math.floor(255 * noiseIntensity * 0.5); // Halved for less intensity on colors
for (let i = 0; i < data.length; i += 4) {
// Add noise primarily to luminance by applying same random to R,G,B
const rand = (Math.random() - 0.5) * noiseAmount;
data[i] = Math.max(0, Math.min(255, data[i] + rand));
data[i+1] = Math.max(0, Math.min(255, data[i+1] + rand));
data[i+2] = Math.max(0, Math.min(255, data[i+2] + rand));
}
ctx.putImageData(imageData, 0, 0);
// Scanlines
const scanlineOpacity = 0.06;
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity})`;
for (let y = 0; y < canvas.height; y += 3) {
ctx.fillRect(0, y, canvas.width, 1);
}
}
return canvas;
}
Apply Changes