You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
particleCountArg = 5000,
particleSizeMinArg = 1,
particleSizeMaxArg = 5,
brightnessThresholdArg = 0, // Expected range 0-255
alphaFactorArg = 1.0, // Multiplier for particle alpha
positionJitterArg = 2, // Max random offset for particle position in pixels
backgroundColorArg = "black" // Background color string (e.g., "black", "#FF0000", "rgba(0,0,0,0)")
) {
// --- Parameter parsing and validation ---
let numParticleCount = Number(particleCountArg);
if (isNaN(numParticleCount) || numParticleCount < 0) {
numParticleCount = 5000; // Default if NaN or negative
}
let numParticleSizeMin = Number(particleSizeMinArg);
if (isNaN(numParticleSizeMin)) {
numParticleSizeMin = 1; // Default if NaN
}
let numParticleSizeMax = Number(particleSizeMaxArg);
if (isNaN(numParticleSizeMax)) {
numParticleSizeMax = 5; // Default if NaN
}
// Ensure particle sizes are positive and min <= max
numParticleSizeMin = Math.max(0.1, numParticleSizeMin); // Smallest drawable size
numParticleSizeMax = Math.max(0.1, numParticleSizeMax);
if (numParticleSizeMin > numParticleSizeMax) {
[numParticleSizeMin, numParticleSizeMax] = [numParticleSizeMax, numParticleSizeMin]; // Swap
}
// Ensure max is at least min after potential individual adjustments
if (numParticleSizeMax < numParticleSizeMin) {
numParticleSizeMax = numParticleSizeMin;
}
let numBrightnessThreshold = Number(brightnessThresholdArg);
if (isNaN(numBrightnessThreshold)) {
numBrightnessThreshold = 0; // Default if NaN
}
numBrightnessThreshold = Math.max(0, Math.min(255, numBrightnessThreshold)); // Clamp to 0-255
let numAlphaFactor = Number(alphaFactorArg);
if (isNaN(numAlphaFactor)) {
numAlphaFactor = 1.0; // Default if NaN
}
let numPositionJitter = Number(positionJitterArg);
if (isNaN(numPositionJitter) || numPositionJitter < 0) {
numPositionJitter = 2; // Default if NaN or negative
}
numPositionJitter = Math.max(0, numPositionJitter); // Ensure non-negative
const backgroundColor = typeof backgroundColorArg === 'string' ? backgroundColorArg : "black";
// --- Canvas setup ---
const outputCanvas = document.createElement('canvas');
const ctx = outputCanvas.getContext('2d');
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
outputCanvas.width = imgWidth;
outputCanvas.height = imgHeight;
// If image dimensions are invalid, return early
if (imgWidth <= 0 || imgHeight <= 0) {
if (backgroundColor && backgroundColor.toLowerCase() !== "transparent") {
try {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, imgWidth, imgHeight);
} catch(e) { /* ignore error for 0-dim canvas */ }
}
return outputCanvas; // Returns blank canvas
}
// Draw background color
if (backgroundColor && backgroundColor.toLowerCase() !== "transparent") {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, imgWidth, imgHeight);
}
// If no particles to draw, return canvas with just background
if (numParticleCount <= 0) {
return outputCanvas;
}
// --- Pixel data acquisition ---
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = imgWidth;
offscreenCanvas.height = imgHeight;
const offscreenCtx = offscreenCanvas.getContext('2d', { willReadFrequently: true });
offscreenCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
let imageData;
try {
imageData = offscreenCtx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error getting image data (CORS issue or invalid image source?):", e);
// Fallback: draw original image onto output canvas if pixel processing fails
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight); // Overwrites background if it was set
return outputCanvas;
}
const pixels = imageData.data;
// --- Particle generation and drawing ---
for (let i = 0; i < numParticleCount; i++) {
// Pick a random source coordinate from the original image
const sourceX = Math.random() * imgWidth;
const sourceY = Math.random() * imgHeight;
const floorSourceX = Math.floor(sourceX);
const floorSourceY = Math.floor(sourceY);
// Calculate index for the 1D pixel array
const pixelIndex = (floorSourceY * imgWidth + floorSourceX) * 4;
// Safety check for pixel array access, though generally Math.floor(Math.random()*dim) is safe
if (pixelIndex < 0 || pixelIndex + 3 >= pixels.length) {
continue; // Should rarely happen with valid imgWidth/imgHeight
}
const r = pixels[pixelIndex];
const g = pixels[pixelIndex + 1];
const b = pixels[pixelIndex + 2];
const a = pixels[pixelIndex + 3];
// Calculate perceptual brightness (luminance)
const brightness = (r * 0.299 + g * 0.587 + b * 0.114);
if (brightness < numBrightnessThreshold) {
continue; // Skip particle if source pixel is too dark
}
// Determine particle size (randomly within min/max range)
const currentParticleSize = numParticleSizeMin + Math.random() * (numParticleSizeMax - numParticleSizeMin);
// Skip if particle size is too small to draw (radius would be half of this)
if (currentParticleSize < 0.1) {
continue;
}
// Determine particle alpha, modulated by alphaFactor
const particleAlpha = (a / 255) * numAlphaFactor;
// Skip if particle is effectively fully transparent
if (particleAlpha <= 0.001) { // Use a small threshold to avoid drawing nearly invisible particles
continue;
}
// Apply position jitter: random offset from source coordinate
const drawX = sourceX + (Math.random() - 0.5) * 2 * numPositionJitter;
const drawY = sourceY + (Math.random() - 0.5) * 2 * numPositionJitter;
// Ensure alpha is clamped between 0 and 1 for canvas context
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${Math.min(1, Math.max(0, particleAlpha))})`;
ctx.beginPath();
// Radius must be positive for ctx.arc
ctx.arc(drawX, drawY, currentParticleSize / 2, 0, 2 * Math.PI);
ctx.fill();
}
return outputCanvas;
}
Apply Changes