You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, glowIntensity = 0.8, glowColorStr = "255,165,0", smokeAmount = 0.6, darkenSky = 0.7) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure image is loaded and dimensions are available
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
// Helper to parse color string "r,g,b" into numbers
const parseColor = (colorStr) => {
try {
return colorStr.split(',').map(s => parseInt(s.trim(), 10));
} catch (e) {
// Default to white if parsing fails
return [255, 255, 255];
}
};
// Clamp input parameters to sensible ranges
const effectiveGlowIntensity = Math.max(0, Math.min(1.5, Number(glowIntensity) || 0));
const [r_glow, g_glow, b_glow] = parseColor(glowColorStr);
const effectiveSmokeAmount = Math.max(0, Math.min(1.0, Number(smokeAmount) || 0));
const effectiveDarkenSky = Math.max(0, Math.min(1.0, Number(darkenSky) || 0));
// ORDER OF DRAWING:
// 1. Original Image
// 2. Sky Darkening (Vignette)
// 3. Smoke
// 4. Glow (Engine Flame + Plume)
// 1. Draw Original Image
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
// Central point for the effect (bottom-center of the image, roughly where rocket engine would be)
const effectCenterX = canvasWidth / 2;
// Main glow position (Y-coordinate, e.g., 0.9 means 90% down from top)
const mainGlowY = canvasHeight * 0.9;
// 2. Sky Darkening / Vignette
if (effectiveDarkenSky > 0) {
ctx.save();
const vignetteCenterX = effectCenterX;
const vignetteCenterY = mainGlowY;
const dx1 = vignetteCenterX;
const dy1 = vignetteCenterY;
const dx2 = canvasWidth - vignetteCenterX;
const dy2 = canvasHeight - vignetteCenterY;
//Ensure maxDist is positive
const maxDist = Math.max(1, Math.sqrt(Math.max(dx1*dx1, dx2*dx2) + Math.max(dy1*dy1, dy2*dy2)));
const innerRadius = Math.max(1, canvasHeight * 0.20);
let gradVignette = ctx.createRadialGradient(
vignetteCenterX, vignetteCenterY, innerRadius,
vignetteCenterX, vignetteCenterY, maxDist
);
gradVignette.addColorStop(0, `rgba(0,0,0,0)`);
gradVignette.addColorStop(0.3, `rgba(0,0,0, ${effectiveDarkenSky * 0.15})`);
gradVignette.addColorStop(0.7, `rgba(0,0,0, ${effectiveDarkenSky * 0.75})`);
gradVignette.addColorStop(1, `rgba(0,0,0, ${effectiveDarkenSky * 0.95})`);
ctx.fillStyle = gradVignette;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.restore();
}
// 3. Smoke
const smokeOriginY = mainGlowY + canvasHeight * 0.03;
if (effectiveSmokeAmount > 0) {
ctx.save();
const maxSmokeParticles = 30 + Math.floor(150 * effectiveSmokeAmount);
for (let i = 0; i < maxSmokeParticles; i++) {
const particleLife = Math.random(); // 0 (new) to 1 (old/dissipated)
const horizontalSpreadFactor = 0.3 + particleLife * 0.4;
const horizontalSpread = canvasWidth * horizontalSpreadFactor * (0.8 + effectiveSmokeAmount * 0.4);
const particleX = effectCenterX + (Math.random() - 0.5) * horizontalSpread;
const verticalDistanceFactor = particleLife * (0.7 + effectiveSmokeAmount * 0.6);
const verticalDistance = canvasHeight * 0.35 * verticalDistanceFactor;
const particleY = smokeOriginY - verticalDistance * (0.6 + Math.random() * 0.8);
const baseRadiusFactor = (1 - particleLife * 0.5) * (0.5 + effectiveSmokeAmount * 0.5);
const baseRadius = Math.max(1, canvasHeight * 0.08 * baseRadiusFactor);
const particleRadiusX = Math.max(1, baseRadius * (0.5 + Math.random() * 0.8));
const particleRadiusY = Math.max(1, baseRadius * (0.5 + Math.random() * 0.8));
const opacityFactor = (1 - particleLife * 0.6) * effectiveSmokeAmount;
const opacity = Math.max(0, (0.1 + 0.5 * Math.random()) * opacityFactor);
const smokeColorVal = 170 + Math.floor(Math.random() * 65);
ctx.fillStyle = `rgba(${smokeColorVal}, ${smokeColorVal}, ${smokeColorVal + 5}, ${opacity})`;
const blurSize = Math.max(0, canvasHeight * 0.01 * (1 + particleLife * 3 + effectiveSmokeAmount * 2));
if (blurSize > 0) { // Avoid filter="blur(0px)" which can be slow or buggy
ctx.filter = `blur(${blurSize}px)`;
}
ctx.beginPath();
ctx.ellipse(particleX, particleY, particleRadiusX, particleRadiusY, Math.random() * Math.PI, 0, Math.PI * 2);
ctx.fill();
if (blurSize > 0) {
ctx.filter = 'none'; // Reset if applied, better to set globally once but this ensures it resets if particles is low
}
}
if (maxSmokeParticles > 0) ctx.filter = 'none'; // Final reset
ctx.restore();
}
// 4. Glow (Engine Flame + Plume)
if (effectiveGlowIntensity > 0) {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
const mainGlowOuterRadius = Math.max(1, canvasHeight * 0.18 * effectiveGlowIntensity);
const mainGlowInnerRadius = Math.max(1, mainGlowOuterRadius * 0.25);
let gradMainGlow = ctx.createRadialGradient(effectCenterX, mainGlowY, mainGlowInnerRadius, effectCenterX, mainGlowY, mainGlowOuterRadius);
gradMainGlow.addColorStop(0, `rgba(${r_glow}, ${g_glow}, ${b_glow}, ${0.7 * effectiveGlowIntensity})`);
gradMainGlow.addColorStop(0.5, `rgba(${r_glow}, ${g_glow}, ${b_glow}, ${0.35 * effectiveGlowIntensity})`);
gradMainGlow.addColorStop(1, `rgba(${r_glow}, ${g_glow}, ${b_glow}, 0)`);
ctx.fillStyle = gradMainGlow;
ctx.beginPath();
ctx.arc(effectCenterX, mainGlowY, mainGlowOuterRadius, 0, Math.PI * 2);
ctx.fill();
const coreRadius = Math.max(1, canvasHeight * 0.05 * effectiveGlowIntensity);
let gradCore = ctx.createRadialGradient(effectCenterX, mainGlowY, coreRadius * 0.05, effectCenterX, mainGlowY, coreRadius);
gradCore.addColorStop(0, `rgba(255, 255, 255, ${0.9 * effectiveGlowIntensity})`);
gradCore.addColorStop(0.3, `rgba(255, 255, 230, ${0.7 * effectiveGlowIntensity})`);
gradCore.addColorStop(1, `rgba(${r_glow},${g_glow},${b_glow}, ${0.2 * effectiveGlowIntensity})`);
ctx.fillStyle = gradCore;
ctx.beginPath();
ctx.arc(effectCenterX, mainGlowY, coreRadius, 0, Math.PI * 2);
ctx.fill();
const plumeY = mainGlowY + canvasHeight * 0.015;
const plumeLength = Math.max(1, canvasHeight * 0.22 * effectiveGlowIntensity);
const plumeWidth = Math.max(1, canvasWidth * 0.10 * effectiveGlowIntensity);
const plumeBlurSize = Math.max(0, canvasHeight * 0.012 * effectiveGlowIntensity);
if (plumeBlurSize > 0) {
ctx.filter = `blur(${plumeBlurSize}px)`;
}
ctx.fillStyle = `rgba(${r_glow}, ${g_glow}, ${b_glow}, ${0.45 * effectiveGlowIntensity})`;
ctx.beginPath();
ctx.ellipse(effectCenterX, plumeY + plumeLength * 0.25, Math.max(1, plumeWidth * 0.6), Math.max(1, plumeLength * 0.5), 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = `rgba(${r_glow}, ${g_glow}, ${b_glow}, ${0.25 * effectiveGlowIntensity})`;
ctx.beginPath();
ctx.ellipse(effectCenterX, plumeY + plumeLength * 0.45, Math.max(1, plumeWidth * 0.9), Math.max(1, plumeLength * 0.9), 0, 0, Math.PI * 2);
ctx.fill();
if (plumeBlurSize > 0) {
ctx.filter = 'none';
}
ctx.restore();
}
return canvas;
}
Apply Changes