You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
title = "Captain's Log",
entryDate = "17th Day o' Bad Luck, Anno 1742",
textColor = "#3B2F2F", // Dark, slightly desaturated brown for text
titleFontName = "Pirata One", // Pirate-style font
entryFontName = "Georgia", // Standard serif for readability
titleFontSizeFactor = 0.08, // Factor of image height for title font size
entryFontSizeFactor = 0.03, // Factor of image height for entry font size
sepiaAmount = 0.75, // Sepia effect intensity (0 to 1)
vignetteColor = "rgba(30,15,5,1)", // Base color for vignette (e.g., dark warm brown, alpha is ignored here)
vignetteOuterAlpha = 0.8, // Alpha of the vignette color at the extreme edges (0 to 1)
vignetteInnerRadiusFactor = 0.25, // Start of vignette transparency (0=center, 1=edge) relative to image diagonal
vignetteOuterRadiusFactor = 0.9, // End of vignette transparency (0=center, 1=edge) relative to image diagonal
parchmentBaseColor = "#F0E8D8", // Light beige/parchment color for background tint
parchmentImageBlendOpacity = 0.7 // Opacity of the image when drawn over parchment (0 to 1)
) {
// --- Font loading helper ---
async function loadWebFont(fontName, fontUrl) {
// Check if font is already loaded or available in the document's font set
if (document.fonts.check(`12px "${fontName}"`)) {
// console.log(`Font "${fontName}" is already available.`);
return true; // Indicates font is ready or was already loaded
}
try {
const fontFace = new FontFace(fontName, `url(${fontUrl})`);
await fontFace.load(); // Wait for the font to be fetched and ready
document.fonts.add(fontFace); // Add it to the document's font set for use
// console.log(`Font "${fontName}" loaded and added successfully from ${fontUrl}.`);
return true;
} catch (e) {
console.warn(`Font "${fontName}" from URL "${fontUrl}" failed to load. System fallback will be used. Error:`, e);
return false; // Indicates font loading failed
}
}
// --- Load specified web fonts ---
// Example: Load "Pirata One" if specified for the title
if (titleFontName === "Pirata One") {
await loadWebFont("Pirata One", "https://fonts.gstatic.com/s/pirataone/v21/I_urMpiDvgLWTsN3iQCXh_hU510_I8c.woff2");
}
// Add similar blocks here if entryFontName or other fonts are web fonts needing dynamic loading.
// --- Canvas and Context Setup ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
// Handle invalid image dimensions (e.g., image not loaded yet)
if (!imgWidth || !imgHeight) {
console.error("Image dimensions are invalid (e.g., 0x0). Make sure the image is fully loaded before processing.");
// Return a small canvas with an error message
canvas.width = 250;
canvas.height = 100;
ctx.fillStyle = "#FDD"; // Light red background for error
ctx.fillRect(0,0,canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Error: Invalid image dimensions.", canvas.width/2, canvas.height/2 -10);
ctx.fillText("Please ensure the image is loaded.", canvas.width/2, canvas.height/2 + 10);
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
// --- 1. Draw Parchment Background Tint ---
// If a parchment color is specified, fill the canvas with it first.
if (parchmentBaseColor) {
ctx.fillStyle = parchmentBaseColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// --- 2. Draw the Original Image ---
// Adjust image opacity if blending with parchment background.
const finalImageOpacity = parchmentBaseColor ? Math.max(0, Math.min(1, parchmentImageBlendOpacity)) : 1.0;
if (finalImageOpacity < 1.0) {
ctx.globalAlpha = finalImageOpacity;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
if (finalImageOpacity < 1.0) {
ctx.globalAlpha = 1.0; // Reset globalAlpha for subsequent operations
}
// --- 3. Apply Sepia Filter ---
if (sepiaAmount > 0) {
const sAmount = Math.max(0, Math.min(1, sepiaAmount)); // Clamp sepiaAmount to [0, 1]
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard sepia weights
const sr = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
const sg = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
const sb = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
// Blend original with sepia based on sAmount
data[i] = Math.round((1 - sAmount) * r + sAmount * sr);
data[i+1] = Math.round((1 - sAmount) * g + sAmount * sg);
data[i+2] = Math.round((1 - sAmount) * b + sAmount * sb);
}
ctx.putImageData(imageData, 0, 0);
}
// --- 4. Apply Vignette Effect ---
const vOuterAlpha = Math.max(0, Math.min(1, vignetteOuterAlpha));
const vInnerRFactor = Math.max(0, Math.min(1, vignetteInnerRadiusFactor));
// Ensure outer radius factor is not less than inner, and clamp to [0,1]
const vOuterRFactor = Math.max(vInnerRFactor, Math.min(1, vignetteOuterRadiusFactor));
if (vOuterAlpha > 0 && vOuterRFactor > vInnerRFactor && vignetteColor) {
ctx.save();
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Calculate radius to a corner of the canvas, for vignette scaling
const maxCanvasRadius = Math.hypot(centerX, centerY);
const r0_pixels = maxCanvasRadius * vInnerRFactor; // Inner radius of gradient (fully transparent)
const r1_pixels = maxCanvasRadius * vOuterRFactor; // Outer radius of gradient (reaches vOuterAlpha)
const gradient = ctx.createRadialGradient(centerX, centerY, r0_pixels, centerX, centerY, r1_pixels);
// Parse the vignetteColor to get RGB components
let baseR = 0, baseG = 0, baseB = 0;
const colorMatch = vignetteColor.match(/rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:,\s*[\d.]+)?\)/);
if (colorMatch) {
baseR = parseInt(colorMatch[1]);
baseG = parseInt(colorMatch[2]);
baseB = parseInt(colorMatch[3]);
} else {
console.warn(`Could not parse vignetteColor "${vignetteColor}". Vignette may not appear as intended.`);
// Default to black components if parsing fails, alpha is handled by vOuterAlpha
}
gradient.addColorStop(0, `rgba(${baseR},${baseG},${baseB},0)`); // Center of vignette is transparent
gradient.addColorStop(1, `rgba(${baseR},${baseG},${baseB},${vOuterAlpha})`); // Edge of vignette uses vOuterAlpha
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height); // Apply gradient over the entire canvas
ctx.restore();
}
// --- 5. Draw Text Elements (Title and Entry Date) ---
// Calculate font sizes and margin based on image height, with minimums
const actualTitleFontSize = Math.max(10, Math.round(canvas.height * Math.max(0, titleFontSizeFactor)));
const actualEntryFontSize = Math.max(8, Math.round(canvas.height * Math.max(0, entryFontSizeFactor)));
const margin = Math.max(5, Math.round(canvas.height * 0.04)); // 4% margin from edges, min 5px
ctx.fillStyle = textColor;
ctx.textBaseline = 'top'; // Align text from its top edge for easier Y positioning
// Draw Title
if (title && actualTitleFontSize > 0) {
ctx.font = `${actualTitleFontSize}px "${titleFontName}", serif`; // Fallback to generic serif
ctx.textAlign = 'center';
ctx.fillText(title, canvas.width / 2, margin);
}
// Draw Entry Date
if (entryDate && actualEntryFontSize > 0) {
ctx.font = `${actualEntryFontSize}px "${entryFontName}", serif`; // Fallback to generic serif
ctx.textAlign = 'right';
// Position from bottom-right corner, considering font height for Y
ctx.fillText(entryDate, canvas.width - margin, canvas.height - margin - actualEntryFontSize);
}
return canvas;
}
Apply Changes