You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, dropAmount = "80", minSize = "15", maxSize = "60", blurBackgroundPx = "8", randomSeed = "42") {
// Parse parameters
const reqAmount = parseInt(dropAmount, 10) || 80;
const minRadius = parseFloat(minSize) || 15;
const maxRadius = parseFloat(maxSize) || 60;
const blur = parseFloat(blurBackgroundPx) || 8;
let seed = parseInt(randomSeed, 10) || 42;
// Seeded random function for deterministic drop placement
function random() {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280;
}
// Set up canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const w = canvas.width = originalImg.width;
const h = canvas.height = originalImg.height;
// Draw blurred background
if (blur > 0) {
ctx.save();
ctx.filter = `blur(${blur}px)`;
// Slightly scale up to avoid blurred transparent edges bleeding into the canvas
ctx.translate(w / 2, h / 2);
ctx.scale(1.05, 1.05);
ctx.drawImage(originalImg, -w / 2, -h / 2, w, h);
ctx.restore();
} else {
ctx.drawImage(originalImg, 0, 0, w, h);
}
// Darken background slightly to enhance the bright dewdrops
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
ctx.fillRect(0, 0, w, h);
// Generate drop coordinates avoiding excessive overlaps
const drops = [];
for (let i = 0; i < reqAmount * 15 && drops.length < reqAmount; i++) {
let r = minRadius + random() * (maxRadius - minRadius);
let x = r + random() * (w - 2 * r);
let y = r + random() * (h - 2 * r);
let overlap = false;
for (let d of drops) {
let dx = d.x - x;
let dy = d.y - y;
let dist = Math.sqrt(dx * dx + dy * dy);
// Allow a small percentage of overlap for a natural look
if (dist < (r + d.r) * 0.85) {
overlap = true;
break;
}
}
if (!overlap) {
drops.push({ x, y, r });
}
}
// Render drops
for (let d of drops) {
let { x, y, r } = d;
// 1. Draw Drop Shadow
ctx.save();
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = r * 0.4;
ctx.shadowOffsetY = r * 0.25;
// The fill creates a dark base edge which mimics thick liquid/glass refraction rim
ctx.fillStyle = 'rgba(10, 10, 10, 1)';
ctx.fill();
ctx.restore();
// 2. Refracted Image (Inverted and Scaled)
ctx.save();
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.clip(); // Restrict drawing to the droplet shape
// Invert mapping for a spherical lens effect
ctx.translate(x, y);
ctx.scale(-1, -1);
// Scale the whole image to mostly fit within the drop radius
const scale = (r * 2.2) / Math.min(w, h);
ctx.scale(scale, scale);
ctx.drawImage(originalImg, -w / 2, -h / 2, w, h);
ctx.restore();
// 3. Drop Shading and Specular Highlights
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.clip();
// Volume / Depth (inner shadow shading to make it look 3D and spherical)
let volGrad = ctx.createRadialGradient(0, 0, r * 0.3, 0, 0, r);
volGrad.addColorStop(0, 'rgba(0, 0, 0, 0)');
volGrad.addColorStop(0.7, 'rgba(0, 0, 0, 0.15)');
volGrad.addColorStop(1, 'rgba(0, 0, 0, 0.85)');
ctx.fillStyle = volGrad;
ctx.fill();
// Secondary crescent edge highlight for glass-like material
let edgeGrad = ctx.createLinearGradient(-r, -r, r, r);
edgeGrad.addColorStop(0, 'rgba(255, 255, 255, 0.5)');
edgeGrad.addColorStop(0.3, 'rgba(255, 255, 255, 0)');
edgeGrad.addColorStop(0.7, 'rgba(0, 0, 0, 0)');
edgeGrad.addColorStop(1, 'rgba(255, 255, 255, 0.25)');
ctx.fillStyle = edgeGrad;
ctx.fill();
// Primary Specular Reflection (Light source striking top left)
let hlX = -r * 0.3;
let hlY = -r * 0.4;
let hlR = r * 0.5;
let hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, hlR);
hlGrad.addColorStop(0, 'rgba(255, 255, 255, 0.9)');
hlGrad.addColorStop(0.25, 'rgba(255, 255, 255, 0.4)');
hlGrad.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = hlGrad;
ctx.beginPath();
ctx.arc(hlX, hlY, hlR, 0, Math.PI * 2);
ctx.fill();
// Secondary Base Reflection (Light bouncing through to the bottom right)
let blX = r * 0.3;
let blY = r * 0.4;
let blR = r * 0.6;
let blGrad = ctx.createRadialGradient(blX, blY, 0, blX, blY, blR);
blGrad.addColorStop(0, 'rgba(255, 255, 255, 0)');
blGrad.addColorStop(0.6, 'rgba(255, 255, 255, 0.15)');
blGrad.addColorStop(1, 'rgba(255, 255, 255, 0.6)');
ctx.fillStyle = blGrad;
ctx.beginPath();
ctx.arc(blX, blY, blR, 0, Math.PI * 2);
ctx.fill();
// Crisp inner rim light
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = r * 0.05;
ctx.stroke();
ctx.restore();
}
return canvas;
}
Apply Changes