You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
filterType = "christmas", // "christmas", "newYear", "winter", "party"
overlayDensity = 0.3, // 0 (none) to 1 (dense)
greetingText = "",
greetingColor = "#FFFFFF",
greetingFontFamily = "Arial", // Default to a web-safe font. Examples for festive: "Mountains of Christmas", "Pacifico", "Lobster"
greetingFontSizeFactor = 0.08, // Factor of image height
vignetteStrength = 0.3 // 0 (none) to 1 (strong)
) {
// --- Helper Functions ---
function hexToRgba(hex, alpha) {
if (typeof hex !== 'string' || !hex || hex.charAt(0) !== '#') {
// console.warn("Invalid hex color for hexToRgba:", hex, "- using black as default.");
hex = '#000000'; // Default to black if hex is invalid
}
const r = parseInt(hex.slice(1, 3), 16) || 0;
const g = parseInt(hex.slice(3, 5), 16) || 0;
const b = parseInt(hex.slice(5, 7), 16) || 0;
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function drawSimpleSnowflake(ctx, x, y, size, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = Math.max(1, size / 7); // Adjust line width based on size
const numSpokes = 6;
for (let i = 0; i < numSpokes; i++) {
const angle = (Math.PI * 2 / numSpokes) * i;
// Main spoke
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size);
// Add "V" branches at points along the spoke (e.g. 60% and 85% of length)
for (const R of [0.6, 0.85]) { // Relative positions on spoke for branches
const branchSourceX = x + Math.cos(angle) * size * R;
const branchSourceY = y + Math.sin(angle) * size * R;
const branchLength = size * 0.20; // Length of each arm of the "V"
const branchAngleOffset = Math.PI / 4; // Angle of arms relative to the spoke (45 degrees)
// Branch 1 of V (points outwards)
ctx.moveTo(branchSourceX, branchSourceY);
ctx.lineTo(branchSourceX + Math.cos(angle + branchAngleOffset) * branchLength,
branchSourceY + Math.sin(angle + branchAngleOffset) * branchLength);
// Branch 2 of V (points outwards)
ctx.moveTo(branchSourceX, branchSourceY);
ctx.lineTo(branchSourceX + Math.cos(angle - branchAngleOffset) * branchLength,
branchSourceY + Math.sin(angle - branchAngleOffset) * branchLength);
}
}
ctx.stroke();
}
function drawSparkle(ctx, x, y, size, baseColorHex) {
ctx.beginPath();
const gradient = ctx.createRadialGradient(x, y, 0, x, y, size);
gradient.addColorStop(0, hexToRgba(baseColorHex, 0.9));
gradient.addColorStop(0.5, hexToRgba(baseColorHex, 0.7));
gradient.addColorStop(1, hexToRgba(baseColorHex, 0)); // Fading edge
ctx.fillStyle = gradient;
ctx.arc(x, y, size, 0, 2 * Math.PI);
ctx.fill();
// Smaller, whiter core for extra "pop"
const coreSize = size * Math.max(0.15, Math.random() * 0.35);
ctx.beginPath();
const coreGradient = ctx.createRadialGradient(x, y, 0, x, y, coreSize);
coreGradient.addColorStop(0, hexToRgba("#FFFFFF", 0.95));
coreGradient.addColorStop(1, hexToRgba(baseColorHex, 0.3));
ctx.fillStyle = coreGradient;
ctx.arc(x, y, coreSize, 0, 2 * Math.PI);
ctx.fill();
}
function drawConfetti(ctx, x, y, size, colorHex) {
ctx.fillStyle = colorHex; // Full opacity for confetti
ctx.save();
ctx.translate(x, y);
ctx.rotate(Math.random() * Math.PI * 2); // Random rotation
const aspect = 0.4 + Math.random() * 0.6; // from thin rectangle to almost square
const confettoWidth = size * (0.7 + Math.random() * 0.6); // Vary width
const confettoHeight = confettoWidth * aspect; // Vary height based on aspect
ctx.fillRect(-confettoWidth / 2, -confettoHeight / 2, confettoWidth, confettoHeight);
ctx.restore();
}
const googleFontMap = {
"Mountains of Christmas": "url(https://fonts.gstatic.com/s/mountainsofchristmas/v19/3y9dx4hYx1gqIVMwdLsUSURA0upUmKcnVoH2cHXhfMQ.woff2)",
"Pacifico": "url(https://fonts.gstatic.com/s/pacifico/v22/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2)",
"Dancing Script": "url(https://fonts.gstatic.com/s/dancingscript/v24/If2cXTr6YS-zF4S-kcSWSVi_sxjsohD9F50Ruu7BMSo3Sup8.woff2)",
"Lobster": "url(https://fonts.gstatic.com/s/lobster/v28/neILzCirqoswsqX9zo-mM5Ez.woff2)",
};
// --- Main Logic ---
const canvas = document.createElement('canvas');
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Canvas 2D context is not available.");
// Create a fallback element if canvas is not supported
const p = document.createElement('p');
p.textContent = "Canvas not supported. Please use a modern browser.";
return p;
}
// 1. Apply base image filter and draw image
let imageFilterStyle = 'none';
switch (filterType) {
case "christmas":
imageFilterStyle = 'saturate(1.1) sepia(0.08) brightness(1.02)';
break;
case "newYear":
imageFilterStyle = 'saturate(1.2) contrast(1.05) brightness(1.03)';
break;
case "winter":
imageFilterStyle = 'brightness(1.05) contrast(1.05)';
break;
case "party":
imageFilterStyle = 'saturate(1.15) brightness(1.05) contrast(1.03)';
break;
}
if (imageFilterStyle !== 'none' && typeof ctx.filter !== 'undefined') {
ctx.filter = imageFilterStyle;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
if (typeof ctx.filter !== 'undefined') {
ctx.filter = 'none'; // Reset filter for subsequent drawings
}
// 2. Apply specific color overlays (e.g., cool tint for Winter)
if (filterType === "winter") {
ctx.globalCompositeOperation = 'overlay'; // 'soft-light' or 'multiply' could also work
ctx.fillStyle = 'rgba(200, 220, 255, 0.07)'; // Very subtle cool blue
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
}
// 3. Draw overlay particles
if (overlayDensity > 0) {
// Adjust particle count based on image area and density.
// Denser for winter (more snowflakes), less for others.
const baseParticleAreaDivisor = filterType === 'winter' ? 15000 : 25000;
const numParticles = Math.floor((canvas.width * canvas.height / baseParticleAreaDivisor) * overlayDensity * 10);
// Base particle size scales with average image dimension. min size 3px.
const baseParticleSize = Math.max(3, (canvas.width + canvas.height) / 300);
const confettiColors = ["#FF6B6B", "#FFD93D", "#6BCB77", "#4D96FF", "#F7C8E0", "#B0E0E6", "#FFC0CB"];
for (let i = 0; i < numParticles; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const particleSize = baseParticleSize * (0.6 + Math.random() * 0.8); // Randomize size a bit
switch (filterType) {
case "christmas":
if (Math.random() < 0.8) { // 80% snowflakes
drawSimpleSnowflake(ctx, x, y, particleSize * 1.2, `rgba(255, 255, 255, ${0.7 + Math.random()*0.25})`);
} else { // 20% red/green sparkles
const sparkleColor = Math.random() < 0.5 ? "#E74C3C" : "#2ECC71"; // Festive red/green
drawSparkle(ctx, x, y, particleSize * 0.7, sparkleColor);
}
break;
case "newYear":
if (Math.random() < 0.6) { // 60% confetti
drawConfetti(ctx, x, y, particleSize, confettiColors[Math.floor(Math.random() * confettiColors.length)]);
} else { // 40% gold/silver sparkles
const sparkleColor = Math.random() < 0.6 ? "#FFD700" : "#C0C0C0"; // Gold/Silver
drawSparkle(ctx, x, y, particleSize * 0.8, sparkleColor);
}
break;
case "winter":
drawSimpleSnowflake(ctx, x, y, particleSize * 1.3, `rgba(255, 255, 255, ${0.55 + Math.random()*0.35})`); // Vary opacity
break;
case "party":
if (Math.random() < 0.7) { // 70% confetti
drawConfetti(ctx, x, y, particleSize, confettiColors[Math.floor(Math.random() * confettiColors.length)]);
} else { // 30% multi-color sparkles
drawSparkle(ctx, x, y, particleSize * 0.7, confettiColors[Math.floor(Math.random() * confettiColors.length)]);
}
break;
default: // Generic white sparkles for unknown/default filter types
drawSparkle(ctx, x, y, particleSize * 0.6, "#FFFFFF");
break;
}
}
}
// 4. Apply vignette
if (vignetteStrength > 0) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Inner radius of vignette area (clear area)
const innerRadiusClearFactor = 1 - Math.min(0.95, vignetteStrength); // Smaller clear area for stronger vignette
const innerRadius = Math.min(canvas.width, canvas.height) / 1.8 * innerRadiusClearFactor;
const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY); // Extends to corners
const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
gradient.addColorStop(0, `rgba(0,0,0,0)`); // Center is transparent
gradient.addColorStop(1, `rgba(0,0,0, ${Math.min(1, vignetteStrength * 1.2)})`); // Edge is dark, cap alpha
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 5. Load font and draw greeting text
if (greetingText && greetingText.trim() !== "") {
const selectedFontFamily = greetingFontFamily || "Arial";
const fontUrl = googleFontMap[selectedFontFamily];
if (fontUrl) {
// Check if font needs loading. Using 10px as common test size.
if (!document.fonts.check(`10px "${selectedFontFamily}"`)) {
try {
const font = new FontFace(selectedFontFamily, fontUrl);
await font.load();
document.fonts.add(font);
} catch (e) {
console.warn(`Failed to load Google Font "${selectedFontFamily}":`, e);
// Fallback (Arial, sans-serif below) will apply.
}
}
}
// If fontUrl is null, it's assumed to be a system/web-safe font.
const actualFontSize = Math.max(16, Math.floor(canvas.height * greetingFontSizeFactor)); // Min 16px font
ctx.font = `${actualFontSize}px "${selectedFontFamily}", Arial, sans-serif`; // CSS font string with fallbacks
ctx.fillStyle = greetingColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom'; // Align text bottom to y-coordinate
// Add shadow for better readability
ctx.shadowColor = 'rgba(0,0,0,0.65)';
ctx.shadowBlur = Math.max(2, actualFontSize * 0.07);
ctx.shadowOffsetX = Math.max(1, actualFontSize * 0.03);
ctx.shadowOffsetY = Math.max(1, actualFontSize * 0.03);
// Position text near bottom center. Adjust vertical position slightly.
const textMarginBottom = Math.max(10, canvas.height * 0.05); // 5% margin from bottom or 10px
const textY = canvas.height - textMarginBottom;
ctx.fillText(greetingText, canvas.width / 2, textY);
// Reset shadow properties
ctx.shadowColor = 'transparent'; // Or `rgba(0,0,0,0)`
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
return canvas;
}
Apply Changes