You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
frameColor = "#FEFEFE",
imagePaddingParam = "15",
bottomBarHeightParam = "70",
captionText = "",
captionFont = "24px 'Caveat', cursive",
captionColor = "#333333",
shadowOpacityParam = "0.3",
shadowBlurParam = "10",
shadowOffsetXParam = "2",
shadowOffsetYParam = "4"
) {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
const errorCanvasPlaceholder = (width = 200, height = 100, message = "Error.") => {
canvas.width = width;
canvas.height = height;
if (ctx) {
ctx.fillStyle = 'lightcoral';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(message, canvas.width / 2, canvas.height / 2, canvas.width - 20);
}
return canvas;
};
if (!originalImg) {
console.error("Original image is null or undefined.");
return errorCanvasPlaceholder(200, 100, "Original image not provided.");
}
if (!ctx) {
console.error("Could not get 2D context from canvas.");
const p = document.createElement('p');
p.textContent = 'Canvas Context2D not supported. Cannot process image.';
return p; // Fallback to a paragraph element if canvas context fails
}
const imagePadding = Number(imagePaddingParam);
const bottomBarHeight = Number(bottomBarHeightParam);
const shadowOpacity = Number(shadowOpacityParam);
const shadowBlur = Number(shadowBlurParam);
const shadowOffsetX = Number(shadowOffsetXParam);
const shadowOffsetY = Number(shadowOffsetYParam);
const numParams = [imagePadding, bottomBarHeight, shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY];
if (numParams.some(isNaN)) {
console.error("Invalid numerical parameter provided. Check values for padding, height, or shadow properties.");
return errorCanvasPlaceholder(originalImg.naturalWidth || 200, (originalImg.naturalHeight || 100) + 50, "Invalid numeric param for Polaroid.");
}
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (!originalImg.complete || imgWidth === 0 || imgHeight === 0) {
console.error("Image is not fully loaded, or its dimensions are zero.");
return errorCanvasPlaceholder(200, 100, "Image not loaded or has zero dimensions.");
}
// Font loading for caption
let finalCaptionFont = captionFont;
if (captionText && captionText.trim() !== "") {
let fontFamily = "sans-serif"; // Default fallback
const fontParts = captionFont.match(/^\s*(?:(?:(italic|oblique|normal)\s+)?(?:(bold|bolder|lighter|[1-9]00)\s+)?([\d\.]+px(?:\/[\d\.]+px)?)\s+)?(?:'([^']+)'|"([^"]+)"|([a-zA-Z\s-]+))(?:\s*,.*)?$/i);
if (fontParts) {
fontFamily = (fontParts[4] || fontParts[5] || fontParts[6] || "sans-serif").trim();
} else {
// Fallback for very simple font strings like "Arial"
const simpleMatch = captionFont.split(',')[0].trim().match(/[^0-9\s"'][a-zA-Z\s-]+/);
if (simpleMatch) fontFamily = simpleMatch[0].trim();
}
const webSafeFonts = ["arial", "verdana", "helvetica", "tahoma", "trebuchet ms", "times new roman", "georgia", "garamond", "courier new", "brush script mt", "cursive", "sans-serif", "serif", "monospace", "fantasy", "system-ui"];
if (!webSafeFonts.includes(fontFamily.toLowerCase()) && !document.fonts.check(`12px "${fontFamily}"`)) {
if (fontFamily.toLowerCase() === 'caveat') { // Specific handling for 'Caveat' font
const GFONT_FAMILY_PARAM = encodeURIComponent(fontFamily).replace(/%20/g, '+');
const GFONT_URL = `https://fonts.googleapis.com/css2?family=${GFONT_FAMILY_PARAM}&display=swap`;
if (!document.querySelector(`link[href="${GFONT_URL}"]`)) {
const link = document.createElement('link');
link.href = GFONT_URL;
link.rel = 'stylesheet';
document.head.appendChild(link);
await new Promise((resolve) => { // Wait for CSS to load
link.onload = resolve;
link.onerror = () => { console.warn(`Failed to load stylesheet for ${fontFamily}.`); resolve(); };
});
}
}
// Attempt to load the font for canvas regardless of specific handling
try {
await document.fonts.load(`12px "${fontFamily}"`);
} catch (e) {
console.warn(`Font "${fontFamily}" could not be loaded by document.fonts.load. Attempting to use fallback. Error:`, e);
// Modify finalCaptionFont to use a fallback
const currentFontFamilyRegex = new RegExp(`(['"]?)${fontFamily.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\1`, 'gi');
finalCaptionFont = captionFont.replace(currentFontFamilyRegex, "sans-serif");
if (!finalCaptionFont.toLowerCase().includes("sans-serif")) {
finalCaptionFont = captionFont.split(",")[0].trim() + ", sans-serif"; // More robust fallback
}
}
}
}
canvas.width = imgWidth + 2 * imagePadding;
canvas.height = imgHeight + imagePadding + bottomBarHeight;
// 1. Draw the frame background
ctx.fillStyle = frameColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Set up shadow for the image part
if (shadowOpacity > 0 && shadowBlur > 0) {
ctx.shadowColor = `rgba(0, 0, 0, ${shadowOpacity})`;
ctx.shadowBlur = shadowBlur;
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
}
// 3. Draw the image
ctx.drawImage(originalImg, imagePadding, imagePadding, imgWidth, imgHeight);
// 4. Clear shadow properties (so they don't affect subsequent drawings like text)
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// 5. Draw caption text
if (captionText && captionText.trim() !== "") {
ctx.fillStyle = captionColor;
ctx.font = finalCaptionFont; // Use the (potentially modified) font string
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const textY = (imagePadding + imgHeight) + (bottomBarHeight / 2);
// Provide a maximum width for the text, slightly less than the available bottom bar width
const maxTextWidth = canvas.width - imagePadding; // Simple max width
ctx.fillText(captionText, canvas.width / 2, textY, maxTextWidth);
}
return canvas;
}
Apply Changes