You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = 0.6, spillColorStr = "50,200,50,150", numSpots = 5, spotSizeMultiplier = 0.3, distortionAmount = 15) {
// Helper function for pseudo-random noise, [0, 1)
// Defined inside processImage to keep it self-contained.
function _simpleNoise(fx, fy, seed) {
const K1 = 12.9898;
const K2 = 78.233;
const K3 = 43758.5453123;
let n = fx * K1 + fy * K2 + seed;
n = Math.sin(n) * K3;
return n - Math.floor(n); // Fractional part, ensures [0, 1)
}
const canvas = document.createElement('canvas');
// Add willReadFrequently hint for potential performance improvement with getImageData
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, width, height);
// If image is empty or effect parameters are such that no change will occur, return early
if (width === 0 || height === 0 || (intensity === 0 && distortionAmount === 0) || numSpots <= 0 && intensity === 0) {
// numSpots <= 0 implies maxInfluence will be 0. If intensity is also 0, no color change.
// If distortionAmount is 0, no pixel shift.
// If numSpots is 0 and intensity is non-zero, there's still no spill color, so it behaves like intensity=0.
// Essentially, if no spots or spots have no effect (intensity=0) AND no distortion, then no change.
if (numSpots <=0 && distortionAmount === 0) return canvas; // no spots and no distortion at all
if (intensity === 0 && distortionAmount === 0) return canvas; // no effect intensity and no distortion
}
const originalImageData = ctx.getImageData(0, 0, width, height);
const originalData = originalImageData.data;
const outputImageData = ctx.createImageData(width, height);
const outputData = outputImageData.data;
// Parse spillColorStr: "R,G,B,A"
let spillR_val = 0, spillG_val = 0, spillB_val = 0, spillA_val = 255; // Default to opaque black
if (spillColorStr && typeof spillColorStr === 'string') {
const parts = spillColorStr.split(',');
spillR_val = parseInt(parts[0], 10);
spillG_val = parseInt(parts[1], 10);
spillB_val = parseInt(parts[2], 10);
if (parts.length > 3 && parts[3] !== undefined) { // Check parts[3] exists
spillA_val = parseInt(parts[3], 10);
}
}
// Validate and clamp color values (0-255)
// Set to default (0 for RGB, 255 for A) if parseInt resulted in NaN
spillR_val = Math.max(0, Math.min(255, isNaN(spillR_val) ? 0 : spillR_val));
spillG_val = Math.max(0, Math.min(255, isNaN(spillG_val) ? 0 : spillG_val));
spillB_val = Math.max(0, Math.min(255, isNaN(spillB_val) ? 0 : spillB_val));
spillA_val = Math.max(0, Math.min(255, isNaN(spillA_val) ? 255 : spillA_val));
const spillANorm = spillA_val / 255.0; // Normalized alpha for blending
// Generate spots for the spill effect
const spots = [];
const baseRadius = Math.max(1, Math.min(width, height) * spotSizeMultiplier);
for (let i = 0; i < numSpots; i++) {
spots.push({
x: Math.random() * width,
y: Math.random() * height,
// Randomize radius slightly for variety: 75% to 125% of baseRadius
radius: baseRadius * (0.75 + Math.random() * 0.5),
});
}
// Pre-generate noise seeds for consistent noise patterns within this call
const noiseSeedAngle = Math.random() * 1000;
const noiseSeedMagnitude = Math.random() * 1000; // Separate seed for magnitude complexity
// Process_simpleNoise each pixel
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let maxInfluence = 0;
if (numSpots > 0) {
for (const spot of spots) {
const dx_spot = x - spot.x;
const dy_spot = y - spot.y;
// Use squared distance for efficiency until sqrt is necessary
const distSq = dx_spot * dx_spot + dy_spot * dy_spot;
const radiusSq = spot.radius * spot.radius;
if (distSq < radiusSq) {
const dist = Math.sqrt(distSq); // Calculate sqrt only if pixel is within spot's radius
// Influence falloff: (1 - dist/radius)^power. Power > 1 gives a sharper effect at the center.
const influence = Math.pow(1 - dist / spot.radius, 1.5);
if (influence > maxInfluence) {
maxInfluence = influence;
}
}
}
}
const currentEffectIntensity = maxInfluence * intensity;
let r_final, g_final, b_final, a_final;
let srcX = x;
let srcY = y;
// Apply distortion if distortionAmount is set and there's some effect intensity
if (distortionAmount > 0 && currentEffectIntensity > 0.001) {
const noiseScaleXY = 0.03; // Scale for coordinate input to noise (affects "waviness")
const noiseScaleMagnitude = 0.05;
const angle = _simpleNoise(x * noiseScaleXY, y * noiseScaleXY, noiseSeedAngle) * Math.PI * 2;
// Another noise layer for magnitude variation
const magnitudeRandomPart = _simpleNoise(x * noiseScaleMagnitude + 10.0, y * noiseScaleMagnitude + 20.0, noiseSeedMagnitude);
// Distortion strength is proportional to overall effect intensity and current pixel's influence
const effectiveDistortion = distortionAmount * currentEffectIntensity;
const displacementMag = magnitudeRandomPart * effectiveDistortion;
const displacementDx = Math.cos(angle) * displacementMag;
const displacementDy = Math.sin(angle) * displacementMag;
srcX = Math.round(x + displacementDx);
srcY = Math.round(y + displacementDy);
// Clamp source coordinates to be within image bounds
srcX = Math.max(0, Math.min(width - 1, srcX));
srcY = Math.max(0, Math.min(height - 1, srcY));
}
// Get original (possibly displaced) pixel color
const originalPixelIndex = (srcY * width + srcX) * 4;
const r_orig = originalData[originalPixelIndex];
const g_orig = originalData[originalPixelIndex + 1];
const b_orig = originalData[originalPixelIndex + 2];
const a_orig = originalData[originalPixelIndex + 3];
// Apply color tinting if there's effect intensity
if (currentEffectIntensity > 0.001 && spillANorm > 0) { // Check spillANorm to avoid NOP math
// Alpha blending: final = original * (1 - mix) + spill * mix
const spillMixAmount = spillANorm * currentEffectIntensity;
r_final = r_orig * (1 - spillMixAmount) + spillR_val * spillMixAmount;
g_final = g_orig * (1 - spillMixAmount) + spillG_val * spillMixAmount;
b_final = b_orig * (1 - spillMixAmount) + spillB_val * spillMixAmount;
} else {
// No color tinting, use original (possibly displaced) color
r_final = r_orig;
g_final = g_orig;
b_final = b_orig;
}
a_final = a_orig; // Preserve original alpha of the sampled pixel
// Set the final pixel color in the output data
const outputPixelIndex = (y * width + x) * 4;
outputData[outputPixelIndex] = r_final;
outputData[outputPixelIndex + 1] = g_final;
outputData[outputPixelIndex + 2] = b_final;
outputData[outputPixelIndex + 3] = a_final;
}
}
// Put the modified image data back onto the canvas
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Apply Changes