You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, dripColor = "rgba(255, 193, 0, 0.7)", numDrips = 10, maxDripLengthFactor = 0.6, maxDripWidthFactor = 0.1, dripRandomnessFactor = 0.3, dripSourceYFactor = 0) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure dimensions are from naturalWidth/Height if available, for unscaled images
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Set the fill style for the drips
ctx.fillStyle = dripColor;
// Helper function to draw a single drip
// x, y: top-center of the drip's starting point on the main canvas
// length: total length of the drip
// maxWidth: maximum width of the drip's bulb
// initialWidth: width of the drip at its starting point (y)
// randomness: a factor (0-1) influencing the jitter/organic look
function drawSingleDrip(ctx, x, y, length, maxWidth, initialWidth, randomness) {
// All coordinates for path drawing are relative to the drip's (x, y) translated origin
// Define key points for the drip silhouette, incorporating randomness
const topL = { x: -initialWidth / 2, y: 0 };
const topR = { x: initialWidth / 2, y: 0 };
// Neck: where the drip narrows before the main bulb
const neckEndY = length * (0.2 + Math.random() * 0.2 * randomness); // 20-40% of length
const neckRelWidthFactor = 0.4 + Math.random() * 0.4; // Neck width relative to maxWidth
const neckWidth = Math.max(initialWidth * 0.7, Math.min(maxWidth * neckRelWidthFactor, initialWidth * 2.0));
const neckL = {
x: -neckWidth / 2 + (Math.random() - 0.5) * neckWidth * randomness,
y: neckEndY
};
const neckR = {
x: neckWidth / 2 + (Math.random() - 0.5) * neckWidth * randomness,
y: neckEndY
};
// Bulb: widest part of the drip
const bulbMidY = length * (0.6 + Math.random() * 0.2 * randomness); // 60-80% of length
const bulbMidL = {
x: -maxWidth / 2 + (Math.random() - 0.5) * maxWidth * randomness,
y: bulbMidY
};
const bulbMidR = {
x: maxWidth / 2 + (Math.random() - 0.5) * maxWidth * randomness,
y: bulbMidY
};
// Tip: the bottom-most point of the drip
const tipY = length;
const tipX = (Math.random() - 0.5) * maxWidth * 0.3 * randomness; // Tip can be slightly off-center
const tip = { x: tipX, y: tipY };
ctx.save();
ctx.translate(x, y); // Move canvas origin to the drip's top-center
ctx.beginPath();
ctx.moveTo(topL.x, topL.y);
// Helper for randomizing control point factors
const cpRand = () => (Math.random() - 0.5) * randomness * 0.5; // Small random factor
// --- Left Side Curves ---
// Curve from topL to neckL
ctx.quadraticCurveTo(
topL.x + cpRand() * initialWidth, // CP_x near topL.x
neckL.y * (0.3 + cpRand() * 0.4), // CP_y between topL.y and neckL.y
neckL.x, neckL.y
);
// Curve from neckL to bulbMidL (bulging outwards)
ctx.quadraticCurveTo(
neckL.x - (maxWidth * 0.1 + cpRand() * neckWidth) * (0.5 + Math.random() * 0.5), // CP_x significantly to the left
(neckL.y + bulbMidL.y) / 2 + cpRand() * (bulbMidL.y - neckL.y), // CP_y midway
bulbMidL.x, bulbMidL.y
);
// Curve from bulbMidL to tip
ctx.quadraticCurveTo(
bulbMidL.x * (0.6 + cpRand()) + tip.x * (0.4 - cpRand()), // CP_x interpolating towards tip, favoring bulbMidL.x
bulbMidL.y + (tip.y - bulbMidL.y) * (0.8 + cpRand() * 0.2), // CP_y mostly towards tip.y
tip.x, tip.y
);
// --- Right Side Curves (mirrored logic) ---
// Curve from tip to bulbMidR
ctx.quadraticCurveTo(
bulbMidR.x * (0.6 + cpRand()) + tip.x * (0.4 - cpRand()), // CP_x interpolating towards tip, favoring bulbMidR.x
bulbMidR.y + (tip.y - bulbMidR.y) * (0.8 + cpRand() * 0.2), // CP_y mostly towards tip.y
bulbMidR.x, bulbMidR.y
);
// Curve from bulbMidR to neckR (bulging outwards)
ctx.quadraticCurveTo(
neckR.x + (maxWidth * 0.1 + cpRand() * neckWidth) * (0.5 + Math.random() * 0.5), // CP_x significantly to the right
(neckR.y + bulbMidR.y) / 2 + cpRand() * (bulbMidR.y - neckR.y), // CP_y midway
neckR.x, neckR.y
);
// Curve from neckR to topR
ctx.quadraticCurveTo(
topR.x + cpRand() * initialWidth, // CP_x near topR.x
neckR.y * (0.3 + cpRand() * 0.4), // CP_y between topR.y and neckR.y
topR.x, topR.y
);
ctx.closePath(); // Connects topR to topL, forming the top edge of the drip
ctx.fill();
ctx.restore(); // Restore canvas state (translation)
}
// Generate and draw the specified number of drips
for (let i = 0; i < numDrips; i++) {
const dripX = Math.random() * canvas.width; // Random X position for the drip
const dripY = canvas.height * dripSourceYFactor; // Y position for drip origin
// Randomize properties for each drip
const currentDripLength = (Math.random() * 0.7 + 0.3) * canvas.height * maxDripLengthFactor;
// Skip if drip length is too small to be meaningful
if (currentDripLength < 10) continue;
const currentInitialWidth = Math.max(2, (Math.random() * 0.02 + 0.005) * canvas.width); // Min 2px or 0.5%-2.5% of canvas width
let currentMaxWidth = currentInitialWidth + (Math.random() * 0.8 + 0.2) * canvas.width * maxDripWidthFactor;
currentMaxWidth = Math.max(currentMaxWidth, currentInitialWidth * 1.5); // Ensure bulb is reasonably wider than stem
// Skip if max width is too small
if (currentMaxWidth < Math.max(5, currentInitialWidth + 2) ) continue;
drawSingleDrip(ctx, dripX, dripY, currentDripLength, currentMaxWidth, currentInitialWidth, dripRandomnessFactor);
}
return canvas;
}
Apply Changes