You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, numBands = 5, intensity = 0.6, colorString = "30,180,100;70,60,180;200,80,150") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure correct dimensions are used, prefer natural dimensions
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
if (imgWidth === 0 || imgHeight === 0) {
// Handle cases where image dimensions are not available (e.g., image not loaded or invalid)
console.error("Image dimensions are zero. Ensure the image is loaded and valid.");
// Return an empty (or small error indicating) canvas
ctx.fillStyle = "grey";
ctx.fillRect(0,0, canvas.width || 100, canvas.height || 30);
ctx.fillStyle = "red";
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.fillText("Error: Image not loaded or invalid.", (canvas.width || 100)/2, (canvas.height || 30)/2 + 4);
return canvas;
}
// Draw the original image
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Parse colors: "R1,G1,B1;R2,G2,B2;..."
const parsedColors = colorString.split(';').map(cStr => {
const parts = cStr.split(',').map(s => parseInt(s.trim(), 10));
return {
r: (Number.isFinite(parts[0]) ? Math.min(255, Math.max(0,parts[0])) : 0),
g: (Number.isFinite(parts[1]) ? Math.min(255, Math.max(0,parts[1])) : 0),
b: (Number.isFinite(parts[2]) ? Math.min(255, Math.max(0,parts[2])) : 0)
};
}).filter(c =>
// Filter out any entries that might have resulted from empty strings e.g. ";;"
// Although the parsing above is robust and provides default 0,0,0
// this check is just an extra safeguard if a color string was like "NaN,NaN,NaN"
!(isNaN(c.r) || isNaN(c.g) || isNaN(c.b))
);
if (parsedColors.length === 0) {
// Default fallback colors if colorString is invalid or results in no usable colors
parsedColors.push({ r: 30, g: 180, b: 100 }); // Aurora Green
parsedColors.push({ r: 70, g: 60, b: 180 }); // Aurora Purple/Blue
parsedColors.push({ r: 200, g: 80, b: 150 });// Aurora Pink
}
// Use 'lighter' composite operation for glowing effects
ctx.globalCompositeOperation = 'lighter';
for (let i = 0; i < numBands; i++) {
const color = parsedColors[i % parsedColors.length];
// Band properties
const bandHeight = Math.max(20, canvas.height * (0.15 + Math.random() * 0.35));
const yPosition = Math.random() * Math.max(0, canvas.height - bandHeight);
const bandAlpha = Math.max(0.01, Math.min(1, intensity * (0.25 + Math.random() * 0.4))); // Alpha for band core
// Vertical gradient for each band to make it softer at edges
const gradient = ctx.createLinearGradient(0, yPosition, 0, yPosition + bandHeight);
gradient.addColorStop(0, `rgba(${color.r}, ${color.g}, ${color.b}, ${bandAlpha * 0.1})`);
gradient.addColorStop(0.2 + Math.random() * 0.1, `rgba(${color.r}, ${color.g}, ${color.b}, ${bandAlpha * 0.7})`);
gradient.addColorStop(0.5, `rgba(${color.r}, ${color.g}, ${color.b}, ${bandAlpha})`); // Core color intensity
gradient.addColorStop(0.8 - Math.random() * 0.1, `rgba(${color.r}, ${color.g}, ${color.b}, ${bandAlpha * 0.7})`);
gradient.addColorStop(1, `rgba(${color.r}, ${color.g}, ${color.b}, ${bandAlpha * 0.1})`);
ctx.fillStyle = gradient;
// Draw a wavy band
ctx.beginPath();
const segments = Math.max(20, Math.floor(canvas.width / 30)); // More segments for wider images
const baseAmplitude = bandHeight * 0.10; // Base for wave height variation
// Top edge of the band
// Start y for the top edge with some random displacement
let currentY = yPosition + (Math.random() - 0.5) * baseAmplitude * 2;
ctx.moveTo(0, Math.max(0, Math.min(canvas.height, currentY)));
for (let j_seg = 0; j_seg <= segments; j_seg++) {
const x = (canvas.width / segments) * j_seg;
// Sum of two sine waves for a more natural, irregular wave pattern
const wave1Freq = (Math.PI * 2) / (canvas.width / (1 + Math.random() * 1.5)); // 1 to 2.5 cycles
const wave1Phase = Math.random() * Math.PI * 2;
const wave1Amp = baseAmplitude * (0.6 + Math.random() * 0.8); // Varies per segment slightly
const wave2Freq = (Math.PI * 2) / (canvas.width / (0.4 + Math.random() * 1.0)); // 0.4 to 1.4 cycles
const wave2Phase = Math.random() * Math.PI * 2;
const wave2Amp = baseAmplitude * (0.4 + Math.random() * 0.6);
const yOffset = Math.sin(x * wave1Freq + wave1Phase) * wave1Amp +
Math.sin(x * wave2Freq + wave2Phase) * wave2Amp;
currentY = yPosition + yOffset;
ctx.lineTo(x, Math.max(0, Math.min(canvas.height, currentY))); // Clamp Y to canvas bounds
}
// Bottom edge of the band (offset by bandHeight)
// Start y for the bottom edge (right side) with random displacement
currentY = yPosition + bandHeight + (Math.random() - 0.5) * baseAmplitude * 2;
ctx.lineTo(canvas.width, Math.max(0, Math.min(canvas.height, currentY))); // Connect to bottom-right general area
for (let j_seg = segments; j_seg >= 0; j_seg--) {
const x = (canvas.width / segments) * j_seg;
// Similar wave calculation for the bottom edge for consistency but still dynamic
const wave1Freq = (Math.PI * 2) / (canvas.width / (1 + Math.random() * 1.5));
const wave1Phase = Math.random() * Math.PI * 2; // Different phase helps
const wave1Amp = baseAmplitude * (0.6 + Math.random() * 0.8);
const wave2Freq = (Math.PI * 2) / (canvas.width / (0.4 + Math.random() * 1.0));
const wave2Phase = Math.random() * Math.PI * 2;
const wave2Amp = baseAmplitude * (0.4 + Math.random() * 0.6);
const yOffset = Math.sin(x * wave1Freq + wave1Phase) * wave1Amp +
Math.sin(x * wave2Freq + wave2Phase) * wave2Amp;
currentY = yPosition + bandHeight + yOffset; // Add bandHeight for bottom edge
ctx.lineTo(x, Math.max(0, Math.min(canvas.height, currentY))); // Clamp Y
}
ctx.closePath();
ctx.fill();
}
// Reset composite operation to default for any subsequent drawing (if any)
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes