You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, rainDensityParam = "150", rainSpeedParam = "20") {
// Parse arguments and provide safety bounds
let rainIntensity = parseInt(rainDensityParam, 10);
if (isNaN(rainIntensity)) rainIntensity = 150;
rainIntensity = Math.max(10, Math.min(rainIntensity, 1000)); // clamp between 10 and 1000
let rainSpeed = parseFloat(rainSpeedParam);
if (isNaN(rainSpeed)) rainSpeed = 20.0;
rainSpeed = Math.max(rainSpeed, 1.0);
// Create and setup the canvas
const canvas = document.createElement('canvas');
canvas.width = originalImg.width || originalImg.naturalWidth;
canvas.height = originalImg.height || originalImg.naturalHeight;
const ctx = canvas.getContext('2d');
// Adapt number of drops to image resolution (Density per 100,000 pixels)
const totalPixels = canvas.width * canvas.height;
const numDrops = Math.floor((rainIntensity * totalPixels) / 100000);
const raindrops = [];
for (let i = 0; i < numDrops; i++) {
raindrops.push({
x: Math.random() * (canvas.width + canvas.height * 0.2), // allow overflow for diagonal path
y: Math.random() * canvas.height,
len: Math.random() * 20 + 10,
speed: Math.random() * 5 + rainSpeed
});
}
// State variables for thunderstorm & lightning
let flashTimer = 0;
let flashAlpha = 0;
let timeToNextLightning = 0; // Trigger an immediate strike on the very first frame
let lightningPaths = null;
// Helper functions for lightning generation
function generateLightning(width, height) {
let bolts = [];
let startX = Math.random() * width;
function branch(x, y, endY, isMain, thickness) {
let path = [];
let currX = x;
let currY = y;
path.push({x, y, thickness});
// Draw path until it reaches target Y
while (currY < endY) {
currX += (Math.random() - 0.5) * (isMain ? 80 : 40);
currY += Math.random() * (isMain ? 50 : 25) + 10;
path.push({x: currX, y: currY, thickness});
// Randomly spawn smaller branches
if (Math.random() < (isMain ? 0.2 : 0.05) && thickness > 1.5) {
branch(currX, currY, currY + Math.random() * 200 + 50, false, thickness * 0.6);
}
}
bolts.push(path);
}
branch(startX, 0, height, true, 4 + Math.random() * 2);
return bolts;
}
// Main animation loop
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 1. Draw base image
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// 2. Logic for generating lightning strikes
if (timeToNextLightning <= 0) {
lightningPaths = generateLightning(canvas.width, canvas.height);
flashTimer = Math.floor(Math.random() * 15 + 10);
timeToNextLightning = Math.random() * 150 + 100;
}
timeToNextLightning--;
// Compute flicker/flash alpha state
if (flashTimer > 0) {
flashTimer--;
// Flicker effect during the initial strike
flashAlpha = Math.random() * 0.6 + 0.4;
if (flashTimer === 0) flashAlpha = 1.0; // lock alpha to full before fadeout starts
} else {
// Smooth fade out
flashAlpha -= 0.05;
if (flashAlpha < 0) {
flashAlpha = 0;
lightningPaths = null;
}
}
// 3. Ambient Dark Overlay (Simulation of a moody storm weather)
// Lightens up corresponding to the lightning flash intensity
const overlayAlpha = 0.65 - (flashAlpha * 0.6);
ctx.fillStyle = `rgba(15, 20, 35, ${Math.max(0, overlayAlpha)})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 4. Render Lightning Bolts
if (lightningPaths && flashAlpha > 0) {
ctx.shadowBlur = 20;
ctx.shadowColor = 'rgba(230, 240, 255, 1)';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = `rgba(255, 255, 255, ${flashAlpha})`;
for (let i = 0; i < lightningPaths.length; i++) {
const path = lightningPaths[i];
ctx.beginPath();
ctx.lineWidth = path[0].thickness;
for (let j = 0; j < path.length; j++) {
if (j === 0) ctx.moveTo(path[j].x, path[j].y);
else ctx.lineTo(path[j].x, path[j].y);
}
ctx.stroke();
}
ctx.shadowBlur = 0; // reset blur for next elements
}
// 5. General Overall Screen Flash illuminating the sky
if (flashAlpha > 0) {
ctx.globalCompositeOperation = 'screen';
ctx.fillStyle = `rgba(200, 220, 255, ${flashAlpha * 0.25})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-over';
}
// 6. Process and Draw Rain Drops (Optimized with a single stroke)
ctx.strokeStyle = `rgba(180, 200, 230, ${0.4 + flashAlpha * 0.3})`;
ctx.lineWidth = 1.5;
ctx.beginPath();
for (let i = 0; i < raindrops.length; i++) {
let drop = raindrops[i];
// Draw slanted rain depending on speed
ctx.moveTo(drop.x, drop.y);
ctx.lineTo(drop.x - drop.len * 0.2, drop.y + drop.len);
// Move drops
drop.y += drop.speed;
drop.x -= drop.speed * 0.2;
// Reset drop strictly outside the boundaries to seem seamless
if (drop.y > canvas.height || drop.x < -drop.len) {
drop.y = -drop.len - Math.random() * 50;
drop.x = Math.random() * (canvas.width + canvas.height * 0.2);
}
}
ctx.stroke();
// Queue next frame
requestAnimationFrame(animate);
}
// Call animate synchronously once to guarantee the first frame (base + lightning)
// is fully rendered to the canvas before returning it.
animate();
return canvas;
}
Apply Changes