You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, rainAmount = 500, dropSpeed = 5, wind = 2, rainColor = 'rgba(174,194,224,0.7)', addRipples = 1, addHaze = 1) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
let rainLayers = [
[], // foreground
[], // midground
[] // background
];
let splashes = [];
const gravity = 0.2;
// Helper to get random number in a range
const rand = (min, max) => Math.random() * (max - min) + min;
/**
* Represents a single raindrop.
*/
class RainDrop {
constructor(layer) {
this.layer = layer;
this.reset();
}
reset() {
this.x = rand(-canvas.width * 0.2, canvas.width * 1.2);
this.y = rand(-canvas.height, 0);
// Properties based on layer for parallax effect
switch (this.layer) {
case 0: // Foreground
this.speed = dropSpeed * rand(1.2, 1.5);
this.length = rand(20, 30);
this.opacity = rand(0.6, 0.8);
this.width = 2;
break;
case 1: // Midground
this.speed = dropSpeed * rand(0.9, 1.1);
this.length = rand(10, 20);
this.opacity = rand(0.4, 0.6);
this.width = 1.5;
break;
case 2: // Background
default:
this.speed = dropSpeed * rand(0.6, 0.8);
this.length = rand(5, 10);
this.opacity = rand(0.2, 0.4);
this.width = 1;
break;
}
}
update() {
this.y += this.speed;
this.x += wind * (this.layer + 1) / 3; // Wind affects foreground more
}
draw() {
ctx.beginPath();
ctx.moveTo(this.x, this.y);
// The line is tilted by the wind
ctx.lineTo(this.x + wind, this.y + this.length);
ctx.strokeStyle = rainColor;
ctx.lineWidth = this.width;
ctx.lineCap = 'round';
ctx.globalAlpha = this.opacity;
ctx.stroke();
ctx.globalAlpha = 1.0; // Reset global alpha
}
}
/**
* Represents a splash particle when a drop hits the ground.
*/
class Splash {
constructor(x) {
this.x = x;
this.y = canvas.height;
this.vx = (Math.random() - 0.5) * 6;
this.vy = -Math.random() * 4 - 2;
this.radius = Math.random() * 1.5 + 1;
this.life = 60; // Frames to live
this.opacity = 0.8;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += gravity;
this.life--;
this.opacity = (this.life / 60) * 0.8;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = rainColor;
ctx.globalAlpha = this.opacity > 0 ? this.opacity : 0;
ctx.fill();
ctx.globalAlpha = 1.0; // Reset
}
}
// --- Initialization ---
// Distribute rain amount across layers
const layerAmounts = [rainAmount * 0.15, rainAmount * 0.5, rainAmount * 0.35];
for (let layer = 0; layer < 3; layer++) {
for (let i = 0; i < layerAmounts[layer]; i++) {
rainLayers[layer].push(new RainDrop(layer));
}
}
// --- Animation Loop ---
let animationFrameId;
function animate() {
// Draw background image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Optional Haze effect
if (addHaze === 1) {
ctx.fillStyle = 'rgba(70, 80, 120, 0.15)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Update and draw rain and splashes
// Draw layers from back to front
for (let layer = 2; layer >= 0; layer--) {
rainLayers[layer].forEach((drop, index) => {
drop.update();
drop.draw();
// If drop is off-screen, reset it or create splashes
if (drop.y > canvas.height) {
// Create splashes for mid and foreground drops
if (addRipples === 1 && layer < 2) {
const splashCount = Math.random() * 3 + 2;
for (let j = 0; j < splashCount; j++) {
splashes.push(new Splash(drop.x));
}
}
drop.reset();
}
// Wrap drops horizontally if blown off-screen
if (drop.x < -canvas.width * 0.2) drop.x = canvas.width * 1.2;
if (drop.x > canvas.width * 1.2) drop.x = -canvas.width * 0.2;
});
}
// Update and draw splashes
splashes.forEach((splash, index) => {
splash.update();
splash.draw();
if (splash.life <= 0) {
splashes.splice(index, 1);
}
});
animationFrameId = requestAnimationFrame(animate);
}
// Start the animation
animate();
// To prevent memory leaks, you might want to stop the animation
// when the canvas is no longer in use. This can be done by
// adding a 'destroy' method to the canvas object.
canvas.destroy = () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
};
return canvas;
}
Apply Changes