Bokeh Effect


Bokeh = function( container, width, height )
{
    var _blobCount = 20;
    var _pool = [];
    var _blobs = [];

    var _width = width;
    var _height = height;

    var _mainCanvas;
    var _mainContext;

    var _tempCanvas;
    var _tempContext;
    var _tempScale = 2;

    var _hOffset = 0.2;

    //...

    function reset()
    {
        _mainCanvas.width = _width;
        _mainCanvas.height = _height;

        _tempCanvas.width = _width /_tempScale;
        _tempCanvas.height = _height /_tempScale;

        _mainContext = _mainCanvas.getContext( '2d' );
        _mainContext.clearRect( 0, 0, _mainCanvas.width, _mainCanvas.height );

        _tempContext = _tempCanvas.getContext( '2d' );
        _tempContext.clearRect( 0, 0, _tempCanvas.width / _tempScale, _tempCanvas.height / _tempScale );
    }

    //...

    function render()
    {
        _mainContext.clearRect( 0, 0, _mainCanvas.width, _mainCanvas.height );

        _tempContext.clearRect( 0, 0, _tempCanvas.width, _tempCanvas.height );

        _tempContext.globalCompositeOperation = "lighter";

        var n = _blobs.length;

        while( --n > -1 )
        {
            var blob = _blobs[ n ];

            var expired = blob.paint( _tempContext );

            if( expired )
            {
                expireBlob( blob );
            }
        }

        if( _blobs.length < _blobCount && Math.random() > 0.8 )
        {
            var x = Math.random() * _width;
            var y = Math.random() * _height;

            _hOffset = ( _hOffset + 0.005 ) % 1;

            var h = _hOffset + ( x / _width ) * 0.3;
            var s = 0.6 + Math.random() * 0.4;
            var v = 0.3;

            var c = hsv2rgb( h, s, v );

            createBlob( x / _tempScale, y / _tempScale, c );
        }

        var imageData = _tempContext.getImageData( 0, 0, _tempCanvas.width, _tempCanvas.height );

        var data = imageData.data;

        for( var iteration = 0; iteration < 2; ++iteration )
        {
            for( var i = 0, m = data.length; i < m; )
            {
                var a = 0;
                var r = 0;
                var g = 0;
                var b = 0;
                var total = 0;
                var off = 4;

                // indices 8 surrounding pixels
                var data8 = [
                    i - off - 4, i - off, i - off + 4, // top
                    i - 4, i + 4, // middle
                    i + off - 4, i + off, i + off + 4 // bottom
                ];

                // calculating Sum value of all close pixels
                for( var e = 0; e < data8.length; e += 1 )
                {
                    if( data8[ e ] >= 0 && data8[ e ] <= data.length - 3 )
                    {
                        a += data[ data8[ e ] ];
                        r += data[ data8[ e ] + 1 ] + 2;
                        g += data[ data8[ e ] + 2 ] + 2;
                        b += data[ data8[ e ] + 3 ] + 2;
                        total += 1;
                    }
                }

                data[ i++ ] = (a / total);
                data[ i++ ] = (r / total);
                data[ i++ ] = (g / total);
                data[ i++ ] = (b / total);
            }
        }

        _tempContext.putImageData( imageData, 0, 0 );

        _mainContext.drawImage( _tempCanvas, 0, 0, _tempCanvas.width, _tempCanvas.height, 0,0, _mainCanvas.width, _mainCanvas.height );

        setTimeout( render, 1000 / 60 );
    }

    init();
};

Blob = function()
{
    var _x;
    var _y;
    var _size = 0;

    var _r = 255;
    var _g = 255;
    var _b = 255;

    var _vx = 0;
    var _vy = 0;

    var _alpha = 1;

    var _lifeSpan = 0;
    var _age = 0;

    this.reset = function( x, y, color )
    {
        _x = x;
        _y = y;
        _r = color[ 0 ];
        _g = color[ 1 ];
        _b = color[ 2 ];

        _vx = ( Math.random() - Math.random() ) * 0.3;
        _vy = ( Math.random() - Math.random() ) * 0.3;

        _size = 15 + Math.random() * 10;
        _lifeSpan = 64 + Math.random() * 256;
        _age = 0;
        _alpha = 1;
    };

    this.paint = function( context )
    {
        _x += _vx;
        _y += _vy;

        _size += 0.1;

        _alpha = Math.sin( ( 1.0 - ( _age / _lifeSpan ) ) * Math.PI );

        var fillAlpha = _alpha * 0.8;
        var strokeAlpha = 0.2 + _alpha * 0.8;

        _r += 0.5;
        _g += 0.5;
        _b += 0.5;

        var r = Math.floor( Math.min( 255, _r ) );
        var g = Math.floor( Math.min( 255, _g ) );
        var b = Math.floor( Math.min( 255, _b ) );

        context.beginPath();
        context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + strokeAlpha + ")";
        context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + fillAlpha + ")";
        context.lineWidth = 1.5;
        context.arc( _x, _y, _size, 0, Math.PI * 2, false );
        context.fill();
        context.stroke();
        context.closePath();

        _age++;

        return ( _age >= _lifeSpan )
    };
};