You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, plasmaScale = 0.05, timeOffset = 1.0, ballRadius = 0.4, ballCenterX = 0.5, ballCenterY = 0.5, intensity = 0.7, color1Str = "255,0,255", color2Str = "0,255,255", color3Str = "255,255,0", blendMode = "overlay") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimization hint for frequent getImageData
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
if (width === 0 || height === 0) {
// Return an empty canvas if image dimensions are zero
return canvas;
}
ctx.drawImage(originalImg, 0, 0, width, height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Error getting image data: ", e);
// This can happen due to a tainted canvas (cross-origin image without CORS)
// Fallback: return the canvas with the original image drawn (without the effect).
return canvas;
}
const data = imageData.data;
// Helper to parse and validate color strings (e.g., "255,0,128")
const parseColor = (str) => {
const arr = str.split(',').map(val => parseFloat(val.trim())); // Use parseFloat for robustness
return [
Math.max(0, Math.min(255, arr[0] || 0)), // Default to 0 if NaN, clamp to 0-255
Math.max(0, Math.min(255, arr[1] || 0)),
Math.max(0, Math.min(255, arr[2] || 0))
];
};
const c1 = parseColor(color1Str); // First plasma color
const c2 = parseColor(color2Str); // Second plasma color
const c3 = parseColor(color3Str); // Third plasma color
// Ball effect parameters
const ballEffectCenterX = ballCenterX * width;
const ballEffectCenterY = ballCenterY * height;
// Calculate actual radius: ballRadius is a factor of the smaller image dimension
const effectRadius = ballRadius > 0 ? ballRadius * Math.min(width, height) : 0;
// Plasma function component parameters
const freqFactor1 = 1.0, freqFactor2 = 1.0, freqFactor3 = 0.5, freqFactor4 = 1.0;
const timeMod1 = 1.0, timeMod2 = 1.5, timeMod3 = 2.0, timeMod4 = 2.5;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
const origR_norm = data[idx] / 255;
const origG_norm = data[idx + 1] / 255;
const origB_norm = data[idx + 2] / 255;
// 1. Calculate plasma value (normalized to 0-1)
const scaledX = x * plasmaScale;
const scaledY = y * plasmaScale;
const scaledCenterX = (x - width / 2) * plasmaScale;
const scaledCenterY = (y - height / 2) * plasmaScale;
const tOff1 = timeOffset * timeMod1;
const tOff2 = timeOffset * timeMod2;
const tOff3 = timeOffset * timeMod3;
const tOff4 = timeOffset * timeMod4;
const pVal1 = Math.sin(scaledX * freqFactor1 + tOff1);
const pVal2 = Math.sin(scaledY * freqFactor2 + tOff2);
const pVal3 = Math.sin((scaledX * freqFactor3 + scaledY * freqFactor3) + tOff3); // Diagonal waves
const pVal4 = Math.sin(Math.sqrt(scaledCenterX * scaledCenterX + scaledCenterY * scaledCenterY) * freqFactor4 + tOff4); // Radial waves from image center
// Sum of 4 sine waves is in range [-4, 4]. Normalize to [0, 1].
let plasmaValue = (pVal1 + pVal2 + pVal3 + pVal4 + 4) / 8;
// 2. Determine plasma color based on plasmaValue (colors normalized 0-1)
let plasmaR_norm, plasmaG_norm, plasmaB_norm;
if (plasmaValue < 0.5) {
const t = plasmaValue * 2; // Interpolation factor for c1 to c2
plasmaR_norm = (c1[0] * (1 - t) + c2[0] * t) / 255;
plasmaG_norm = (c1[1] * (1 - t) + c2[1] * t) / 255;
plasmaB_norm = (c1[2] * (1 - t) + c2[2] * t) / 255;
} else {
const t = (plasmaValue - 0.5) * 2; // Interpolation factor for c2 to c3
plasmaR_norm = (c2[0] * (1 - t) + c3[0] * t) / 255;
plasmaG_norm = (c2[1] * (1 - t) + c3[1] * t) / 255;
plasmaB_norm = (c2[2] * (1 - t) + c3[2] * t) / 255;
}
// 3. Calculate "ball" mask alpha (0-1)
let ballMaskAlpha;
if (ballRadius <= 0 || effectRadius <= 0) { // Apply effect to the whole image if radius is zero/negative
ballMaskAlpha = 1.0;
} else {
const distToBallEffectCenter = Math.sqrt(Math.pow(x - ballEffectCenterX, 2) + Math.pow(y - ballEffectCenterY, 2));
ballMaskAlpha = 1.0 - Math.min(1.0, distToBallEffectCenter / effectRadius);
ballMaskAlpha = Math.pow(ballMaskAlpha, 1.5); // Adjust falloff curve (1.0 linear, >1.0 sharper center)
}
// 4. Calculate effective intensity for blending (0-1)
const effectiveIntensity = Math.max(0, Math.min(1, intensity * ballMaskAlpha));
// 5. Blend plasma color with original pixel color
const blendFunctions = {
screen: (b, l) => 1 - (1 - b) * (1 - l),
multiply: (b, l) => b * l,
additive: (b, l) => Math.min(1, b + l),
overlay: (b, l) => b <= 0.5 ? (2 * b * l) : (1 - 2 * (1 - b) * (1 - l)),
difference:(b, l) => Math.abs(b - l),
// 'alpha' blend mode: standard alpha compositing with plasma as foreground
alpha: (b, l, a) => b * (1 - a) + l * a
};
const selectedBlendFn = blendFunctions[blendMode] || blendFunctions.overlay; // Default to overlay
let finalR_norm, finalG_norm, finalB_norm;
if (blendMode === 'alpha') {
finalR_norm = selectedBlendFn(origR_norm, plasmaR_norm, effectiveIntensity);
finalG_norm = selectedBlendFn(origG_norm, plasmaG_norm, effectiveIntensity);
finalB_norm = selectedBlendFn(origB_norm, plasmaB_norm, effectiveIntensity);
} else {
// For other modes, calculate the fully blended color, then mix with original by effectiveIntensity
const blendedR = selectedBlendFn(origR_norm, plasmaR_norm);
const blendedG = selectedBlendFn(origG_norm, plasmaG_norm);
const blendedB = selectedBlendFn(origB_norm, plasmaB_norm);
finalR_norm = origR_norm * (1 - effectiveIntensity) + blendedR * effectiveIntensity;
finalG_norm = origG_norm * (1 - effectiveIntensity) + blendedG * effectiveIntensity;
finalB_norm = origB_norm * (1 - effectiveIntensity) + blendedB * effectiveIntensity;
}
data[idx] = finalR_norm * 255;
data[idx + 1] = finalG_norm * 255;
data[idx + 2] = finalB_norm * 255;
// Alpha channel (data[idx + 3]) remains unchanged
}
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes