You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
posterText = "BEWARE",
textColor = "rgb(200, 0, 0)", // A strong red
tintColor = "rgba(80, 0, 0, 0.5)", // Dark red semi-transparent overlay
fontFamily = "Creepster, cursive", // Evil-themed Google Font with fallback
fontSizeRatio = 0.18, // Relative to image height
baseFilter = 'grayscale(50%) contrast(150%) brightness(90%) sepia(30%)', // Initial image treatment
vignetteColor = "rgba(0,0,0,0.7)", // Dark vignette at edges
textShadowColor = "black",
textShadowBlur = 8, // Set to 0 to disable shadow
textShadowOffsetX = 3,
textShadowOffsetY = 3
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas dimensions from image, with fallbacks
canvas.width = originalImg.naturalWidth || originalImg.width || 300;
canvas.height = originalImg.naturalHeight || originalImg.height || 150;
// Ensure dimensions are positive; otherwise, return a minimal canvas
if (canvas.width <= 0 || canvas.height <= 0) {
console.warn("Image has invalid dimensions. Returning basic canvas.");
canvas.width = Math.max(1, canvas.width || 100); // Ensure positive
canvas.height = Math.max(1, canvas.height || 100); // Ensure positive
ctx.fillStyle = 'gray'; // Indicate an issue or empty state
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (posterText) {
ctx.fillStyle = 'white';
ctx.font = '10px sans-serif';
ctx.textAlign = 'center';
ctx.fillText("Error: Invalid Image", canvas.width/2, canvas.height/2);
}
return canvas;
}
const H_FONT_FAMILY_TO_USE = fontFamily;
// Extract the primary font name (e.g., "Creepster" from "Creepster, cursive")
const primaryFontName = fontFamily.split(',')[0].replace(/["']/g, '').trim();
// Dynamically load "Creepster" font if specified and not already available in a browser environment
if (primaryFontName === "Creepster" && typeof document !== 'undefined' && document.head && document.fonts) {
// Check if font is already loaded/available
if (!document.fonts.check(`1em "${primaryFontName}"`)) {
const link = document.createElement('link');
link.href = `https://fonts.googleapis.com/css2?family=${primaryFontName.replace(/\s/g, '+')}&display=swap`;
link.rel = 'stylesheet';
// Create a promise to await font loading
const fontLoadPromise = new Promise((resolve, reject) => {
link.onload = () => {
// Stylesheet loaded, now attempt to load the font definition
document.fonts.load(`1em "${primaryFontName}"`)
.then(resolve) // Font face ready
.catch(reject); // Font face loading failed
};
link.onerror = reject; // CSS link loading failed
});
document.head.appendChild(link);
try {
await fontLoadPromise;
// console.log(`Font "${primaryFontName}" loaded successfully.`);
} catch (e) {
console.warn(`Failed to load font "${primaryFontName}". Fallback font will be used. Error:`, e);
// Browser will use fallback font (e.g., "cursive") as specified in H_FONT_FAMILY_TO_USE
}
}
}
// --- Image Processing ---
// 1. Draw original image with base filters
// Ensure baseFilter is a non-empty string before applying
if (baseFilter && baseFilter.trim() !== "" && baseFilter.trim().toLowerCase() !== "none") {
ctx.filter = baseFilter;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
ctx.filter = 'none'; // Reset filter for subsequent drawing operations
// 2. Apply the "evil" tint overlay
// Ensure tintColor is a non-empty string and not transparent before applying
if (tintColor && tintColor.trim() !== "" && tintColor.toLowerCase() !== "transparent" && tintColor.toLowerCase() !== "none") {
ctx.fillStyle = tintColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 3. Add vignette effect
// Ensure vignetteColor is a non-empty string and not transparent before applying
if (vignetteColor && vignetteColor.trim() !== "" && vignetteColor.toLowerCase() !== "transparent" && vignetteColor.toLowerCase() !== "none") {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Make gradient radii relative to min dimension for a somewhat circular inner transparent area,
// and max dimension for outer edge to cover corners well.
const innerRadius = Math.min(canvas.width, canvas.height) * 0.2; // Smaller transparent center
const outerRadius = Math.max(canvas.width, canvas.height) * 0.85; // Ensure vignette reaches far edges
const gradient = ctx.createRadialGradient(
centerX, centerY, innerRadius,
centerX, centerY, outerRadius
);
let centerVignetteColor = 'rgba(0,0,0,0)'; // Default fully transparent black center
// Try to make the center of the vignette match the hue & saturation of vignetteColor, but fully transparent
if (vignetteColor.startsWith('rgba')) { // e.g. rgba(80,0,0,0.7) -> rgba(80,0,0,0)
centerVignetteColor = vignetteColor.replace(/,( ?[\d.]+ ?)\)$/, ',0)'); // Replace alpha component with 0
} else if (vignetteColor.startsWith('rgb')) { // e.g. rgb(80,0,0) -> rgba(80,0,0,0)
centerVignetteColor = vignetteColor.replace('rgb', 'rgba').replace(')', ',0)');
}
// For hex or named colors, the default 'rgba(0,0,0,0)' for center works well.
gradient.addColorStop(0, centerVignetteColor);
gradient.addColorStop(1, vignetteColor);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// --- Text Rendering ---
if (posterText && posterText.trim() !== "") {
const fontSize = Math.max(10, Math.floor(canvas.height * fontSizeRatio)); // Minimum font size 10px
ctx.font = `${fontSize}px ${H_FONT_FAMILY_TO_USE}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'alphabetic'; // A common baseline for text rendering
const textX = canvas.width / 2;
// Position text Y: Considering padding from bottom.
// 'alphabetic' baseline is where most characters sit. Padding of 0.5 * fontSize from bottom edge.
const textY = canvas.height - (fontSize * 0.5);
// Apply shadow for "evil" text effect
if (textShadowBlur > 0 || textShadowOffsetX !== 0 || textShadowOffsetY !== 0) {
ctx.shadowColor = textShadowColor;
ctx.shadowBlur = textShadowBlur;
ctx.shadowOffsetX = textShadowOffsetX;
ctx.shadowOffsetY = textShadowOffsetY;
}
ctx.fillStyle = textColor;
ctx.fillText(posterText, textX, textY);
// Reset shadow settings for any subsequent drawing operations (good practice)
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
return canvas;
}
Apply Changes