var BaseTexture = require('./BaseTexture'), Texture = require('./Texture'), RenderTarget = require('../renderers/webgl/utils/RenderTarget'), FilterManager = require('../renderers/webgl/managers/FilterManager'), CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), math = require('../math'), CONST = require('../const'), tempMatrix = new math.Matrix(); /** * A RenderTexture is a special texture that allows any Pixi display object to be rendered to it. * * __Hint__: All DisplayObjects (i.e. Sprites) that render to a RenderTexture should be preloaded * otherwise black rectangles will be drawn instead. * * A RenderTexture takes a snapshot of any Display Object given to its render method. The position * and rotation of the given Display Objects is ignored. For example: * * ```js * var renderTexture = new PIXI.RenderTexture(800, 600); * var sprite = PIXI.Sprite.fromImage("spinObj_01.png"); * * sprite.position.x = 800/2; * sprite.position.y = 600/2; * sprite.anchor.x = 0.5; * sprite.anchor.y = 0.5; * * renderTexture.render(sprite); * ``` * * The Sprite in this case will be rendered to a position of 0,0. To render this sprite at its actual * position a Container should be used: * * ```js * var doc = new Container(); * * doc.addChild(sprite); * * renderTexture.render(doc); // Renders to center of renderTexture * ``` * * @class * @extends PIXI.Texture * @memberof PIXI * @param renderer {CanvasRenderer|WebGLRenderer} The renderer used for this RenderTexture * @param [width=100] {number} The width of the render texture * @param [height=100] {number} The height of the render texture * @param [scaleMode] {number} See {@link SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution of the texture being generated */ function RenderTexture(renderer, width, height, scaleMode, resolution) { if (!renderer) { throw new Error('Unable to create RenderTexture, you must pass a renderer into the constructor.'); } width = width || 100; height = height || 100; resolution = resolution || CONST.RESOLUTION; /** * The base texture object that this texture uses * * @member {BaseTexture} */ var baseTexture = new BaseTexture(); baseTexture.width = width; baseTexture.height = height; baseTexture.resolution = resolution; baseTexture.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; baseTexture.hasLoaded = true; Texture.call(this, baseTexture, new math.Rectangle(0, 0, width, height) ); /** * The with of the render texture * * @member {number} */ this.width = width; /** * The height of the render texture * * @member {number} */ this.height = height; /** * The Resolution of the texture. * * @member {number} */ this.resolution = resolution; /** * The framing rectangle of the render texture * * @member {Rectangle} */ //this._frame = new math.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution); /** * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) * * @member {Rectangle} */ //this.crop = new math.Rectangle(0, 0, this.width * this.resolution, this.height * this.resolution); /** * Draw/render the given DisplayObject onto the texture. * * The displayObject and descendents are transformed during this operation. * If `updateTransform` is true then the transformations will be restored before the * method returns. Otherwise it is up to the calling code to correctly use or reset * the transformed display objects. * * The display object is always rendered with a worldAlpha value of 1. * * @method * @param displayObject {DisplayObject} The display object to render this texture on * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. * @param [clear=false] {boolean} If true the texture will be cleared before the displayObject is drawn * @param [updateTransform=true] {boolean} If true the displayObject's worldTransform/worldAlpha and all children * transformations will be restored. Not restoring this information will be a little faster. */ this.render = null; /** * The renderer this RenderTexture uses. A RenderTexture can only belong to one renderer at the moment if its webGL. * * @member {CanvasRenderer|WebGLRenderer} */ this.renderer = renderer; if (this.renderer.type === CONST.RENDERER_TYPE.WEBGL) { var gl = this.renderer.gl; this.textureBuffer = new RenderTarget(gl, this.width, this.height, baseTexture.scaleMode, this.resolution);//, this.baseTexture.scaleMode); this.baseTexture._glTextures[gl.id] = this.textureBuffer.texture; //TODO refactor filter manager.. as really its no longer a manager if we use it here.. this.filterManager = new FilterManager(this.renderer); this.filterManager.onContextChange(); this.filterManager.resize(width, height); this.render = this.renderWebGL; // the creation of a filter manager unbinds the buffers.. this.renderer.currentRenderer.start(); this.renderer.currentRenderTarget.activate(); } else { this.render = this.renderCanvas; this.textureBuffer = new CanvasBuffer(this.width* this.resolution, this.height* this.resolution); this.baseTexture.source = this.textureBuffer.canvas; } /** * @member {boolean} */ this.valid = true; this._updateUvs(); } RenderTexture.prototype = Object.create(Texture.prototype); RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; /** * Resizes the RenderTexture. * * @param width {number} The width to resize to. * @param height {number} The height to resize to. * @param updateBase {boolean} Should the baseTexture.width and height values be resized as well? */ RenderTexture.prototype.resize = function (width, height, updateBase) { if (width === this.width && height === this.height) { return; } this.valid = (width > 0 && height > 0); this.width = this._frame.width = this.crop.width = width; this.height = this._frame.height = this.crop.height = height; if (updateBase) { this.baseTexture.width = this.width; this.baseTexture.height = this.height; } if (!this.valid) { return; } this.textureBuffer.resize(this.width, this.height); if(this.filterManager) { this.filterManager.resize(this.width, this.height); } }; /** * Clears the RenderTexture. * */ RenderTexture.prototype.clear = function () { if (!this.valid) { return; } if (this.renderer.type === CONST.RENDERER_TYPE.WEBGL) { this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); } this.textureBuffer.clear(); }; /** * Internal method assigned to the `render` property if using a CanvasRenderer. * * @private * @param displayObject {DisplayObject} The display object to render this texture on * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. * @param [clear=false] {boolean} If true the texture will be cleared before the displayObject is drawn * @param [updateTransform=true] {boolean} If true the displayObject's worldTransform/worldAlpha and all children * transformations will be restored. Not restoring this information will be a little faster. */ RenderTexture.prototype.renderWebGL = function (displayObject, matrix, clear, updateTransform) { if (!this.valid) { return; } updateTransform = (updateTransform !== undefined) ? updateTransform : true;//!updateTransform; this.textureBuffer.transform = matrix; //TODO not a fan that this is here... it will move! this.textureBuffer.activate(); // setWorld Alpha to ensure that the object is renderer at full opacity displayObject.worldAlpha = 1; if (updateTransform) { // reset the matrix of the displatyObject.. displayObject.worldTransform.identity(); displayObject.currentBounds = null; // Time to update all the children of the displayObject with the new matrix.. var children = displayObject.children; var i, j; for (i = 0, j = children.length; i < j; ++i) { children[i].updateTransform(); } } //TODO rename textureBuffer to renderTarget.. var temp = this.renderer.filterManager; this.renderer.filterManager = this.filterManager; this.renderer.renderDisplayObject(displayObject, this.textureBuffer, clear); this.renderer.filterManager = temp; }; /** * Internal method assigned to the `render` property if using a CanvasRenderer. * * @private * @param displayObject {DisplayObject} The display object to render this texture on * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering. * @param [clear] {boolean} If true the texture will be cleared before the displayObject is drawn */ RenderTexture.prototype.renderCanvas = function (displayObject, matrix, clear, updateTransform) { if (!this.valid) { return; } updateTransform = !!updateTransform; var cachedWt = displayObject.worldTransform; var wt = tempMatrix; wt.identity(); if (matrix) { wt.append(matrix); } displayObject.worldTransform = wt; // setWorld Alpha to ensure that the object is renderer at full opacity displayObject.worldAlpha = 1; // Time to update all the children of the displayObject with the new matrix.. var children = displayObject.children; var i, j; for (i = 0, j = children.length; i < j; ++i) { children[i].updateTransform(); } if (clear) { this.textureBuffer.clear(); } displayObject.worldTransform = cachedWt; // this.textureBuffer. var context = this.textureBuffer.context; var realResolution = this.renderer.resolution; this.renderer.resolution = this.resolution; this.renderer.renderDisplayObject(displayObject, context); this.renderer.resolution = realResolution; // context.setTransform(1, 0, 0, 1, 0, 0); // context.fillStyle ="#FF0000" // context.fillRect(0, 0, 800, 600); }; /** * Destroys this texture * * @param destroyBase {boolean} Whether to destroy the base texture as well */ RenderTexture.prototype.destroy = function () { Texture.prototype.destroy.call(this, true); this.textureBuffer.destroy(); // destroy the filtermanager.. if(this.filterManager) { this.filterManager.destroy(); } this.renderer = null; }; /** * Will return a HTML Image of the texture * * @return {Image} */ RenderTexture.prototype.getImage = function () { var image = new Image(); image.src = this.getBase64(); return image; }; /** * Will return a a base64 encoded string of this texture. It works by calling RenderTexture.getCanvas and then running toDataURL on that. * * @return {string} A base64 encoded string of the texture. */ RenderTexture.prototype.getBase64 = function () { return this.getCanvas().toDataURL(); }; /** * Creates a Canvas element, renders this RenderTexture to it and then returns it. * * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. */ RenderTexture.prototype.getCanvas = function () { if (this.renderer.type === CONST.RENDERER_TYPE.WEBGL) { var gl = this.renderer.gl; var width = this.textureBuffer.size.width; var height = this.textureBuffer.size.height; var webGLPixels = new Uint8Array(4 * width * height); gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); gl.bindFramebuffer(gl.FRAMEBUFFER, null); var tempCanvas = new CanvasBuffer(width, height); var canvasData = tempCanvas.context.getImageData(0, 0, width, height); canvasData.data.set(webGLPixels); tempCanvas.context.putImageData(canvasData, 0, 0); return tempCanvas.canvas; } else { return this.textureBuffer.canvas; } }; /** * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). * * @return {Uint8ClampedArray} */ RenderTexture.prototype.getPixels = function () { var width, height; if (this.renderer.type === CONST.RENDERER_TYPE.WEBGL) { var gl = this.renderer.gl; width = this.textureBuffer.size.width; height = this.textureBuffer.size.height; var webGLPixels = new Uint8Array(4 * width * height); gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); gl.bindFramebuffer(gl.FRAMEBUFFER, null); return webGLPixels; } else { width = this.textureBuffer.canvas.width; height = this.textureBuffer.canvas.height; return this.textureBuffer.canvas.getContext('2d').getImageData(0, 0, width, height).data; } }; /** * Will return a one-dimensional array containing the pixel data of a pixel within the texture in RGBA order, with integer values between 0 and 255 (included). * * @param x {number} The x coordinate of the pixel to retrieve. * @param y {number} The y coordinate of the pixel to retrieve. * @return {Uint8ClampedArray} */ RenderTexture.prototype.getPixel = function (x, y) { if (this.renderer.type === CONST.RENDERER_TYPE.WEBGL) { var gl = this.renderer.gl; var webGLPixels = new Uint8Array(4); gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer); gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); gl.bindFramebuffer(gl.FRAMEBUFFER, null); return webGLPixels; } else { return this.textureBuffer.canvas.getContext('2d').getImageData(x, y, 1, 1).data; } };