#

API Documentation for: NEXT
Show: Inherited Protected Private Deprecated

# File:BlurFilter.js

    /*
    * BlurFilter
    * Visit http://createjs.com/ for documentation, updates and examples.
    *
    * Copyright (c) 2010 gskinner.com, inc.
    *
    * Permission is hereby granted, free of charge, to any person
    * obtaining a copy of this software and associated documentation
    * files (the "Software"), to deal in the Software without
    * restriction, including without limitation the rights to use,
    * copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the
    * Software is furnished to do so, subject to the following
    * conditions:
    *
    * The above copyright notice and this permission notice shall be
    * included in all copies or substantial portions of the Software.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    * OTHER DEALINGS IN THE SOFTWARE.
    */

    /**
     * @module EaselJS
     */

    // namespace:
    this.createjs = this.createjs||{};

    (function() {
    	"use strict";

    /**
     * Applies a box blur to DisplayObjects. Note that this filter is fairly CPU intensive, particularly if the quality is
     * set higher than 1.
     *
     * <h4>Example</h4>
     * This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}}
     * method to account for the spread that the blur causes.
     *
     *      var shape = new createjs.Shape().set({x:100,y:100});
     *      shape.graphics.beginFill("#ff0000").drawCircle(0,0,50);
     *
     *      var blurFilter = new createjs.BlurFilter(5, 5, 1);
     *      shape.filters = [blurFilter];
     *      var bounds = blurFilter.getBounds();
     *
     *      shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height);
     *
     * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters.
     * @class BlurFilter
     * @extends Filter
     * @constructor
     * @param {Number} [blurX=0] The horizontal blur radius in pixels.
     * @param {Number} [blurY=0] The vertical blur radius in pixels.
     * @param {Number} [quality=1] The number of blur iterations.
     **/
    var BlurFilter = function( blurX, blurY, quality ) {
      this.initialize( blurX, blurY, quality );
    };
    var p = BlurFilter.prototype = new createjs.Filter();

    // constructor:
    	/** @ignore */
    	p.initialize = function( blurX, blurY, quality ) {
    		if ( isNaN(blurX) || blurX < 0 ) blurX = 0;
    		this.blurX = blurX | 0;
    		if ( isNaN(blurY) || blurY < 0 ) blurY = 0;
    		this.blurY = blurY | 0;
    		if ( isNaN(quality) || quality < 1  ) quality = 1;
    		this.quality = quality | 0;
    	};

    // public properties:

    	/**
    	 * Horizontal blur radius in pixels
    	 * @property blurX
    	 * @default 0
    	 * @type Number
    	 **/
    	p.blurX = 0;

    	/**
    	 * Vertical blur radius in pixels
    	 * @property blurY
    	 * @default 0
    	 * @type Number
    	 **/
    	p.blurY = 0;

    	/**
    	 * Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a
    	 * smoother blur, but take twice as long to run.
    	 * @property quality
    	 * @default 1
    	 * @type Number
    	 **/
    	p.quality = 1;

    	//TODO: There might be a better better way to place these two lookup tables:
    	p.mul_table = [ 1,171,205,293,57,373,79,137,241,27,391,357,41,19,283,265,497,469,443,421,25,191,365,349,335,161,155,149,9,278,269,261,505,245,475,231,449,437,213,415,405,395,193,377,369,361,353,345,169,331,325,319,313,307,301,37,145,285,281,69,271,267,263,259,509,501,493,243,479,118,465,459,113,446,55,435,429,423,209,413,51,403,199,393,97,3,379,375,371,367,363,359,355,351,347,43,85,337,333,165,327,323,5,317,157,311,77,305,303,75,297,294,73,289,287,71,141,279,277,275,68,135,67,133,33,262,260,129,511,507,503,499,495,491,61,121,481,477,237,235,467,232,115,457,227,451,7,445,221,439,218,433,215,427,425,211,419,417,207,411,409,203,202,401,399,396,197,49,389,387,385,383,95,189,47,187,93,185,23,183,91,181,45,179,89,177,11,175,87,173,345,343,341,339,337,21,167,83,331,329,327,163,81,323,321,319,159,79,315,313,39,155,309,307,153,305,303,151,75,299,149,37,295,147,73,291,145,289,287,143,285,71,141,281,35,279,139,69,275,137,273,17,271,135,269,267,133,265,33,263,131,261,130,259,129,257,1];

    	p.shg_table = [0,9,10,11,9,12,10,11,12,9,13,13,10,9,13,13,14,14,14,14,10,13,14,14,14,13,13,13,9,14,14,14,15,14,15,14,15,15,14,15,15,15,14,15,15,15,15,15,14,15,15,15,15,15,15,12,14,15,15,13,15,15,15,15,16,16,16,15,16,14,16,16,14,16,13,16,16,16,15,16,13,16,15,16,14,9,16,16,16,16,16,16,16,16,16,13,14,16,16,15,16,16,10,16,15,16,14,16,16,14,16,16,14,16,16,14,15,16,16,16,14,15,14,15,13,16,16,15,17,17,17,17,17,17,14,15,17,17,16,16,17,16,15,17,16,17,11,17,16,17,16,17,16,17,17,16,17,17,16,17,17,16,16,17,17,17,16,14,17,17,17,17,15,16,14,16,15,16,13,16,15,16,14,16,15,16,12,16,15,16,17,17,17,17,17,13,16,15,17,17,17,16,15,17,17,17,16,15,17,17,14,16,17,17,16,17,17,16,15,17,16,14,17,16,15,17,16,17,17,16,17,15,16,17,14,17,16,15,17,16,17,13,17,16,17,17,16,17,14,17,16,17,16,17,16,17,9];

    // public methods:
    	/** docced in super class **/
    	p.getBounds = function() {
    		var q = Math.pow(this.quality, 0.6)*0.5;
    		return new createjs.Rectangle(-this.blurX*q,-this.blurY*q,2*this.blurX*q,2*this.blurY*q);
    	};

    	p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) {
    		targetCtx = targetCtx || ctx;
    		if (targetX == null) { targetX = x; }
    		if (targetY == null) { targetY = y; }
    		try {
    			var imageData = ctx.getImageData(x, y, width, height);
    		} catch(e) {
    			//if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e);
    			return false;
    		}

    		var radiusX = this.blurX/2;
    		if ( isNaN(radiusX) || radiusX < 0 ) return false;
    		radiusX |= 0;

    		var radiusY = this.blurY/2;
    		if ( isNaN(radiusY) || radiusY < 0 ) return false;
    		radiusY |= 0;

    		if ( radiusX == 0 && radiusY == 0 ) return false;

    		var iterations = this.quality;
    		if ( isNaN(iterations) || iterations < 1  ) iterations = 1;
    		iterations |= 0;
    		if ( iterations > 3 ) iterations = 3;
    		if ( iterations < 1 ) iterations = 1;

    		var pixels = imageData.data;

    		var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum,
    		r_out_sum, g_out_sum, b_out_sum, a_out_sum,
    		r_in_sum, g_in_sum, b_in_sum, a_in_sum,
    		pr, pg, pb, pa, rbs;

    		var divx = radiusX + radiusX + 1;
    		var divy = radiusY + radiusY + 1;
    		var w4 = width << 2;
    		var widthMinus1  = width - 1;
    		var heightMinus1 = height - 1;
    		var rxp1  = radiusX + 1;
    		var ryp1  = radiusY + 1;
    		var stackStartX = {r:0,b:0,g:0,a:0,next:null};
    		var stackx = stackStartX;
    		for ( i = 1; i < divx; i++ )
    		{
    			stackx = stackx.next = {r:0,b:0,g:0,a:0,next:null};
    			if ( i == rxp1 ) var stackEndX = stackx;
    		}
    		stackx.next = stackStartX;

    		var stackStartY = {r:0,b:0,g:0,a:0,next:null};
    		var stacky = stackStartY;
    		for ( i = 1; i < divy; i++ )
    		{
    			stacky = stacky.next = {r:0,b:0,g:0,a:0,next:null};
    			if ( i == ryp1 ) var stackEndY = stacky;
    		}
    		stacky.next = stackStartY;

    		var stackIn = null;

    		while ( iterations-- > 0 ) {
    			yw = yi = 0;
    			var mul_sum = this.mul_table[radiusX];
    			var shg_sum = this.shg_table[radiusX];
    			for ( y = height; --y > -1; )
    			{
    				r_sum = rxp1 * ( pr = pixels[yi] );
    				g_sum = rxp1 * ( pg = pixels[yi+1] );
    				b_sum = rxp1 * ( pb = pixels[yi+2] );
    				a_sum = rxp1 * ( pa = pixels[yi+3] );

    				stackx = stackStartX;

    				for( i = rxp1; --i > -1; )
    				{
    					stackx.r = pr;
    					stackx.g = pg;
    					stackx.b = pb;
    					stackx.a = pa;
    					stackx = stackx.next;
    				}

    				for( i = 1; i < rxp1; i++ )
    				{
    					p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 );
    					r_sum += ( stackx.r = pixels[p]);
    					g_sum += ( stackx.g = pixels[p+1]);
    					b_sum += ( stackx.b = pixels[p+2]);
    					a_sum += ( stackx.a = pixels[p+3]);

    					stackx = stackx.next;
    				}

    				stackIn = stackStartX;
    				for ( x = 0; x < width; x++ )
    				{
    					pixels[yi++] = (r_sum * mul_sum) >>> shg_sum;
    					pixels[yi++] = (g_sum * mul_sum) >>> shg_sum;
    					pixels[yi++] = (b_sum * mul_sum) >>> shg_sum;
    					pixels[yi++] = (a_sum * mul_sum) >>> shg_sum;

    					p =  ( yw + ( ( p = x + radiusX + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2;

    					r_sum -= stackIn.r - ( stackIn.r = pixels[p]);
    					g_sum -= stackIn.g - ( stackIn.g = pixels[p+1]);
    					b_sum -= stackIn.b - ( stackIn.b = pixels[p+2]);
    					a_sum -= stackIn.a - ( stackIn.a = pixels[p+3]);

    					stackIn = stackIn.next;

    				}
    				yw += width;
    			}

    			mul_sum = this.mul_table[radiusY];
    			shg_sum = this.shg_table[radiusY];
    			for ( x = 0; x < width; x++ )
    			{
    				yi = x << 2;

    				r_sum = ryp1 * ( pr = pixels[yi]);
    				g_sum = ryp1 * ( pg = pixels[yi+1]);
    				b_sum = ryp1 * ( pb = pixels[yi+2]);
    				a_sum = ryp1 * ( pa = pixels[yi+3]);

    				stacky = stackStartY;

    				for( i = 0; i < ryp1; i++ )
    				{
    					stacky.r = pr;
    					stacky.g = pg;
    					stacky.b = pb;
    					stacky.a = pa;
    					stacky = stacky.next;
    				}

    				yp = width;

    				for( i = 1; i <= radiusY; i++ )
    				{
    					yi = ( yp + x ) << 2;

    					r_sum += ( stacky.r = pixels[yi]);
    					g_sum += ( stacky.g = pixels[yi+1]);
    					b_sum += ( stacky.b = pixels[yi+2]);
    					a_sum += ( stacky.a = pixels[yi+3]);

    					stacky = stacky.next;

    					if( i < heightMinus1 )
    					{
    						yp += width;
    					}
    				}

    				yi = x;
    				stackIn = stackStartY;
    				if ( iterations > 0 )
    				{
    					for ( y = 0; y < height; y++ )
    					{
    						p = yi << 2;
    						pixels[p+3] = pa =(a_sum * mul_sum) >>> shg_sum;
    						if ( pa > 0 )
    						{
    							pixels[p]   = ((r_sum * mul_sum) >>> shg_sum );
    							pixels[p+1] = ((g_sum * mul_sum) >>> shg_sum );
    							pixels[p+2] = ((b_sum * mul_sum) >>> shg_sum );
    						} else {
    							pixels[p] = pixels[p+1] = pixels[p+2] = 0
    						}

    						p = ( x + (( ( p = y + ryp1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2;

    						r_sum -= stackIn.r - ( stackIn.r = pixels[p]);
    						g_sum -= stackIn.g - ( stackIn.g = pixels[p+1]);
    						b_sum -= stackIn.b - ( stackIn.b = pixels[p+2]);
    						a_sum -= stackIn.a - ( stackIn.a = pixels[p+3]);

    						stackIn = stackIn.next;

    						yi += width;
    					}
    				} else {
    					for ( y = 0; y < height; y++ )
    					{
    						p = yi << 2;
    						pixels[p+3] = pa =(a_sum * mul_sum) >>> shg_sum;
    						if ( pa > 0 )
    						{
    							pa = 255 / pa;
    							pixels[p]   = ((r_sum * mul_sum) >>> shg_sum ) * pa;
    							pixels[p+1] = ((g_sum * mul_sum) >>> shg_sum ) * pa;
    							pixels[p+2] = ((b_sum * mul_sum) >>> shg_sum ) * pa;
    						} else {
    							pixels[p] = pixels[p+1] = pixels[p+2] = 0
    						}

    						p = ( x + (( ( p = y + ryp1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2;

    						r_sum -= stackIn.r - ( stackIn.r = pixels[p]);
    						g_sum -= stackIn.g - ( stackIn.g = pixels[p+1]);
    						b_sum -= stackIn.b - ( stackIn.b = pixels[p+2]);
    						a_sum -= stackIn.a - ( stackIn.a = pixels[p+3]);

    						stackIn = stackIn.next;

    						yi += width;
    					}
    				}
    			}
    		}
    		targetCtx.putImageData(imageData, targetX, targetY);
    		return true;
    	};

    	/**
    	 * Returns a clone of this object.
    	 * @method clone
    	 * @return {BlurFilter}
    	 **/
    	p.clone = function() {
    		return new BlurFilter(this.blurX, this.blurY, this.quality);
    	};

    	p.toString = function() {
    		return "[BlurFilter]";
    	};

    	createjs.BlurFilter = BlurFilter;

    }());