You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, temperature = '20°C', weatherCondition = 'sunny', location = 'New York') {
/**
* Dynamically loads a font from Google Fonts.
* @param {string} family The font family name.
* @param {string} url The URL to the font file.
* @returns {Promise<void>} A promise that resolves when the font is loaded.
*/
const loadFont = async (family, url) => {
const fontFace = new FontFace(family, `url(${url})`, {
style: 'normal',
weight: '400'
});
try {
await fontFace.load();
document.fonts.add(fontFace);
} catch (e) {
console.error(`Font ${family} could not be loaded:`, e);
// The browser will use the fallback font specified in the 'font' property.
}
};
/**
* Draws a sunny lens flare/glow effect.
* @param {CanvasRenderingContext2D} ctx The canvas context.
* @param {number} width The canvas width.
* @param {number} height The canvas height.
*/
const drawSunnyEffect = (ctx, width, height) => {
const gradient = ctx.createRadialGradient(width, 0, 0, width, 0, width * 0.8);
gradient.addColorStop(0, 'rgba(255, 255, 0, 0.35)');
gradient.addColorStop(1, 'rgba(255, 255, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
};
/**
* Draws a rain effect.
* @param {CanvasRenderingContext2D} ctx The canvas context.
* @param {number} width The canvas width.
* @param {number} height The canvas height.
*/
const drawRainEffect = (ctx, width, height) => {
ctx.strokeStyle = 'rgba(174, 194, 224, 0.8)';
ctx.lineWidth = 1.5;
ctx.lineCap = 'round';
const density = Math.min(500, (width * height) / 2000);
for (let i = 0; i < density; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const length = Math.random() * 20 + 10;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x - 5, y + length);
ctx.stroke();
}
};
/**
* Draws a snow effect.
* @param {CanvasRenderingContext2D} ctx The canvas context.
* @param {number} width The canvas width.
* @param {number} height The canvas height.
*/
const drawSnowEffect = (ctx, width, height) => {
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
const density = Math.min(400, (width * height) / 2500);
for (let i = 0; i < density; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const radius = Math.random() * 3 + 1;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
};
/**
* Draws a specific weather icon.
* @param {CanvasRenderingContext2D} ctx The canvas context.
* @param {string} condition The weather condition string.
* @param {number} x The center x-coordinate for the icon.
* @param {number} y The center y-coordinate for the icon.
* @param {number} size The approximate size of the icon.
*/
const drawWeatherIcon = (ctx, condition, x, y, size) => {
ctx.save();
ctx.translate(x, y);
ctx.lineWidth = size * 0.08;
ctx.lineCap = 'round';
// Helper function to draw a cloud
const drawCloud = () => {
ctx.fillStyle = 'white';
ctx.strokeStyle = '#f0f0f0';
ctx.beginPath();
// Base
ctx.moveTo(-size * 0.3, size * 0.15);
ctx.lineTo(size * 0.3, size * 0.15);
// Bumps
ctx.arc(-size * 0.18, size * 0.05, size * 0.18, Math.PI * 0.55, Math.PI * 1.85);
ctx.arc(0, -size * 0.05, size * 0.22, Math.PI * 1.1, Math.PI * 0.1);
ctx.arc(size * 0.22, size * 0.05, size * 0.19, Math.PI * 1.4, Math.PI * 0.4);
ctx.closePath();
ctx.fill();
ctx.stroke();
};
// Helper function to draw a sun
const drawSun = (offsetX = 0, offsetY = 0, scale = 1) => {
ctx.save();
ctx.translate(offsetX, offsetY);
ctx.scale(scale, scale);
ctx.fillStyle = '#FFD700';
ctx.strokeStyle = '#FFD700';
ctx.beginPath();
ctx.arc(0, 0, size * 0.25, 0, Math.PI * 2, false);
ctx.fill();
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(Math.cos(angle) * size * 0.3, Math.sin(angle) * size * 0.3);
ctx.lineTo(Math.cos(angle) * size * 0.4, Math.sin(angle) * size * 0.4);
ctx.stroke();
}
ctx.restore();
};
if (condition.includes('cloud') && condition.includes('sun')) {
drawSun(-size * 0.15, -size * 0.1, 0.8);
drawCloud();
} else if (condition.includes('sun')) {
drawSun();
} else if (condition.includes('rain')) {
drawCloud();
ctx.strokeStyle = '#7B94B5';
ctx.beginPath();
ctx.moveTo(-size * 0.15, size * 0.25);
ctx.lineTo(-size * 0.1, size * 0.45);
ctx.moveTo(0, size * 0.25);
ctx.lineTo(size * 0.05, size * 0.45);
ctx.moveTo(size * 0.15, size * 0.25);
ctx.lineTo(size * 0.2, size * 0.45);
ctx.stroke();
} else if (condition.includes('snow')) {
ctx.strokeStyle = 'white';
const arms = 8;
for (let i = 0; i < arms; i++) {
const angle = (i / arms) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(Math.cos(angle) * size * 0.4, Math.sin(angle) * size * 0.4);
// Sub-branches
let x1 = Math.cos(angle) * size * 0.2;
let y1 = Math.sin(angle) * size * 0.2;
let pAngle1 = angle + Math.PI / 4;
let pAngle2 = angle - Math.PI / 4;
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + Math.cos(pAngle1) * size * 0.15, y1 + Math.sin(pAngle1) * size * 0.15);
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + Math.cos(pAngle2) * size * 0.15, y1 + Math.sin(pAngle2) * size * 0.15);
ctx.stroke();
}
} else if (condition.includes('cloud')) {
drawCloud();
}
ctx.restore();
};
// --- Main Function Logic ---
const FONT_FAMILY = 'Roboto';
if (!document.fonts.check(`12px ${FONT_FAMILY}`)) {
await loadFont(FONT_FAMILY, 'https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2');
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
const condition = weatherCondition.toLowerCase();
// 1. Apply base filter and draw original image
if (condition.includes('cloud') || condition.includes('rain') || condition.includes('snow')) {
ctx.filter = 'grayscale(30%) brightness(95%)';
}
ctx.drawImage(originalImg, 0, 0);
ctx.filter = 'none'; // Reset filter for subsequent drawings
// 2. Apply cosmetic weather effect overlay
if (condition.includes('rain')) {
drawRainEffect(ctx, canvas.width, canvas.height);
} else if (condition.includes('snow')) {
drawSnowEffect(ctx, canvas.width, canvas.height);
} else if (condition.includes('sun')) {
drawSunnyEffect(ctx, canvas.width, canvas.height);
}
// 3. Draw information display panel
const padding = Math.min(canvas.width, canvas.height) * 0.04;
const iconSize = Math.min(canvas.width, canvas.height) * 0.1;
const tempTextSize = Math.min(canvas.width, canvas.height) * 0.07;
const locationTextSize = tempTextSize * 0.6;
// Calculate dynamic size for the background panel
ctx.font = `bold ${tempTextSize}px ${FONT_FAMILY}, sans-serif`;
const tempWidth = ctx.measureText(temperature).width;
ctx.font = `bold ${locationTextSize}px ${FONT_FAMILY}, sans-serif`;
const locWidth = ctx.measureText(location).width;
const infoBoxWidth = padding + Math.max(locWidth, tempWidth + iconSize + padding) + padding;
const infoBoxHeight = padding + locationTextSize + tempTextSize + padding;
// Draw panel background
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(padding, padding, infoBoxWidth, infoBoxHeight, 15);
ctx.fill();
ctx.stroke();
// Draw text and icon
ctx.fillStyle = 'white';
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
// Location Text
const locX = padding * 2;
const locY = padding * 2;
ctx.font = `bold ${locationTextSize}px ${FONT_FAMILY}, sans-serif`;
ctx.fillText(location, locX, locY);
// Temperature Text
const tempX = locX;
const tempY = locY + locationTextSize + 5;
ctx.font = `bold ${tempTextSize}px ${FONT_FAMILY}, sans-serif`;
ctx.fillText(temperature, tempX, tempY);
// Weather Icon
const iconX = tempX + tempWidth + padding;
const iconY = tempY + tempTextSize / 2;
drawWeatherIcon(ctx, condition, iconX, iconY, iconSize);
return canvas;
}
Apply Changes