#

API Documentation for: NEXT
Show: Inherited Protected Private Deprecated

# File:BitmapCache.js

    /*
    * Filter
    * 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";

    // constructor:
    	/**
    	 * The BitmapCache is an internal representation of all the cache properties and logic required in order to "cache"
    	 * an object. This information and functionality used to be located on a {{#crossLink "DisplayObject/cache"}}{{/crossLink}}
    	 * method in {{#crossLink "DisplayObject"}}{{/crossLink}}, but was moved to its own class.
    	 *
    	 * Caching in this context is purely visual, and will render the DisplayObject out into an image to be used instead
    	 * of the object. The actual cache itself is still stored on the target with the {{#crossLink "DisplayObject/cacheCanvas:property"}}{{/crossLink}}.
    	 * Working with a singular image like a {{#crossLink "Bitmap"}}{{/crossLink}} there is little benefit to performing
    	 * a cache as it is already a single image. Caching is best done on containers containing multiple complex parts that
    	 * do not move often, so that rendering the image instead will improve overall rendering speed. A cached object will
    	 * not visually update until explicitly told to do so with a call to update, much like a Stage. If a cache is being
    	 * updated every frame it is likely not improving rendering performance. Cache are best used when updates will be sparse.
    	 *
    	 * Caching is also a co-requisite for applying filters to prevent expensive filters running constantly without need,
    	 * and to physically enable some effects. The BitmapCache is also responsible for applying filters to objects and
    	 * reads each {{#crossLink "Filter"}}{{/crossLink}} due to this relationship. Real-time Filters are not recommended
    	 * performance wise when dealing with a Context2D canvas. For best performance and to still allow for some visual
    	 * effects use a compositeOperation when possible.
    	 * @class BitmapCache
    	 * @constructor
    	 **/
    	function BitmapCache() {

    		// public:
    		/**
    		 * Width of the cache relative to the target object.
    		 * @property width
    		 * @protected
    		 * @type {Number}
    		 * @default undefined
    		 **/
    		this.width = undefined;

    		/**
    		 * Height of the cache relative to the target object.
    		 * @property height
    		 * @protected
    		 * @type {Number}
    		 * @default undefined
    		 * @todo Should the width and height be protected?
    		 **/
    		this.height = undefined;

    		/**
    		 * Horizontal position of the cache relative to the target's origin.
    		 * @property x
    		 * @protected
    		 * @type {Number}
    		 * @default undefined
    		 **/
    		this.x = undefined;

    		/**
    		 * Vertical position of the cache relative to target's origin.
    		 * @property y
    		 * @protected
    		 * @type {Number}
    		 * @default undefined
    		 **/
    		this.y = undefined;

    		/**
    		 * The internal scale of the cache image, does not affects display size. This is useful to both increase and
    		 * decrease render quality. Objects with increased scales are more likely to look good when scaled up or rotated.
    		 * Objects with decreased scales can save on rendering performance.
    		 * @property scale
    		 * @protected
    		 * @type {Number}
    		 * @default 1
    		 **/
    		this.scale = 1;

    		/**
    		 * The x offset used for drawing into the cache itself, accounts for both transforms applied.
    		 * @property offX
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this.offX = 0;

    		/**
    		 * The y offset used for drawing into the cache itself, accounts for both transforms applied.
    		 * @property offY
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this.offY = 0;

    		/**
    		 * Track how many times the cache has been updated, mostly used for preventing duplicate cacheURLs.
    		 * This can be useful to see if a cache has been updated.
    		 * @property cacheID
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this.cacheID = 0;

    		// protected:
    		/**
    		 * The relative offset of the filter's x position, used for drawing the cache onto its container.
    		 * Re-calculated every update call before drawing.
    		 * @property _filterOffY
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this._filterOffX = 0;

    		/**
    		 * The relative offset of the filter's y position, used for drawing the cache onto its container.
    		 * Re-calculated every update call before drawing.
    		 * @property _filterOffY
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this._filterOffY = 0;

    		/**
    		 * The cacheID when a DataURL was requested.
    		 * @property _cacheDataURLID
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this._cacheDataURLID = 0;

    		/**
    		 * The cache's DataURL, generated on-demand using the getter.
    		 * @property _cacheDataURL
    		 * @protected
    		 * @type {String}
    		 * @default null
    		 **/
    		this._cacheDataURL = null;

    		/**
    		 * Internal tracking of final bounding width, approximately width*scale; however, filters can complicate the actual value.
    		 * @property _drawWidth
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this._drawWidth = 0;

    		/**
    		 * Internal tracking of final bounding height, approximately height*scale; however, filters can complicate the actual value.
    		 * @property _drawHeight
    		 * @protected
    		 * @type {Number}
    		 * @default 0
    		 **/
    		this._drawHeight = 0;
    	}
    	var p = BitmapCache.prototype;

    	/**
    	 * Returns the bounds that surround all applied filters, relies on each filter to describe how it changes bounds.
    	 * @method getFilterBounds
    	 * @param {DisplayObject} target The object to check the filter bounds for.
    	 * @param {Rectangle} [output=null] Optional parameter, if provided then calculated bounds will be applied to that object.
    	 * @return {Rectangle} bounds object representing the bounds with filters.
    	 * @static
    	 **/
    	BitmapCache.getFilterBounds = function(target, output) {
    		if(!output){ output = new createjs.Rectangle(); }
    		var filters = target.filters;
    		var filterCount = filters && filters.length;
    		if (!!filterCount <= 0) { return output; }

    		for(var i=0; i<filterCount; i++) {
    			var f = filters[i];
    			if(!f || !f.getBounds){ continue; }
    			var test = f.getBounds();
    			if(!test){ continue; }
    			if(i==0) {
    				output.setValues(test.x, test.y, test.width, test.height);
    			} else {
    				output.extend(test.x, test.y, test.width, test.height);
    			}
    		}

    		return output;
    	};

    // public methods:
    	/**
    	 * Returns a string representation of this object.
    	 * @method toString
    	 * @return {String} a string representation of the instance.
    	 **/
    	p.toString = function() {
    		return "[BitmapCache]";
    	};

    	/**
    	 * Actually create the correct cache surface and properties associated with it. Caching and it's benefits are discussed
    	 * by the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} function and this class description. Here are the detailed
    	 * specifics of how to use the options object.
    	 *
    	 * - If options.useGL is set to "new" a StageGL is created and contained on this for use when rendering the cache.
    	 * - If options.useGL is set to "stage" if the current stage is a StageGL it will be used. If not then it will default to "new".
    	 * - If options.useGL is a StageGL instance it will not create one but use the one provided.
    	 * - If options.useGL is undefined a Context 2D cache will be performed.
    	 *
    	 * This means you can use any combination of StageGL and 2D with either, neither, or both the stage and cache being
    	 * WebGL. Using "new" with a StageGL display list is highly unrecommended, but still an option. It should be avoided
    	 * due to negative performance reasons and the Image loading limitation noted in the class complications above.
    	 *
    	 * When "options.useGL" is set to the parent stage of the target and WebGL, performance is increased by using
    	 * "RenderTextures" instead of canvas elements. These are internal Textures on the graphics card stored in the GPU.
    	 * Because they are no longer canvases you cannot perform operations you could with a regular canvas. The benefit
    	 * is that this avoids the slowdown of copying the texture back and forth from the GPU to a Canvas element.
    	 * This means "stage" is the recommended option when available.
    	 *
    	 * A StageGL cache does not infer the ability to draw objects a StageGL cannot currently draw, i.e. do not use a
    	 * WebGL context cache when caching a Shape, Text, etc.
    	 * <h4>WebGL cache with a 2D context</h4>
    	 *
    	 *     var stage = new createjs.Stage();
    	 *     var bmp = new createjs.Bitmap(src);
    	 *     bmp.cache(0, 0, bmp.width, bmp.height, 1, {gl: "new"});          // no StageGL to use, so make one
    	 *
    	 *     var shape = new createjs.Shape();
    	 *     shape.graphics.clear().fill("red").drawRect(0,0,20,20);
    	 *     shape.cache(0, 0, 20, 20, 1);                             // cannot use WebGL cache
    	 *
    	 * <h4>WebGL cache with a WebGL context</h4>
    	 *
    	 *     var stageGL = new createjs.StageGL();
    	 *     var bmp = new createjs.Bitmap(src);
    	 *     bmp.cache(0, 0, bmp.width, bmp.height, 1, {gl: "stage"});       // use our StageGL to cache
    	 *
    	 *     var shape = new createjs.Shape();
    	 *     shape.graphics.clear().fill("red").drawRect(0,0,20,20);
    	 *     shape.cache(0, 0, 20, 20, 1);                             // cannot use WebGL cache
    	 *
    	 * You may wish to create your own StageGL instance to control factors like clear color, transparency, AA, and
    	 * others. If you do, pass a new instance in instead of "true", the library will automatically set the
    	 * {{#crossLink "StageGL/isCacheControlled"}}{{/crossLink}} to true on your instance. This will trigger it to behave
    	 * correctly, and not assume your main context is WebGL.
    	 *
    	 * @public
    	 * @method BitmapCache.cache
    	 * @param {Number} x The x coordinate origin for the cache region.
    	 * @param {Number} y The y coordinate origin for the cache region.
    	 * @param {Number} width The width of the cache region.
    	 * @param {Number} height The height of the cache region.
    	 * @param {Number} [scale=1] The scale at which the cache will be created. For example, if you cache a vector shape
    	 * using myShape.cache(0,0,100,100,2) then the resulting cacheCanvas will be 200x200 px. This lets you scale and
    	 * rotate cached elements with greater fidelity. Default is 1.
    	 * @param {Object} [options=undefined] Specify additional parameters for the cache logic
    	 * @param {undefined|"new"|"stage"|StageGL} [options.useGL=undefined] Select whether to use context 2D, or WebGL rendering, and
    	 * whether to make a new stage instance or use an existing one. See above for extensive details on use.
    	 * @for BitmapCache
    	 */
    	 p.define = function(target, x, y, width, height, scale, options) {
    		if(!target){ throw "No symbol to cache"; }
    		this._options = options;
    		this.target = target;

    		this.width =		width >= 1 ? width : 1;
    		this.height =		height >= 1 ? height : 1;
    		this.x =			x || 0;
    		this.y =			y || 0;
    		this.scale =		scale || 1;

    		this.update();
    	};

    	/**
    	 * Directly called via {{#crossLink "DisplayObject/updateCache:method"}}{{/crossLink}}, but also internally. This
    	 * has the dual responsibility of making sure the surface is ready to be drawn to, and performing the draw. For
    	 * full details of each behaviour, check the protected functions {{#crossLink "BitmapCache/_updateSurface"}}{{/crossLink}}
    	 * and {{#crossLink "BitmapCache/_drawToCache"}}{{/crossLink}} respectively.
    	 * @method update
    	 * @param {String} [compositeOperation=null] The DisplayObject this cache is linked to.
    	 **/
    	p.update = function(compositeOperation) {
    		if(!this.target) { throw "define() must be called before update()"; }

    		var filterBounds = BitmapCache.getFilterBounds(this.target);
    		var surface = this.target.cacheCanvas;

    		this._drawWidth = Math.ceil(this.width*this.scale) + filterBounds.width;
    		this._drawHeight = Math.ceil(this.height*this.scale) + filterBounds.height;

    		if(!surface || this._drawWidth != surface.width || this._drawHeight != surface.height) {
    			this._updateSurface();
    		}

    		this._filterOffX = filterBounds.x;
    		this._filterOffY = filterBounds.y;
    		this.offX = this.x*this.scale + this._filterOffX;
    		this.offY = this.y*this.scale + this._filterOffY;

    		this._drawToCache(compositeOperation);

    		this.cacheID = this.cacheID?this.cacheID+1:1;
    	};

    	/**
    	 * Reset and release all the properties and memory associated with this cache.
    	 * @method release
    	 **/
    	p.release = function() {
    		if (this._webGLCache) {
    			// if it isn't cache controlled clean up after yourself
    			if (!this._webGLCache.isCacheControlled) {
    				if (this.__lastRT){ this.__lastRT = undefined; }
    				if (this.__rtA){ this._webGLCache._killTextureObject(this.__rtA); }
    				if (this.__rtB){ this._webGLCache._killTextureObject(this.__rtB); }
    				if (this.target && this.target.cacheCanvas){ this._webGLCache._killTextureObject(this.target.cacheCanvas); }
    			}
    			// set the context to none and let the garbage collector get the rest when the canvas itself gets removed
    			this._webGLCache = false;
    		} else {
    			var stage = this.target.stage;
    			if (stage instanceof createjs.StageGL){
    				stage.releaseTexture(this.target.cacheCanvas);
    			}
    		}

    		this.target = this.target.cacheCanvas = null;
    		this.cacheID = this._cacheDataURLID = this._cacheDataURL = undefined;
    		this.width = this.height = this.x = this.y = this.offX = this.offY = 0;
    		this.scale = 1;
    	};

    	/**
    	 * Returns a data URL for the cache, or `null` if this display object is not cached.
    	 * Uses {{#crossLink "BitmapCache/cacheID:property"}}{{/crossLink}} to ensure a new data URL is not generated if the
    	 * cache has not changed.
    	 * @method getCacheDataURL
    	 * @return {String} The image data url for the cache.
    	 **/
    	p.getCacheDataURL = function() {
    		var cacheCanvas = this.target && this.target.cacheCanvas;
    		if (!cacheCanvas) { return null; }
    		if (this.cacheID != this._cacheDataURLID) {
    			this._cacheDataURLID = this.cacheID;
    			this._cacheDataURL = cacheCanvas.toDataURL?cacheCanvas.toDataURL():null;	// incase function is
    		}
    		return this._cacheDataURL;
    	};

    	/**
    	 * Use context2D drawing commands to display the cache canvas being used.
    	 * @method draw
    	 * @param {CanvasRenderingContext2D} ctx The context to draw into.
    	 * @return {Boolean} Whether the draw was handled successfully.
    	 **/
    	p.draw = function(ctx) {
    		if(!this.target) { return false; }
    		ctx.drawImage(this.target.cacheCanvas,
    			this.x + (this._filterOffX/this.scale),		this.y + (this._filterOffY/this.scale),
    			this._drawWidth/this.scale,					this._drawHeight/this.scale
    		);
    		return true;
    	};

    // private methods:
    	/**
    	 * Create or resize the invisible canvas/surface that is needed for the display object(s) to draw to,
    	 * and in turn be used in their stead when drawing. The surface is resized to the size defined
    	 * by the width and height, factoring in scaling and filters. Adjust them to adjust the output size.
    	 * @method _updateSurface
    	 * @protected
    	 **/
    	p._updateSurface = function() {
    		if (!this._options || !this._options.useGL) {
    			var surface = this.target.cacheCanvas;

    			// create it if it's missing
    			if(!surface) {
    				surface = this.target.cacheCanvas = createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");
    			}

    			// now size it
    			surface.width = this._drawWidth;
    			surface.height = this._drawHeight;
    			return;
    		}

    		// create it if it's missing
    		if (!this._webGLCache) {
    			if (this._options.useGL === "stage") {
    				if(!(this.target.stage && this.target.stage.isWebGL)){
    					var error = "Cannot use 'stage' for cache because the object's parent stage is ";
    					error += this.target.stage ? "non WebGL." : "not set, please addChild to the correct stage.";
    					throw error;
    				}
    				this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks
    				this._webGLCache = this.target.stage;

    			} else if(this._options.useGL === "new") {
    				this.target.cacheCanvas = document.createElement("canvas"); // we can turn off autopurge because we wont be making textures here
    				this._webGLCache = new createjs.StageGL(this.target.cacheCanvas, {antialias: true, transparent: true, autoPurge: -1});
    				this._webGLCache.isCacheControlled = true;	// use this flag to control stage sizing and final output

    			} else if(this._options.useGL instanceof createjs.StageGL) {
    				this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks
    				this._webGLCache = this._options.useGL;
    				this._webGLCache.isCacheControlled = true;	// use this flag to control stage sizing and final output

    			} else {
    				throw "Invalid option provided to useGL, expected ['stage', 'new', StageGL, undefined], got "+ this._options.useGL;
    			}
    		}

    		// now size render surfaces
    		var surface = this.target.cacheCanvas;
    		var stageGL = this._webGLCache;

    		// if we have a dedicated stage we've gotta size it
    		if (stageGL.isCacheControlled) {
    			surface.width = this._drawWidth;
    			surface.height = this._drawHeight;
    			stageGL.updateViewport(this._drawWidth, this._drawHeight);
    		}
    		if (this.target.filters) {
    			// with filters we can't tell how many we'll need but the most we'll ever need is two, so make them now
    			stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight);
    			stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight);
    		} else {
    			// without filters then we only need one RenderTexture, and that's only if its not a dedicated stage
    			if (!stageGL.isCacheControlled) {
    				stageGL.getTargetRenderTexture(this.target, this._drawWidth,this._drawHeight);
    			}
    		}
    	};

    	/**
    	 * Perform the cache draw out for context 2D now that the setup properties have been performed.
    	 * @method _drawToCache
    	 * @protected
    	 **/
    	p._drawToCache = function(compositeOperation) {
    		var surface = this.target.cacheCanvas;
    		var target = this.target;
    		var webGL = this._webGLCache;

    		if (webGL){
    			//TODO: auto split blur into an x/y pass
    			webGL.cacheDraw(target, target.filters, this);

    			// we may of swapped around which element the surface is, so we re-fetch it
    			surface = this.target.cacheCanvas;

    			surface.width = this._drawWidth;
    			surface.height = this._drawHeight;
    		} else {
    			var ctx = surface.getContext("2d");

    			if (!compositeOperation) {
    				ctx.clearRect(0, 0, this._drawWidth+1, this._drawHeight+1);
    			}

    			ctx.save();
    			ctx.globalCompositeOperation = compositeOperation;
    			ctx.setTransform(this.scale,0,0,this.scale, -this._filterOffX,-this._filterOffY);
    			ctx.translate(-this.x, -this.y);
    			target.draw(ctx, true);
    			ctx.restore();

    			if (target.filters && target.filters.length) {
    				this._applyFilters(ctx);
    			}
    		}
    		surface._invalid = true;
    	};

    	/**
    	 * Work through every filter and apply its individual visual transformation.
    	 * @method _applyFilters
    	 * @protected
    	 **/
    	p._applyFilters = function(ctx) {
    		var filters = this.target.filters;

    		var w = this._drawWidth;
    		var h = this._drawHeight;

    		var data;

    		var i = 0, filter = filters[i];
    		do { // this is safe because we wouldn't be in apply filters without a filter count of at least 1
    			if(filter.usesContext){
    				if(data) {
    					ctx.putImageData(data, 0,0);
    					data = null;
    				}
    				filter.applyFilter(ctx, 0,0, w,h);
    			} else {
    				if(!data) {
    					data = ctx.getImageData(0,0, w,h);
    				}
    				filter._applyFilter(data);
    			}

    			// work through the multipass if it's there, otherwise move on
    			filter = filter._multiPass !== null ? filter._multiPass : filters[++i];
    		} while (filter);

    		//done
    		if(data) {
    			ctx.putImageData(data, 0,0);
    		}
    	};

    	createjs.BitmapCache = BitmapCache;
    }());