You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, flareXPercent = 50, flareYPercent = 50, mainColorStr = "255,255,220", overallSize = 300, starburstRays = 16, starburstRayLengthScale = 0.8, haloElements = 5, coreBrightness = 0.7, haloBaseOpacity = 0.15) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Draw the original image
ctx.drawImage(originalImg, 0, 0);
// Parse main color string
let r = 255, g = 255, b = 220; // Default main color
const colorParts = mainColorStr.split(',');
if (colorParts.length === 3) {
r = parseInt(colorParts[0].trim(), 10) || 255;
g = parseInt(colorParts[1].trim(), 10) || 255;
b = parseInt(colorParts[2].trim(), 10) || 220;
}
// Ensure brightness/opacity values are within [0,1]
coreBrightness = Math.max(0, Math.min(1, coreBrightness));
haloBaseOpacity = Math.max(0, Math.min(1, haloBaseOpacity));
const flareX = (flareXPercent / 100) * canvas.width;
const flareY = (flareYPercent / 100) * canvas.height;
// Set composite mode for additive blending (light effects)
ctx.globalCompositeOperation = 'lighter';
// 1. Main Glare: A large, soft, circular glow
const glareRadius = Math.max(1, overallSize * 0.6);
const glareGradient = ctx.createRadialGradient(flareX, flareY, 0, flareX, flareY, glareRadius);
glareGradient.addColorStop(0, `rgba(${r},${g},${b}, ${coreBrightness * 0.2})`);
glareGradient.addColorStop(0.5, `rgba(${r},${g},${b}, ${coreBrightness * 0.1})`);
glareGradient.addColorStop(1, `rgba(${r},${g},${b}, 0)`);
ctx.fillStyle = glareGradient;
ctx.beginPath();
ctx.arc(flareX, flareY, glareRadius, 0, Math.PI * 2);
ctx.fill();
// 2. Central Core: Smaller, brighter, more focused glow
const coreRadius = Math.max(1, overallSize * 0.1);
const coreGradient = ctx.createRadialGradient(flareX, flareY, 0, flareX, flareY, coreRadius);
coreGradient.addColorStop(0, `rgba(${r},${g},${b}, ${coreBrightness})`); // Brightest part
coreGradient.addColorStop(0.6, `rgba(${r},${g},${b}, ${coreBrightness * 0.5})`);
coreGradient.addColorStop(1, `rgba(${r},${g},${b}, 0)`);
ctx.fillStyle = coreGradient;
ctx.beginPath();
ctx.arc(flareX, flareY, coreRadius, 0, Math.PI * 2);
ctx.fill();
// 3. Starburst Rays originating from the core
const rayBaseLength = overallSize * starburstRayLengthScale;
const raySharpnessFactor = 0.03;
for (let i = 0; i < starburstRays; i++) {
const angle = (i / starburstRays) * Math.PI * 2 + (Math.random() - 0.5) * (Math.PI / starburstRays) * 0.3;
const currentRayLength = rayBaseLength * (0.7 + Math.random() * 0.6); // Vary length
const startRadiusN = Math.max(1, coreRadius * 0.5); // Rays start slightly out from exact center
const endRadiusN = startRadiusN + currentRayLength;
const tipX = flareX + Math.cos(angle) * endRadiusN;
const tipY = flareY + Math.sin(angle) * endRadiusN;
const perpAngle = angle + Math.PI / 2;
const baseHalfWidth = currentRayLength * raySharpnessFactor * (Math.random() * 0.5 + 0.75);
const x1 = flareX + Math.cos(angle) * startRadiusN + Math.cos(perpAngle) * baseHalfWidth;
const y1 = flareY + Math.sin(angle) * startRadiusN + Math.sin(perpAngle) * baseHalfWidth;
const x2 = flareX + Math.cos(angle) * startRadiusN - Math.cos(perpAngle) * baseHalfWidth;
const y2 = flareY + Math.sin(angle) * startRadiusN - Math.sin(perpAngle) * baseHalfWidth;
ctx.beginPath();
ctx.moveTo(tipX, tipY);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
const rayGradient = ctx.createLinearGradient(flareX, flareY, tipX, tipY);
const rayAlpha = coreBrightness * (0.25 + Math.random() * 0.25);
rayGradient.addColorStop(0, `rgba(${r},${g},${b}, ${rayAlpha})`);
rayGradient.addColorStop(0.6, `rgba(${r},${g},${b}, ${rayAlpha * 0.7})`);
rayGradient.addColorStop(1, `rgba(${r},${g},${b}, 0)`);
ctx.fillStyle = rayGradient;
ctx.fill();
}
// 4. Halo Elements (Ghosts/Secondary Flares)
const baseGhostColors = [
`rgba(220, 50, 50, ${haloBaseOpacity})`, // Reddish
`rgba(50, 200, 50, ${haloBaseOpacity})`, // Greenish
`rgba(80, 80, 230, ${haloBaseOpacity})`, // Bluish
`rgba(${r}, ${g}, ${b}, ${haloBaseOpacity * 1.1})`, // Main flare color
`rgba(${Math.min(255, r+30)}, ${Math.min(255,g+10)}, ${Math.max(0,b-30)}, ${haloBaseOpacity * 0.9})` // Warmer tone
];
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const vecFlareToCenterX = centerX - flareX; // Vector from flare to center
const vecFlareToCenterY = centerY - flareY;
for (let i = 0; i < haloElements; i++) {
// k determines position along the line connecting flare and image center.
// k=0: at flare. k=1: at image center. k=2: at reflection of flare through center.
// Distribute ghosts along this line including random variations.
const kBase = (i / Math.max(1, haloElements - 1)) * 1.8; // Spread up to nearly reflection point
const k = kBase + (Math.random() * 0.8 - 0.4); // Add jitter and spread. Range around -0.4 to 2.2
let ghostX, ghostY;
// If flare is at image center, distribute ghosts radially
if (Math.abs(vecFlareToCenterX) < 1 && Math.abs(vecFlareToCenterY) < 1) {
const randomAngle = Math.random() * Math.PI * 2;
const randomDist = (i + 1) * overallSize * 0.12 * (Math.random() * 0.4 + 0.8);
ghostX = flareX + Math.cos(randomAngle) * randomDist;
ghostY = flareY + Math.sin(randomAngle) * randomDist;
} else {
ghostX = flareX + vecFlareToCenterX * k;
ghostY = flareY + vecFlareToCenterY * k;
}
const ghostRadius = Math.max(1, overallSize * (0.02 + Math.random() * 0.10));
const selectedColorStr = baseGhostColors[i % baseGhostColors.length];
const colorMatch = selectedColorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
if (!colorMatch) continue;
const rC = colorMatch[1];
const gC = colorMatch[2];
const bC = colorMatch[3];
const aC = parseFloat(colorMatch[4] !== undefined ? colorMatch[4] : '1.0');
const ghostGradient = ctx.createRadialGradient(ghostX, ghostY, 0, ghostX, ghostY, ghostRadius);
ghostGradient.addColorStop(0, `rgba(${rC},${gC},${bC}, ${aC * 0.7})`);
ghostGradient.addColorStop(0.5, `rgba(${rC},${gC},${bC}, ${aC * 0.4})`);
ghostGradient.addColorStop(1, `rgba(${rC},${gC},${bC}, 0)`);
ctx.fillStyle = ghostGradient;
ctx.beginPath();
ctx.arc(ghostX, ghostY, ghostRadius, 0, Math.PI * 2);
ctx.fill();
// Optional: add a thin, brighter ring to some ghosts
if (Math.random() < 0.35) {
ctx.beginPath();
ctx.arc(ghostX, ghostY, ghostRadius * (0.7 + Math.random() * 0.2), 0, Math.PI * 2);
ctx.strokeStyle = `rgba(${rC},${gC},${bC}, ${aC * 0.9 * (Math.random() * 0.4 + 0.6)})`;
ctx.lineWidth = Math.max(1, ghostRadius * 0.08 * (Math.random() * 0.6 + 0.7));
ctx.stroke();
}
}
// Reset composite mode to default
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes