Please bookmark this page to avoid losing your image tool!

Image Beer Foam 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.
async function processImage(originalImg, foamIntensity = 0.9, noiseStrength = 50, noiseScale = 2, foamBaseColorStr = "255,250,230") {
    // foamIntensity (0-1): How much the foam effect overtakes the original. 0 for original, 1 for full foam.
    // noiseStrength (0-255): Max deviation for random noise for grain/bubble texture. Noise will be in [-noiseStrength/2, +noiseStrength/2].
    // noiseScale (integer >= 1): Size of noise blocks. 1 for pixel-level noise, >1 for clumpier noise.
    // foamBaseColorStr ("r,g,b"): The dominant color of the foam (e.g., "255,250,230" for creamy off-white).

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        // Handle zero-size image gracefully, return empty canvas
        return canvas;
    }

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;

    let parsedFoamBaseColor = foamBaseColorStr.split(',').map(s => parseInt(s.trim(), 10));
    if (parsedFoamBaseColor.length !== 3 || parsedFoamBaseColor.some(isNaN)) {
        console.warn("Invalid foamBaseColorStr, defaulting to 255,250,230. Received: " + foamBaseColorStr);
        parsedFoamBaseColor = [255, 250, 230];
    }
    // Clamp base color components to valid 0-255 range
    const baseR = Math.max(0, Math.min(255, parsedFoamBaseColor[0]));
    const baseG = Math.max(0, Math.min(255, parsedFoamBaseColor[1]));
    const baseB = Math.max(0, Math.min(255, parsedFoamBaseColor[2]));

    const width = canvas.width;
    const height = canvas.height;

    const effectiveNoiseScale = Math.max(1, Math.floor(noiseScale));
    const clampedNoiseStrength = Math.max(0, noiseStrength);

    // Generate random noise map for blocky noise if noiseScale > 1 and noise is active
    const useBlockNoise = effectiveNoiseScale > 1 && clampedNoiseStrength > 0;
    let noiseMap;
    let noiseMapWidth = 0; // Initialize to avoid potential issues if not set

    if (useBlockNoise) {
        noiseMapWidth = Math.ceil(width / effectiveNoiseScale);
        const noiseMapHeight = Math.ceil(height / effectiveNoiseScale);
        noiseMap = new Float32Array(noiseMapWidth * noiseMapHeight);
        for (let i = 0; i < noiseMap.length; i++) {
            noiseMap[i] = (Math.random() - 0.5); // Noise from -0.5 to 0.5
        }
    }
    
    // K_lum_mod: Factor for how much original luminance affects foam brightness variation.
    // Higher values mean original image structure is more visible in foam brightness.
    const K_lum_mod = 0.4; 

    const clampedFoamIntensity = Math.max(0, Math.min(1, foamIntensity));

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const i = (y * width + x) * 4;
            const r_orig = data[i];
            const g_orig = data[i + 1];
            const b_orig = data[i + 2];

            // 1. Calculate original pixel's luminance
            const luminance_orig = 0.299 * r_orig + 0.587 * g_orig + 0.114 * b_orig;

            // 2. Determine the foam color for this pixel, modulated by original luminance
            // Calculate luminance of the foamBaseColor itself
            const baseLuminance = 0.299 * baseR + 0.587 * baseG + 0.114 * baseB;

            // Modulate foam brightness based on original pixel's luminance
            // This means darker original pixels result in slightly darker foam, brighter ones in brighter foam,
            // all relative to the foamBaseColor's brightness.
            let currentFoamTargetLuminance = baseLuminance + (luminance_orig - 128) * K_lum_mod;
            currentFoamTargetLuminance = Math.max(0, Math.min(255, currentFoamTargetLuminance));
            
            let foamR_calc, foamG_calc, foamB_calc;

            if (baseLuminance > 0.001) { // If base color is not black, scale its components
                const scaleFactor = currentFoamTargetLuminance / baseLuminance;
                foamR_calc = baseR * scaleFactor;
                foamG_calc = baseG * scaleFactor;
                foamB_calc = baseB * scaleFactor;
            } else { // If base color is black (or very dark), foam color becomes grayscale based on target luminance
                foamR_calc = currentFoamTargetLuminance;
                foamG_calc = currentFoamTargetLuminance;
                foamB_calc = currentFoamTargetLuminance;
            }

            // 3. Add noise/grain for texture
            let noiseVal = 0;
            if (clampedNoiseStrength > 0) {
                if (useBlockNoise) {
                    const noiseMapX = Math.floor(x / effectiveNoiseScale);
                    const noiseMapY = Math.floor(y / effectiveNoiseScale);
                    // Ensure noiseMapX and noiseMapY are within bounds for safety, though Math.floor should handle it
                    const safeNoiseMapX = Math.max(0, Math.min(noiseMapX, noiseMapWidth - 1));
                    // noiseMapHeight is implicitly defined by noiseMap.length / noiseMapWidth
                    noiseVal = noiseMap[noiseMapY * noiseMapWidth + safeNoiseMapX] * clampedNoiseStrength;
                } else { // Pixel-level noise (if noiseScale is 1 or effectiveNoiseScale becomes 1)
                    noiseVal = (Math.random() - 0.5) * clampedNoiseStrength;
                }
            }
            
            let foamR_final = foamR_calc + noiseVal;
            let foamG_final = foamG_calc + noiseVal;
            let foamB_final = foamB_calc + noiseVal;

            // Clamp foam colors after adding noise
            foamR_final = Math.max(0, Math.min(255, foamR_final));
            foamG_final = Math.max(0, Math.min(255, foamG_final));
            foamB_final = Math.max(0, Math.min(255, foamB_final));

            // 4. Blend with original based on foamIntensity
            data[i]     = Math.round(r_orig * (1 - clampedFoamIntensity) + foamR_final * clampedFoamIntensity);
            data[i + 1] = Math.round(g_orig * (1 - clampedFoamIntensity) + foamG_final * clampedFoamIntensity);
            data[i + 2] = Math.round(b_orig * (1 - clampedFoamIntensity) + foamB_final * clampedFoamIntensity);
            // Alpha (data[i+3]) remains unchanged
        }
    }

    ctx.putImageData(imageData, 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 Beer Foam Filter Effect Tool allows users to apply a unique foam effect to their images, simulating the appearance of beer foam with adjustable intensity and noise. Users can customize the foam’s color, controlling its base hue and the level of texture, making it suitable for enhancing photographs for social media, crafting custom graphics, or adding playful elements to visual content. This tool can be particularly useful for food and beverage marketers, graphic designers, and anyone looking to create visually appealing images that capture the essence of refreshing drinks.

Leave a Reply

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