Please bookmark this page to avoid losing your image tool!

Image Chemical Spill Filter Effect Tool

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Chemical Spill Filter Effect Tool provides a way to simulate a chemical spill effect on images. Users can customize the intensity of the effect, the color of the spill, the number of spill spots, and the degree of distortion applied to the original image. This tool can be useful for graphic designers, artists, and video game developers looking to create visually striking images with a unique and stylized chemical spill aesthetic. It allows for creative experimentation with color blending and distortion, making it ideal for enhancing digital artwork or creating thematic visual effects.

Leave a Reply

Your email address will not be published. Required fields are marked *