You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = 0.85, lightColor = '#fff7d6', shadowColor = '#1e2b17', density = 50, blur = 15) {
// Parse numeric inputs
intensity = Number(intensity);
density = Number(density);
blur = Number(blur);
// Create the main canvas and context
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw the original image as base
ctx.drawImage(originalImg, 0, 0);
// Minimum dimension for proportional scaling
const minDim = Math.min(width, height);
// Add padding to prevent blur edge-bleeding (halos)
const p = minDim * 0.1;
// Create secondary canvas for the lighting / shadow map
const mapCanvas = document.createElement('canvas');
mapCanvas.width = width + p * 2;
mapCanvas.height = height + p * 2;
const mCtx = mapCanvas.getContext('2d');
// Scale blur relative to image size for consistent effect at any resolution
const dynamicBlur = blur * (minDim / 800);
mCtx.filter = `blur(${dynamicBlur}px)`;
// Fill the map with the underlying canopy shadow
mCtx.fillStyle = shadowColor;
mCtx.fillRect(0, 0, mapCanvas.width, mapCanvas.height);
// Helper to compute hex to rgba safely
function hexToRgba(hex, alpha) {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
const transparentLight = hexToRgba(lightColor, 0);
// Determine counts based on density and image resolution
const numClusters = Math.max(1, Math.floor((density / 50) * 15 * (width * height) / 1000000));
const spotsPerCluster = 12;
// Procedural generation of dappled light through the canopy
for (let i = 0; i < numClusters; i++) {
// Random cluster center, encompassing padded area
const cx = -p + Math.random() * (width + p * 2);
const cy = -p + Math.random() * (height + p * 2);
// Ambient scattered background light
const ambRadius = minDim * (0.15 + Math.random() * 0.25);
const ambGrad = mCtx.createRadialGradient(cx, cy, 0, cx, cy, ambRadius);
ambGrad.addColorStop(0, hexToRgba(lightColor, 0.45));
ambGrad.addColorStop(1, transparentLight);
mCtx.fillStyle = ambGrad;
mCtx.beginPath();
mCtx.arc(cx, cy, ambRadius, 0, Math.PI * 2);
mCtx.fill();
// Specific focused "camera obscura" sunlight circles through leaf gaps
for (let j = 0; j < spotsPerCluster; j++) {
const rx = cx + (Math.random() - 0.5) * ambRadius * 1.5;
const ry = cy + (Math.random() - 0.5) * ambRadius * 1.5;
const spotRadius = minDim * (0.01 + Math.random() * 0.05);
const spotGrad = mCtx.createRadialGradient(rx, ry, spotRadius * 0.2, rx, ry, spotRadius);
spotGrad.addColorStop(0, hexToRgba(lightColor, 0.85));
spotGrad.addColorStop(0.5, hexToRgba(lightColor, 0.5));
spotGrad.addColorStop(1, transparentLight);
mCtx.fillStyle = spotGrad;
mCtx.beginPath();
mCtx.arc(rx, ry, spotRadius, 0, Math.PI * 2);
mCtx.fill();
}
}
// Draw leaf silhouettes occasionally crossing out the light spots
// to simulate depth and overlapping canopy branches
const numLeaves = Math.max(1, Math.floor(density * 5 * (width * height) / 1000000));
mCtx.fillStyle = hexToRgba(shadowColor, 0.85);
for (let i = 0; i < numLeaves; i++) {
const lx = -p + Math.random() * (width + p * 2);
const ly = -p + Math.random() * (height + p * 2);
const lSize = minDim * (0.04 + Math.random() * 0.08);
const lAngle = Math.random() * Math.PI * 2;
mCtx.save();
mCtx.translate(lx, ly);
mCtx.rotate(lAngle);
// Draw organic leaf shape
mCtx.beginPath();
mCtx.moveTo(0, -lSize);
mCtx.quadraticCurveTo(lSize / 1.5, 0, 0, lSize);
mCtx.quadraticCurveTo(-lSize / 1.5, 0, 0, -lSize);
mCtx.fill();
mCtx.restore();
}
// Blend the dappled light map over the original image
// First pass: 'overlay' creates high contrast, darkening shadows and brightening light gaps
ctx.globalAlpha = intensity;
ctx.globalCompositeOperation = 'overlay';
ctx.drawImage(mapCanvas, -p, -p);
// Second pass: 'soft-light' at half strength to enrich colors without blowing out the highlights
ctx.globalAlpha = intensity * 0.5;
ctx.globalCompositeOperation = 'soft-light';
ctx.drawImage(mapCanvas, -p, -p);
// Reset properties
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes