You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
caseId = "CASE-001X",
location = "CLASSIFIED",
customTimestamp = "", // If empty, uses current time. Provide string like "2023-10-27 14:35:00"
effectLevel = 3, // 0-10 (0: no noise, 10: max noise)
monochrome = 1, // 1 for grayscale, 0 for color
scanLines = 1, // 1 to add scan lines, 0 for no scan lines
headerText = "PARANORMAL EVIDENCE",
footerText = "FILE: RESTRICTED",
fontFamily = "Share Tech Mono", // "Share Tech Mono" (loads from Google Fonts), "monospace", "Courier New", etc.
infoTextColor = "#33FF33", // Light green for info text (timestamp, case ID, location)
headerFooterBgColor = "#111111", // Dark gray/black for header and footer backgrounds
headerTextColor = "#FF0000", // Red for header text
footerTextColor = "#FF0000" // Red for footer banner text
) {
async function _ensureFontLoaded(fontFamilyName, cssUrl) {
const fontId = `font-link-${fontFamilyName.replace(/\s+/g, '-')}`;
// Check if font is already usable
if (document.fonts.check(`12px "${fontFamilyName}"`)) {
return true;
}
// If link tag for CSS is not present, add it
if (!document.getElementById(fontId)) {
const link = document.createElement('link');
link.id = fontId;
link.href = cssUrl;
link.rel = 'stylesheet';
const p = new Promise((resolve, reject) => {
link.onload = () => resolve(true); // CSS loaded
link.onerror = () => reject(new Error(`Failed to load CSS: ${cssUrl}`));
});
document.head.appendChild(link);
try {
await p; // Wait for CSS to download
} catch(err) {
console.warn(err.message);
return false; // CSS failed to load
}
}
// CSS is linked (or was already present), now wait for the specific font to be usable
try {
await document.fonts.load(`12px "${fontFamilyName}"`);
return document.fonts.check(`12px "${fontFamilyName}"`); // Final check
} catch (e) {
console.warn(`Failed to load font "${fontFamilyName}" after linking CSS. It might be loading or fallback will be used.`, e);
return false; // Font failed to load or make itself available via API in time
}
}
let effectiveFontFamily = fontFamily;
if (fontFamily.toLowerCase() === "share tech mono") {
const fontLoaded = await _ensureFontLoaded("Share Tech Mono", "https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap");
if (!fontLoaded) {
effectiveFontFamily = "monospace"; // Fallback if "Share Tech Mono" fails
console.warn("Fell back to monospace font.");
} else {
effectiveFontFamily = "Share Tech Mono"; // Use the canonical name
}
}
// 1. Create fxCanvas for image effects
const fxCanvas = document.createElement('canvas');
fxCanvas.width = originalImg.width;
fxCanvas.height = originalImg.height;
const fxCtx = fxCanvas.getContext('2d');
// Draw original image
fxCtx.drawImage(originalImg, 0, 0, originalImg.width, originalImg.height);
// Apply effects
if (monochrome === 1 || effectLevel > 0) {
const imageData = fxCtx.getImageData(0, 0, fxCanvas.width, fxCanvas.height);
const data = imageData.data;
const noiseStrength = effectLevel * 5; // Max noise +-50 per channel for level 10
for (let i = 0; i < data.length; i += 4) {
// Grayscale
if (monochrome === 1) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // Red
data[i + 1] = avg; // Green
data[i + 2] = avg; // Blue
}
// Noise
if (effectLevel > 0) {
const noise = (Math.random() - 0.5) * 2 * noiseStrength;
data[i] = Math.max(0, Math.min(255, data[i] + noise));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + noise));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + noise));
}
}
fxCtx.putImageData(imageData, 0, 0);
}
// Scan lines
if (scanLines === 1) {
fxCtx.fillStyle = "rgba(0, 0, 0, 0.15)";
for (let y = 0; y < fxCanvas.height; y += 3) { // 1px line every 3px
fxCtx.fillRect(0, y, fxCanvas.width, 1);
}
}
// 2. Create outputCanvas for final composition
const headerHeight = 40;
const footerHeight = 80; // Allows for 3 lines of info + footer banner
const outputCanvas = document.createElement('canvas');
outputCanvas.width = originalImg.width;
outputCanvas.height = headerHeight + originalImg.height + footerHeight;
const outputCtx = outputCanvas.getContext('2d');
// Fill entire canvas (useful if image is transparent or smaller areas for some reason)
outputCtx.fillStyle = headerFooterBgColor; // Default background for whole thing
outputCtx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
// Draw Header
outputCtx.fillStyle = headerFooterBgColor;
outputCtx.fillRect(0, 0, outputCanvas.width, headerHeight);
const headerFontSize = Math.max(10, Math.min(headerHeight * 0.5, outputCanvas.width / (headerText.length * 0.60 + 2))); // Dynamic font size
outputCtx.font = `bold ${headerFontSize}px ${effectiveFontFamily}`;
outputCtx.fillStyle = headerTextColor;
outputCtx.textAlign = "center";
outputCtx.textBaseline = "middle";
outputCtx.fillText(headerText, outputCanvas.width / 2, headerHeight / 2);
// Draw processed image
outputCtx.drawImage(fxCanvas, 0, headerHeight);
// Draw Footer
const footerYStart = headerHeight + originalImg.height;
outputCtx.fillStyle = headerFooterBgColor;
outputCtx.fillRect(0, footerYStart, outputCanvas.width, footerHeight);
// Info text (timestamp, case ID, location)
const infoFontSize = Math.max(8, Math.min(14, outputCanvas.width / 30)); // Small dynamic font size for info
const infoLineHeight = infoFontSize * 1.3;
const infoPaddingTop = footerHeight * 0.15;
let currentTextY = footerYStart + infoPaddingTop;
outputCtx.font = `${infoFontSize}px ${effectiveFontFamily}`;
outputCtx.fillStyle = infoTextColor;
outputCtx.textAlign = "left";
outputCtx.textBaseline = "top";
let timestampStr = customTimestamp;
if (!customTimestamp) {
const now = new Date();
timestampStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
}
outputCtx.fillText(`TIME: ${timestampStr}`, 10, currentTextY);
currentTextY += infoLineHeight;
outputCtx.fillText(`CASE: ${caseId}`, 10, currentTextY);
currentTextY += infoLineHeight;
outputCtx.fillText(`LOC: ${location}`, 10, currentTextY);
// Footer banner text
const footerBannerFontSize = Math.max(10, Math.min(footerHeight * 0.25, outputCanvas.width / (footerText.length * 0.60 + 2))); // Dynamic font size
outputCtx.font = `bold ${footerBannerFontSize}px ${effectiveFontFamily}`;
outputCtx.fillStyle = footerTextColor;
outputCtx.textAlign = "center";
outputCtx.textBaseline = "bottom";
outputCtx.fillText(footerText, outputCanvas.width / 2, outputCanvas.height - (footerHeight * 0.1));
return outputCanvas;
}
Apply Changes