diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js new file mode 100644 index 0000000..4e8a35c --- /dev/null +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -0,0 +1,160 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const'), + CanvasTinter = require('./CanvasTinter') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasSpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasSpriteRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasSpriteRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; +module.exports = CanvasSpriteRenderer; + +CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); + +/** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ +CanvasSpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture.crop.width, + height = texture.crop.height; + + if (texture.crop.width <= 0 || texture.crop.height <= 0) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + + //inline GroupD8.isSwapWidthHeight + if ((texture.rotate & 3) === 2) { + width = texture.crop.height; + height = texture.crop.width; + } + if (texture.trim) { + dx = texture.crop.width/2 + texture.trim.x - sprite.anchor.x * texture.trim.width; + dy = texture.crop.height/2 + texture.trim.y - sprite.anchor.y * texture.trim.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture._frame.width; + dy = (0.5 - sprite.anchor.y) * texture._frame.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture.frame.x * resolution, + texture.frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } +}; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js new file mode 100644 index 0000000..4e8a35c --- /dev/null +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -0,0 +1,160 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const'), + CanvasTinter = require('./CanvasTinter') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasSpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasSpriteRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasSpriteRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; +module.exports = CanvasSpriteRenderer; + +CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); + +/** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ +CanvasSpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture.crop.width, + height = texture.crop.height; + + if (texture.crop.width <= 0 || texture.crop.height <= 0) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + + //inline GroupD8.isSwapWidthHeight + if ((texture.rotate & 3) === 2) { + width = texture.crop.height; + height = texture.crop.width; + } + if (texture.trim) { + dx = texture.crop.width/2 + texture.trim.x - sprite.anchor.x * texture.trim.width; + dy = texture.crop.height/2 + texture.trim.y - sprite.anchor.y * texture.trim.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture._frame.width; + dy = (0.5 - sprite.anchor.y) * texture._frame.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture.frame.x * resolution, + texture.frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } +}; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js new file mode 100644 index 0000000..640734d --- /dev/null +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -0,0 +1,238 @@ +var utils = require('../../utils'), +canUseNewCanvasBlendModes = require('../../renderers/canvas/utils/canUseNewCanvasBlendModes'); +/** + * Utility methods for Sprite/Texture tinting. + * @static + * @class + * @memberof PIXI + */ +var CanvasTinter = {}; +module.exports = CanvasTinter; + +/** + * Basically this method just needs a sprite and a color and tints the sprite with the given color. + * + * @param sprite {PIXI.Sprite} the sprite to tint + * @param color {number} the color to use to tint the sprite with + * @return {HTMLCanvasElement} The tinted canvas + */ +CanvasTinter.getTintedTexture = function (sprite, color) +{ + var texture = sprite.texture; + + color = CanvasTinter.roundColor(color); + + var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + texture.tintCache = texture.tintCache || {}; + + if (texture.tintCache[stringColor]) + { + return texture.tintCache[stringColor]; + } + + // clone texture.. + var canvas = CanvasTinter.canvas || document.createElement('canvas'); + + //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); + CanvasTinter.tintMethod(texture, color, canvas); + + if (CanvasTinter.convertTintToImage) + { + // is this better? + var tintImage = new Image(); + tintImage.src = canvas.toDataURL(); + + texture.tintCache[stringColor] = tintImage; + } + else + { + texture.tintCache[stringColor] = canvas; + // if we are not converting the texture to an image then we need to lose the reference to the canvas + CanvasTinter.canvas = null; + } + + return canvas; +}; + +/** + * Tint a texture using the 'multiply' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithMultiply = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'multiply'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + context.globalCompositeOperation = 'destination-atop'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); +}; + +/** + * Tint a texture using the 'overlay' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithOverlay = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'destination-atop'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + // context.globalCompositeOperation = 'copy'; +}; + +/** + * Tint a texture pixel per pixel. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithPerPixel = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + var rgbValues = utils.hex2rgb(color); + var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; + + var pixelData = context.getImageData(0, 0, crop.width, crop.height); + + var pixels = pixelData.data; + + for (var i = 0; i < pixels.length; i += 4) + { + pixels[i+0] *= r; + pixels[i+1] *= g; + pixels[i+2] *= b; + } + + context.putImageData(pixelData, 0, 0); +}; + +/** + * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. + * + * @param color {number} the color to round, should be a hex color + */ +CanvasTinter.roundColor = function (color) +{ + var step = CanvasTinter.cacheStepsPerColorChannel; + + var rgbValues = utils.hex2rgb(color); + + rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); + rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); + rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); + + return utils.rgb2hex(rgbValues); +}; + +/** + * Number of steps which will be used as a cap when rounding colors. + * + * @member + */ +CanvasTinter.cacheStepsPerColorChannel = 8; + +/** + * Tint cache boolean flag. + * + * @member + */ +CanvasTinter.convertTintToImage = false; + +/** + * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. + * + * @member + */ +CanvasTinter.canUseMultiply = canUseNewCanvasBlendModes(); + +/** + * The tinting method that will be used. + * + */ +CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js new file mode 100644 index 0000000..4e8a35c --- /dev/null +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -0,0 +1,160 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const'), + CanvasTinter = require('./CanvasTinter') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasSpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasSpriteRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasSpriteRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; +module.exports = CanvasSpriteRenderer; + +CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); + +/** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ +CanvasSpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture.crop.width, + height = texture.crop.height; + + if (texture.crop.width <= 0 || texture.crop.height <= 0) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + + //inline GroupD8.isSwapWidthHeight + if ((texture.rotate & 3) === 2) { + width = texture.crop.height; + height = texture.crop.width; + } + if (texture.trim) { + dx = texture.crop.width/2 + texture.trim.x - sprite.anchor.x * texture.trim.width; + dy = texture.crop.height/2 + texture.trim.y - sprite.anchor.y * texture.trim.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture._frame.width; + dy = (0.5 - sprite.anchor.y) * texture._frame.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture.frame.x * resolution, + texture.frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } +}; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js new file mode 100644 index 0000000..640734d --- /dev/null +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -0,0 +1,238 @@ +var utils = require('../../utils'), +canUseNewCanvasBlendModes = require('../../renderers/canvas/utils/canUseNewCanvasBlendModes'); +/** + * Utility methods for Sprite/Texture tinting. + * @static + * @class + * @memberof PIXI + */ +var CanvasTinter = {}; +module.exports = CanvasTinter; + +/** + * Basically this method just needs a sprite and a color and tints the sprite with the given color. + * + * @param sprite {PIXI.Sprite} the sprite to tint + * @param color {number} the color to use to tint the sprite with + * @return {HTMLCanvasElement} The tinted canvas + */ +CanvasTinter.getTintedTexture = function (sprite, color) +{ + var texture = sprite.texture; + + color = CanvasTinter.roundColor(color); + + var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + texture.tintCache = texture.tintCache || {}; + + if (texture.tintCache[stringColor]) + { + return texture.tintCache[stringColor]; + } + + // clone texture.. + var canvas = CanvasTinter.canvas || document.createElement('canvas'); + + //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); + CanvasTinter.tintMethod(texture, color, canvas); + + if (CanvasTinter.convertTintToImage) + { + // is this better? + var tintImage = new Image(); + tintImage.src = canvas.toDataURL(); + + texture.tintCache[stringColor] = tintImage; + } + else + { + texture.tintCache[stringColor] = canvas; + // if we are not converting the texture to an image then we need to lose the reference to the canvas + CanvasTinter.canvas = null; + } + + return canvas; +}; + +/** + * Tint a texture using the 'multiply' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithMultiply = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'multiply'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + context.globalCompositeOperation = 'destination-atop'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); +}; + +/** + * Tint a texture using the 'overlay' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithOverlay = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'destination-atop'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + // context.globalCompositeOperation = 'copy'; +}; + +/** + * Tint a texture pixel per pixel. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithPerPixel = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + var rgbValues = utils.hex2rgb(color); + var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; + + var pixelData = context.getImageData(0, 0, crop.width, crop.height); + + var pixels = pixelData.data; + + for (var i = 0; i < pixels.length; i += 4) + { + pixels[i+0] *= r; + pixels[i+1] *= g; + pixels[i+2] *= b; + } + + context.putImageData(pixelData, 0, 0); +}; + +/** + * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. + * + * @param color {number} the color to round, should be a hex color + */ +CanvasTinter.roundColor = function (color) +{ + var step = CanvasTinter.cacheStepsPerColorChannel; + + var rgbValues = utils.hex2rgb(color); + + rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); + rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); + rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); + + return utils.rgb2hex(rgbValues); +}; + +/** + * Number of steps which will be used as a cap when rounding colors. + * + * @member + */ +CanvasTinter.cacheStepsPerColorChannel = 8; + +/** + * Tint cache boolean flag. + * + * @member + */ +CanvasTinter.convertTintToImage = false; + +/** + * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. + * + * @member + */ +CanvasTinter.canUseMultiply = canUseNewCanvasBlendModes(); + +/** + * The tinting method that will be used. + * + */ +CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/sprites/renderWebGL.js b/src/core/sprites/renderWebGL.js deleted file mode 100644 index d857e8f..0000000 --- a/src/core/sprites/renderWebGL.js +++ /dev/null @@ -1,23 +0,0 @@ -var = renderWebGL = function (renderer, sprite) -{ - if(this.textureDirty) - { - this.textureDirty = false; - - this._onTextureUpdate(); - - this.vertexDirty = true; - } - - if(this.vertexDirty) - { - this.vertexDirty = false; - - // set the vertex data - this.caclulateVertices(); - - } - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js new file mode 100644 index 0000000..4e8a35c --- /dev/null +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -0,0 +1,160 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const'), + CanvasTinter = require('./CanvasTinter') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasSpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasSpriteRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasSpriteRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; +module.exports = CanvasSpriteRenderer; + +CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); + +/** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ +CanvasSpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture.crop.width, + height = texture.crop.height; + + if (texture.crop.width <= 0 || texture.crop.height <= 0) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + + //inline GroupD8.isSwapWidthHeight + if ((texture.rotate & 3) === 2) { + width = texture.crop.height; + height = texture.crop.width; + } + if (texture.trim) { + dx = texture.crop.width/2 + texture.trim.x - sprite.anchor.x * texture.trim.width; + dy = texture.crop.height/2 + texture.trim.y - sprite.anchor.y * texture.trim.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture._frame.width; + dy = (0.5 - sprite.anchor.y) * texture._frame.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture.frame.x * resolution, + texture.frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } +}; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js new file mode 100644 index 0000000..640734d --- /dev/null +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -0,0 +1,238 @@ +var utils = require('../../utils'), +canUseNewCanvasBlendModes = require('../../renderers/canvas/utils/canUseNewCanvasBlendModes'); +/** + * Utility methods for Sprite/Texture tinting. + * @static + * @class + * @memberof PIXI + */ +var CanvasTinter = {}; +module.exports = CanvasTinter; + +/** + * Basically this method just needs a sprite and a color and tints the sprite with the given color. + * + * @param sprite {PIXI.Sprite} the sprite to tint + * @param color {number} the color to use to tint the sprite with + * @return {HTMLCanvasElement} The tinted canvas + */ +CanvasTinter.getTintedTexture = function (sprite, color) +{ + var texture = sprite.texture; + + color = CanvasTinter.roundColor(color); + + var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + texture.tintCache = texture.tintCache || {}; + + if (texture.tintCache[stringColor]) + { + return texture.tintCache[stringColor]; + } + + // clone texture.. + var canvas = CanvasTinter.canvas || document.createElement('canvas'); + + //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); + CanvasTinter.tintMethod(texture, color, canvas); + + if (CanvasTinter.convertTintToImage) + { + // is this better? + var tintImage = new Image(); + tintImage.src = canvas.toDataURL(); + + texture.tintCache[stringColor] = tintImage; + } + else + { + texture.tintCache[stringColor] = canvas; + // if we are not converting the texture to an image then we need to lose the reference to the canvas + CanvasTinter.canvas = null; + } + + return canvas; +}; + +/** + * Tint a texture using the 'multiply' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithMultiply = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'multiply'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + context.globalCompositeOperation = 'destination-atop'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); +}; + +/** + * Tint a texture using the 'overlay' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithOverlay = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'destination-atop'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + // context.globalCompositeOperation = 'copy'; +}; + +/** + * Tint a texture pixel per pixel. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithPerPixel = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + var rgbValues = utils.hex2rgb(color); + var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; + + var pixelData = context.getImageData(0, 0, crop.width, crop.height); + + var pixels = pixelData.data; + + for (var i = 0; i < pixels.length; i += 4) + { + pixels[i+0] *= r; + pixels[i+1] *= g; + pixels[i+2] *= b; + } + + context.putImageData(pixelData, 0, 0); +}; + +/** + * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. + * + * @param color {number} the color to round, should be a hex color + */ +CanvasTinter.roundColor = function (color) +{ + var step = CanvasTinter.cacheStepsPerColorChannel; + + var rgbValues = utils.hex2rgb(color); + + rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); + rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); + rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); + + return utils.rgb2hex(rgbValues); +}; + +/** + * Number of steps which will be used as a cap when rounding colors. + * + * @member + */ +CanvasTinter.cacheStepsPerColorChannel = 8; + +/** + * Tint cache boolean flag. + * + * @member + */ +CanvasTinter.convertTintToImage = false; + +/** + * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. + * + * @member + */ +CanvasTinter.canUseMultiply = canUseNewCanvasBlendModes(); + +/** + * The tinting method that will be used. + * + */ +CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/sprites/renderWebGL.js b/src/core/sprites/renderWebGL.js deleted file mode 100644 index d857e8f..0000000 --- a/src/core/sprites/renderWebGL.js +++ /dev/null @@ -1,23 +0,0 @@ -var = renderWebGL = function (renderer, sprite) -{ - if(this.textureDirty) - { - this.textureDirty = false; - - this._onTextureUpdate(); - - this.vertexDirty = true; - } - - if(this.vertexDirty) - { - this.vertexDirty = false; - - // set the vertex data - this.caclulateVertices(); - - } - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index e61a97b..6d375ea 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -2,7 +2,6 @@ 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(), @@ -68,6 +67,8 @@ this._glRenderTargets = []; + this._canvasRenderTarget = null; + /** * The renderer this BaseRenderTexture uses. A BaseRenderTexture can only belong to one renderer at the moment if its webGL. * diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js new file mode 100644 index 0000000..4e8a35c --- /dev/null +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -0,0 +1,160 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const'), + CanvasTinter = require('./CanvasTinter') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasSpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasSpriteRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasSpriteRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; +module.exports = CanvasSpriteRenderer; + +CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); + +/** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ +CanvasSpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture.crop.width, + height = texture.crop.height; + + if (texture.crop.width <= 0 || texture.crop.height <= 0) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + + //inline GroupD8.isSwapWidthHeight + if ((texture.rotate & 3) === 2) { + width = texture.crop.height; + height = texture.crop.width; + } + if (texture.trim) { + dx = texture.crop.width/2 + texture.trim.x - sprite.anchor.x * texture.trim.width; + dy = texture.crop.height/2 + texture.trim.y - sprite.anchor.y * texture.trim.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture._frame.width; + dy = (0.5 - sprite.anchor.y) * texture._frame.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture.frame.x * resolution, + texture.frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } +}; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js new file mode 100644 index 0000000..640734d --- /dev/null +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -0,0 +1,238 @@ +var utils = require('../../utils'), +canUseNewCanvasBlendModes = require('../../renderers/canvas/utils/canUseNewCanvasBlendModes'); +/** + * Utility methods for Sprite/Texture tinting. + * @static + * @class + * @memberof PIXI + */ +var CanvasTinter = {}; +module.exports = CanvasTinter; + +/** + * Basically this method just needs a sprite and a color and tints the sprite with the given color. + * + * @param sprite {PIXI.Sprite} the sprite to tint + * @param color {number} the color to use to tint the sprite with + * @return {HTMLCanvasElement} The tinted canvas + */ +CanvasTinter.getTintedTexture = function (sprite, color) +{ + var texture = sprite.texture; + + color = CanvasTinter.roundColor(color); + + var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + texture.tintCache = texture.tintCache || {}; + + if (texture.tintCache[stringColor]) + { + return texture.tintCache[stringColor]; + } + + // clone texture.. + var canvas = CanvasTinter.canvas || document.createElement('canvas'); + + //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); + CanvasTinter.tintMethod(texture, color, canvas); + + if (CanvasTinter.convertTintToImage) + { + // is this better? + var tintImage = new Image(); + tintImage.src = canvas.toDataURL(); + + texture.tintCache[stringColor] = tintImage; + } + else + { + texture.tintCache[stringColor] = canvas; + // if we are not converting the texture to an image then we need to lose the reference to the canvas + CanvasTinter.canvas = null; + } + + return canvas; +}; + +/** + * Tint a texture using the 'multiply' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithMultiply = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'multiply'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + context.globalCompositeOperation = 'destination-atop'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); +}; + +/** + * Tint a texture using the 'overlay' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithOverlay = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'destination-atop'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + // context.globalCompositeOperation = 'copy'; +}; + +/** + * Tint a texture pixel per pixel. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithPerPixel = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + var rgbValues = utils.hex2rgb(color); + var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; + + var pixelData = context.getImageData(0, 0, crop.width, crop.height); + + var pixels = pixelData.data; + + for (var i = 0; i < pixels.length; i += 4) + { + pixels[i+0] *= r; + pixels[i+1] *= g; + pixels[i+2] *= b; + } + + context.putImageData(pixelData, 0, 0); +}; + +/** + * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. + * + * @param color {number} the color to round, should be a hex color + */ +CanvasTinter.roundColor = function (color) +{ + var step = CanvasTinter.cacheStepsPerColorChannel; + + var rgbValues = utils.hex2rgb(color); + + rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); + rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); + rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); + + return utils.rgb2hex(rgbValues); +}; + +/** + * Number of steps which will be used as a cap when rounding colors. + * + * @member + */ +CanvasTinter.cacheStepsPerColorChannel = 8; + +/** + * Tint cache boolean flag. + * + * @member + */ +CanvasTinter.convertTintToImage = false; + +/** + * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. + * + * @member + */ +CanvasTinter.canUseMultiply = canUseNewCanvasBlendModes(); + +/** + * The tinting method that will be used. + * + */ +CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/sprites/renderWebGL.js b/src/core/sprites/renderWebGL.js deleted file mode 100644 index d857e8f..0000000 --- a/src/core/sprites/renderWebGL.js +++ /dev/null @@ -1,23 +0,0 @@ -var = renderWebGL = function (renderer, sprite) -{ - if(this.textureDirty) - { - this.textureDirty = false; - - this._onTextureUpdate(); - - this.vertexDirty = true; - } - - if(this.vertexDirty) - { - this.vertexDirty = false; - - // set the vertex data - this.caclulateVertices(); - - } - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index e61a97b..6d375ea 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -2,7 +2,6 @@ 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(), @@ -68,6 +67,8 @@ this._glRenderTargets = []; + this._canvasRenderTarget = null; + /** * The renderer this BaseRenderTexture uses. A BaseRenderTexture can only belong to one renderer at the moment if its webGL. * diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 29cd7fc..241c91a 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -64,40 +64,7 @@ return ((rgb[0]*255 << 16) + (rgb[1]*255 << 8) + rgb[2]*255); }, - /** - * Checks whether the Canvas BlendModes are supported by the current browser - * - * @return {boolean} whether they are supported - */ - canUseNewCanvasBlendModes: function () - { - if (typeof document === 'undefined') - { - return false; - } - - var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; - var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; - - var magenta = new Image(); - magenta.src = pngHead + 'AP804Oa6' + pngEnd; - - var yellow = new Image(); - yellow.src = pngHead + '/wCKxvRF' + pngEnd; - - var canvas = document.createElement('canvas'); - canvas.width = 6; - canvas.height = 1; - - var context = canvas.getContext('2d'); - context.globalCompositeOperation = 'multiply'; - context.drawImage(magenta, 0, 0); - context.drawImage(yellow, 2, 0); - - var data = context.getImageData(2,0,1,1).data; - - return (data[0] === 255 && data[1] === 0 && data[2] === 0); - }, + /** * Given a number, this function returns the closest number that is a power of two diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 59012a9..92f3cea 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1,8 +1,7 @@ var Container = require('../display/Container'), Texture = require('../textures/Texture'), RenderTexture = require('../textures/RenderTexture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + CanvasRenderTarget = require('../renderers/canvas/utils/CanvasRenderTarget'), GraphicsData = require('./GraphicsData'), Sprite = require('../sprites/Sprite'), math = require('../math'), @@ -690,16 +689,16 @@ var bounds = this.getLocalBounds(); - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + var canvasRenderTarget = new CanvasRenderTarget(bounds.width * resolution, bounds.height * resolution); - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + var texture = Texture.fromCanvas(canvasRenderTarget.canvas, scaleMode); texture.baseTexture.resolution = resolution; - canvasBuffer.context.scale(resolution, resolution); + canvasRenderTarget.context.scale(resolution, resolution); - canvasBuffer.context.translate(-bounds.x,-bounds.y); + canvasRenderTarget.context.translate(-bounds.x,-bounds.y); - CanvasGraphics.renderGraphics(this, canvasBuffer.context); + CanvasGraphics.renderGraphics(this, canvasRenderTarget.context); return texture; }; @@ -722,7 +721,9 @@ this.glDirty = false; } - if(this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT) + if(this.graphicsData.length === 1 + && this.graphicsData[0].shape.type === CONST.SHAPES.RECT + && !this.graphicsData[0].lineWidth) { this._renderSpriteRect(renderer); } @@ -776,34 +777,7 @@ return; } - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - - var context = renderer.context; - var transform = this.worldTransform; - - var compositeOperation = renderer.blendModes[this.blendMode]; - - if (compositeOperation !== context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - var resolution = renderer.resolution; - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); + renderer.plugins.graphics.render(this); }; /** diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js new file mode 100644 index 0000000..8a1cb9c --- /dev/null +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -0,0 +1,269 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasGraphicsRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasGraphicsRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasGraphicsRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; +module.exports = CanvasGraphicsRenderer; + +CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); + +/* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ +CanvasGraphicsRenderer.prototype.render = function (graphics) +{ + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (shape.closed) + { + context.lineTo(points[0], points[1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.beginPath(); + context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.ELIP) + { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } +}; + +/* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ +CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) +{ + if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) + { + return; + } + + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } +}; + diff --git a/src/core/graphics/webgl/utils/buildLine.js b/src/core/graphics/webgl/utils/buildLine.js index 104167f..5e2b985 100644 --- a/src/core/graphics/webgl/utils/buildLine.js +++ b/src/core/graphics/webgl/utils/buildLine.js @@ -12,7 +12,7 @@ { // TODO OPTIMISE! var i = 0; - var points = graphicsData.shape.points; + var points = graphicsData.points; if (points.length === 0) { diff --git a/src/core/index.js b/src/core/index.js index cc4aa4d..a80f03b 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -20,6 +20,7 @@ // sprites Sprite: require('./sprites/Sprite'), + CanvasSpriteRender: require('./sprites/canvas/CanvasSpriteRenderer'), //ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), //ParticleRenderer: require('./particles/webgl/ParticleRenderer'), @@ -31,6 +32,7 @@ Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), + CanvasGraphicsRenderer: require('./graphics/canvas/CanvasGraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,8 +44,7 @@ // renderers - canvas CanvasRenderer: require('./renderers/canvas/CanvasRenderer'), - CanvasGraphics: require('./renderers/canvas/utils/CanvasGraphics'), - CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), + CanvasRenderTarget: require('./renderers/canvas/utils/CanvasRenderTarget'), // renderers - webgl WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index d91d2b9..f65d371 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -2,8 +2,9 @@ math = require('../math'), CONST = require('../const'), Container = require('../display/Container'), - EventEmitter = require('eventemitter3'); - + RenderTexture = require('../textures/RenderTexture'), + EventEmitter = require('eventemitter3'), + tempMatrix = new math.Matrix(); /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL. * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :) @@ -223,6 +224,20 @@ } }; +SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 840560c..99532b5 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -1,5 +1,7 @@ var SystemRenderer = require('../SystemRenderer'), CanvasMaskManager = require('./utils/CanvasMaskManager'), + CanvasRenderTarget = require('./utils/CanvasRenderTarget'), + mapCanvasBlendModesToPixi = require('./utils/mapCanvasBlendModesToPixi'), utils = require('../../utils'), math = require('../../math'), CONST = require('../../const'); @@ -36,7 +38,8 @@ * * @member {CanvasRenderingContext2D} */ - this.context = this.view.getContext('2d', { alpha: this.transparent }); + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; /** * Boolean flag controlling canvas refresh. @@ -50,7 +53,7 @@ * * @member {PIXI.CanvasMaskManager} */ - this.maskManager = new CanvasMaskManager(); + this.maskManager = new CanvasMaskManager(this); /** * The canvas property used to set the canvas smoothing property. @@ -59,21 +62,21 @@ */ this.smoothProperty = 'imageSmoothingEnabled'; - if (!this.context.imageSmoothingEnabled) + if (!this.rootContext.imageSmoothingEnabled) { - if (this.context.webkitImageSmoothingEnabled) + if (this.rootContext.webkitImageSmoothingEnabled) { this.smoothProperty = 'webkitImageSmoothingEnabled'; } - else if (this.context.mozImageSmoothingEnabled) + else if (this.rootContext.mozImageSmoothingEnabled) { this.smoothProperty = 'mozImageSmoothingEnabled'; } - else if (this.context.oImageSmoothingEnabled) + else if (this.rootContext.oImageSmoothingEnabled) { this.smoothProperty = 'oImageSmoothingEnabled'; } - else if (this.context.msImageSmoothingEnabled) + else if (this.rootContext.msImageSmoothingEnabled) { this.smoothProperty = 'msImageSmoothingEnabled'; } @@ -81,19 +84,12 @@ this.initPlugins(); - this._mapBlendModes(); + this.blendModes = mapCanvasBlendModesToPixi(); + console.log(this.blendModes) + this._activeBlendMode = null; - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = { - worldTransform: new math.Matrix(), - worldAlpha: 1 - }; - + this.context = null; + this.renderingToScreen = false; this.resize(width, height); } @@ -104,56 +100,110 @@ module.exports = CanvasRenderer; utils.pluginTarget.mixin(CanvasRenderer); + /** * Renders the object to this canvas view * * @param object {PIXI.DisplayObject} the object to be rendered */ -CanvasRenderer.prototype.render = function (object) +CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) { + // can be handy to know! + this.renderingToScreen = !renderTexture; + + + this.emit('prerender'); - var cacheParent = object.parent; - - this._lastObjectRendered = object; - - object.parent = this._tempDisplayObjectParent; - - // update the scene graph - object.updateTransform(); - - object.parent = cacheParent; - - this.context.setTransform(1, 0, 0, 1, 0, 0); - - this.context.globalAlpha = 1; - - this.context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - - if (navigator.isCocoonJS && this.view.screencanvas) + if(renderTexture) { - this.context.fillStyle = 'black'; - this.context.clear(); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + + this.context = this.rootContext; + this.resolution = this.rootResolution } - if (this.clearBeforeRender) - { - if (this.transparent) + var context = this.context; + + this._lastObjectRendered = displayObject; + + + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) { - this.context.clearRect(0, 0, this.width, this.height); + transform.copy(tempWt); } else { - this.context.fillStyle = this._backgroundColorString; - this.context.fillRect(0, 0, this.width , this.height); + tempWt.identity(); + } + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if( clear || this.clearBeforeRender) + { + if (this.transparent) + { + context.clearRect(0, 0, this.width, this.height); + } + else + { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width , this.height); } } - this.renderDisplayObject(object, this.context); + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; this.emit('postrender'); }; + +CanvasRenderer.prototype.setBlendMode = function (blendMode) +{ + if(this._activeBlendMode === blendMode)return; + renderer.context.globalCompositeOperation = renderer.blendModes[blendMode]; +} + /** * Removes everything from the renderer and optionally removes the Canvas DOM element. * @@ -177,21 +227,6 @@ }; /** - * Renders a display object - * - * @param displayObject {PIXI.DisplayObject} The displayObject to render - * @private - */ -CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context) -{ - var tempContext = this.context; - - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; -}; - -/** * @extends PIXI.SystemRenderer#resize * * @param {number} w @@ -205,62 +240,7 @@ //surely a browser bug?? Let pixi fix that for you.. if(this.smoothProperty) { - this.context[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); } -}; - -/** - * Maps Pixi blend modes to canvas blend modes. - * - * @private - */ -CanvasRenderer.prototype._mapBlendModes = function () -{ - if (!this.blendModes) - { - this.blendModes = {}; - - if (utils.canUseNewCanvasBlendModes()) - { - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'screen'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'overlay'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'darken'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'hue'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'saturate'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'color'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; - } - else - { - // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' - this.blendModes[CONST.BLEND_MODES.NORMAL] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? - this.blendModes[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SCREEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.OVERLAY] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DARKEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.HUE] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.SATURATION] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.COLOR] = 'source-over'; - this.blendModes[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; - } - } -}; +}; \ No newline at end of file diff --git a/src/core/renderers/canvas/utils/CanvasBuffer.js b/src/core/renderers/canvas/utils/CanvasBuffer.js deleted file mode 100644 index bb9b4fe..0000000 --- a/src/core/renderers/canvas/utils/CanvasBuffer.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Creates a Canvas element of the given size. - * - * @class - * @memberof PIXI - * @param width {number} the width for the newly created canvas - * @param height {number} the height for the newly created canvas - */ -function CanvasBuffer(width, height) -{ - /** - * The Canvas object that belongs to this CanvasBuffer. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. - * - * @member {CanvasRenderingContext2D} - */ - this.context = this.canvas.getContext('2d'); - - this.canvas.width = width; - this.canvas.height = height; -} - -CanvasBuffer.prototype.constructor = CanvasBuffer; -module.exports = CanvasBuffer; - -Object.defineProperties(CanvasBuffer.prototype, { - /** - * The width of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - width: { - get: function () - { - return this.canvas.width; - }, - set: function (val) - { - this.canvas.width = val; - } - }, - /** - * The height of the canvas buffer in pixels. - * - * @member {number} - * @memberof PIXI.CanvasBuffer# - */ - height: { - get: function () - { - return this.canvas.height; - }, - set: function (val) - { - this.canvas.height = val; - } - } -}); - -/** - * Clears the canvas that was created by the CanvasBuffer class. - * - * @private - */ -CanvasBuffer.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasBuffer.prototype.resize = function (width, height) -{ - this.canvas.width = width; - this.canvas.height = height; -}; - -/** - * Destroys this canvas. - * - */ -CanvasBuffer.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/canvas/utils/CanvasGraphics.js b/src/core/renderers/canvas/utils/CanvasGraphics.js deleted file mode 100644 index b2d5b91..0000000 --- a/src/core/renderers/canvas/utils/CanvasGraphics.js +++ /dev/null @@ -1,352 +0,0 @@ -var CONST = require('../../../const'); - -/** - * A set of functions used by the canvas renderer to draw the primitive graphics data. - * @static - * @class - * @memberof PIXI - */ -var CanvasGraphics = {}; -module.exports = CanvasGraphics; - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphics.renderGraphics = function (graphics, context) -{ - var worldAlpha = graphics.worldAlpha; - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (shape.closed) - { - context.lineTo(points[0], points[1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.beginPath(); - context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.ELIP) - { - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Renders a graphics mask - * - * @private - * @param graphics {PIXI.Graphics} the graphics which will be used as a mask - * @param context {CanvasRenderingContext2D} the context 2d method of the canvas - */ -CanvasGraphics.renderGraphicsMask = function (graphics, context) -{ - var len = graphics.graphicsData.length; - - if (len === 0) - { - return; - } - - context.beginPath(); - - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - if (data.type === CONST.SHAPES.POLY) - { - - var points = shape.points; - - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) - { - context.closePath(); - } - - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { - - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - - var w = shape.width * 2; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { - - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphics.updateGraphicsTint = function (graphics) -{ - if (graphics.tint === 0xFFFFFF && graphics._prevTint === graphics.tint) - { - return; - } - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - /* - var colorR = (fillColor >> 16 & 0xFF) / 255; - var colorG = (fillColor >> 8 & 0xFF) / 255; - var colorB = (fillColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - fillColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - - colorR = (lineColor >> 16 & 0xFF) / 255; - colorG = (lineColor >> 8 & 0xFF) / 255; - colorB = (lineColor & 0xFF) / 255; - - colorR *= tintR; - colorG *= tintG; - colorB *= tintB; - - lineColor = ((colorR*255 << 16) + (colorG*255 << 8) + colorB*255); - */ - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - - } -}; - diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 48e5397..5246d36 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -1,4 +1,3 @@ -var CanvasGraphics = require('./CanvasGraphics'); /** * A set of functions used to handle masking. @@ -6,8 +5,10 @@ * @class * @memberof PIXI */ -function CanvasMaskManager() -{} +function CanvasMaskManager(renderer) +{ + this.renderer = renderer; +} CanvasMaskManager.prototype.constructor = CanvasMaskManager; module.exports = CanvasMaskManager; @@ -18,13 +19,14 @@ * @param maskData {object} the maskData that will be pushed * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. */ -CanvasMaskManager.prototype.pushMask = function (maskData, renderer) +CanvasMaskManager.prototype.pushMask = function (maskData) { + var renderer = this.renderer; renderer.context.save(); var cacheAlpha = maskData.alpha; - var transform = maskData.worldTransform; + var transform = maskData.transform.worldTransform; var resolution = renderer.resolution; renderer.context.setTransform( @@ -38,15 +40,114 @@ //TODO suport sprite alpha masks?? //lots of effort required. If demand is great enough.. - if(!maskData.texture) + if(!maskData._texture) { - CanvasGraphics.renderGraphicsMask(maskData, renderer.context); + this.renderGraphicsShape(maskData); renderer.context.clip(); } maskData.worldAlpha = cacheAlpha; }; +CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) +{ + var context = this.renderer.context; + var len = graphics.graphicsData.length; + + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) + { + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + + } + else if (data.type === CONST.SHAPES.RECT) + { + context.rect(shape.x, shape.y, shape.width, shape.height); + context.closePath(); + } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { + + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + + var w = shape.width * 2; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { + + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } + } +}; + /** * Restores the current drawing context to the state it was before the mask was applied. * diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js new file mode 100644 index 0000000..5030080 --- /dev/null +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -0,0 +1,100 @@ +/** + * Creates a Canvas element of the given size. + * + * @class + * @memberof PIXI + * @param width {number} the width for the newly created canvas + * @param height {number} the height for the newly created canvas + */ +function CanvasRenderTarget(width, height, resolution) +{ + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution; + + this.resize(width, height); +} + +CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; +module.exports = CanvasRenderTarget; + +Object.defineProperties(CanvasRenderTarget.prototype, { + /** + * The width of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + width: { + get: function () + { + return this.canvas.width; + }, + set: function (val) + { + this.canvas.width = val; + } + }, + /** + * The height of the canvas buffer in pixels. + * + * @member {number} + * @memberof PIXI.CanvasRenderTarget# + */ + height: { + get: function () + { + return this.canvas.height; + }, + set: function (val) + { + this.canvas.height = val; + } + } +}); + +/** + * Clears the canvas that was created by the CanvasRenderTarget class. + * + * @private + */ +CanvasRenderTarget.prototype.clear = function () +{ + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); +}; + +/** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ +CanvasRenderTarget.prototype.resize = function (width, height) +{ + + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; +}; + +/** + * Destroys this canvas. + * + */ +CanvasRenderTarget.prototype.destroy = function () +{ + this.context = null; + this.canvas = null; +}; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js deleted file mode 100644 index 8827558..0000000 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ /dev/null @@ -1,238 +0,0 @@ -var utils = require('../../../utils'); - -/** - * Utility methods for Sprite/Texture tinting. - * @static - * @class - * @memberof PIXI - */ -var CanvasTinter = {}; -module.exports = CanvasTinter; - -/** - * Basically this method just needs a sprite and a color and tints the sprite with the given color. - * - * @param sprite {PIXI.Sprite} the sprite to tint - * @param color {number} the color to use to tint the sprite with - * @return {HTMLCanvasElement} The tinted canvas - */ -CanvasTinter.getTintedTexture = function (sprite, color) -{ - var texture = sprite.texture; - - color = CanvasTinter.roundColor(color); - - var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - texture.tintCache = texture.tintCache || {}; - - if (texture.tintCache[stringColor]) - { - return texture.tintCache[stringColor]; - } - - // clone texture.. - var canvas = CanvasTinter.canvas || document.createElement('canvas'); - - //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); - CanvasTinter.tintMethod(texture, color, canvas); - - if (CanvasTinter.convertTintToImage) - { - // is this better? - var tintImage = new Image(); - tintImage.src = canvas.toDataURL(); - - texture.tintCache[stringColor] = tintImage; - } - else - { - texture.tintCache[stringColor] = canvas; - // if we are not converting the texture to an image then we need to lose the reference to the canvas - CanvasTinter.canvas = null; - } - - return canvas; -}; - -/** - * Tint a texture using the 'multiply' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithMultiply = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'multiply'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - context.globalCompositeOperation = 'destination-atop'; - - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); -}; - -/** - * Tint a texture using the 'overlay' operation. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithOverlay = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); - context.fillRect(0, 0, crop.width, crop.height); - - context.globalCompositeOperation = 'destination-atop'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - // context.globalCompositeOperation = 'copy'; -}; - -/** - * Tint a texture pixel per pixel. - * - * @param texture {PIXI.Texture} the texture to tint - * @param color {number} the color to use to tint the sprite with - * @param canvas {HTMLCanvasElement} the current canvas - */ -CanvasTinter.tintWithPerPixel = function (texture, color, canvas) -{ - var context = canvas.getContext( '2d' ); - - var resolution = texture.baseTexture.resolution; - - var crop = texture._frame; - - canvas.width = crop.width; - canvas.height = crop.height; - - context.globalCompositeOperation = 'copy'; - context.drawImage( - texture.baseTexture.source, - crop.x, - crop.y, - crop.width, - crop.height, - 0, - 0, - crop.width, - crop.height - ); - - var rgbValues = utils.hex2rgb(color); - var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; - - var pixelData = context.getImageData(0, 0, crop.width, crop.height); - - var pixels = pixelData.data; - - for (var i = 0; i < pixels.length; i += 4) - { - pixels[i+0] *= r; - pixels[i+1] *= g; - pixels[i+2] *= b; - } - - context.putImageData(pixelData, 0, 0); -}; - -/** - * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. - * - * @param color {number} the color to round, should be a hex color - */ -CanvasTinter.roundColor = function (color) -{ - var step = CanvasTinter.cacheStepsPerColorChannel; - - var rgbValues = utils.hex2rgb(color); - - rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); - rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); - rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); - - return utils.rgb2hex(rgbValues); -}; - -/** - * Number of steps which will be used as a cap when rounding colors. - * - * @member - */ -CanvasTinter.cacheStepsPerColorChannel = 8; - -/** - * Tint cache boolean flag. - * - * @member - */ -CanvasTinter.convertTintToImage = false; - -/** - * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. - * - * @member - */ -CanvasTinter.canUseMultiply = utils.canUseNewCanvasBlendModes(); - -/** - * The tinting method that will be used. - * - */ -CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js new file mode 100644 index 0000000..b2d9b13 --- /dev/null +++ b/src/core/renderers/canvas/utils/canUseNewCanvasBlendModes.js @@ -0,0 +1,38 @@ + + +/** + * Checks whether the Canvas BlendModes are supported by the current browser + * + * @return {boolean} whether they are supported + */ +var canUseNewCanvasBlendModes = function () +{ + if (typeof document === 'undefined') + { + return false; + } + + var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; + var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; + + var magenta = new Image(); + magenta.src = pngHead + 'AP804Oa6' + pngEnd; + + var yellow = new Image(); + yellow.src = pngHead + '/wCKxvRF' + pngEnd; + + var canvas = document.createElement('canvas'); + canvas.width = 6; + canvas.height = 1; + + var context = canvas.getContext('2d'); + context.globalCompositeOperation = 'multiply'; + context.drawImage(magenta, 0, 0); + context.drawImage(yellow, 2, 0); + + var data = context.getImageData(2,0,1,1).data; + + return (data[0] === 255 && data[1] === 0 && data[2] === 0); +} + +module.exports = canUseNewCanvasBlendModes; diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js new file mode 100644 index 0000000..5a87070 --- /dev/null +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -0,0 +1,58 @@ +var CONST = require('../../../Const'), +canUseNewCanvasBlendModes = require('./canUseNewCanvasBlendModes'); + +/** + * Maps gl blend combinations to WebGL + * @class + * @memberof PIXI + */ +function mapWebGLBlendModesToPixi(array) +{ + array = array || []; + + if (canUseNewCanvasBlendModes()) + { + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'multiply'; + array[CONST.BLEND_MODES.SCREEN] = 'screen'; + array[CONST.BLEND_MODES.OVERLAY] = 'overlay'; + array[CONST.BLEND_MODES.DARKEN] = 'darken'; + array[CONST.BLEND_MODES.LIGHTEN] = 'lighten'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'color-dodge'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'color-burn'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'hard-light'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'soft-light'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'difference'; + array[CONST.BLEND_MODES.EXCLUSION] = 'exclusion'; + array[CONST.BLEND_MODES.HUE] = 'hue'; + array[CONST.BLEND_MODES.SATURATION] = 'saturate'; + array[CONST.BLEND_MODES.COLOR] = 'color'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'luminosity'; + } + else + { + // this means that the browser does not support the cool new blend modes in canvas 'cough' ie 'cough' + array[CONST.BLEND_MODES.NORMAL] = 'source-over'; + array[CONST.BLEND_MODES.ADD] = 'lighter'; //IS THIS OK??? + array[CONST.BLEND_MODES.MULTIPLY] = 'source-over'; + array[CONST.BLEND_MODES.SCREEN] = 'source-over'; + array[CONST.BLEND_MODES.OVERLAY] = 'source-over'; + array[CONST.BLEND_MODES.DARKEN] = 'source-over'; + array[CONST.BLEND_MODES.LIGHTEN] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_DODGE] = 'source-over'; + array[CONST.BLEND_MODES.COLOR_BURN] = 'source-over'; + array[CONST.BLEND_MODES.HARD_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.SOFT_LIGHT] = 'source-over'; + array[CONST.BLEND_MODES.DIFFERENCE] = 'source-over'; + array[CONST.BLEND_MODES.EXCLUSION] = 'source-over'; + array[CONST.BLEND_MODES.HUE] = 'source-over'; + array[CONST.BLEND_MODES.SATURATION] = 'source-over'; + array[CONST.BLEND_MODES.COLOR] = 'source-over'; + array[CONST.BLEND_MODES.LUMINOSITY] = 'source-over'; + } + + return array +} + +module.exports = mapWebGLBlendModesToPixi; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 9e46aed..c96030b 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -1,11 +1,9 @@ var math = require('../math'), Texture = require('../textures/Texture'), Container = require('../display/Container'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), utils = require('../utils'), CONST = require('../const'), tempPoint = new math.Point(), - GroupD8 = math.GroupD8, canvasRenderWorldTransform = new math.Matrix(); /** @@ -270,6 +268,18 @@ }; /** +* Renders the object using the Canvas renderer +* +* @param renderer {PIXI.CanvasRenderer} The renderer +* @private +*/ +Sprite.prototype._renderCanvas = function (renderer) +{ + renderer.plugins.sprite.render(this); +}; + + +/** * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. * * @param matrix {PIXI.Matrix} the transformation matrix of the sprite @@ -400,134 +410,6 @@ return false; }; -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - if (this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - renderer.context.globalCompositeOperation = compositeOperation; - } - - // Ignore null sources - if (this.texture.valid) - { - var texture = this._texture, - wt = this.worldTransform, - dx, - dy, - width = texture.crop.width, - height = texture.crop.height; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - - //inline GroupD8.isSwapWidthHeight - if ((texture.rotate & 3) === 2) { - width = texture.crop.height; - height = texture.crop.width; - } - if (texture.trim) { - dx = texture.crop.width/2 + texture.trim.x - this.anchor.x * texture.trim.width; - dy = texture.crop.height/2 + texture.trim.y - this.anchor.y * texture.trim.height; - } else { - dx = (0.5 - this.anchor.x) * texture._frame.width; - dy = (0.5 - this.anchor.y) * texture._frame.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - texture.baseTexture.source, - texture.frame.x * resolution, - texture.frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; /** * Destroys this sprite and optionally its texture diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js new file mode 100644 index 0000000..4e8a35c --- /dev/null +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -0,0 +1,160 @@ +var CanvasRenderer = require('../../renderers/canvas/CanvasRenderer'), + CONST = require('../../const'), + CanvasTinter = require('./CanvasTinter') + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's CanvasSpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/CanvasSpriteRenderer.java + */ + +/** + * Renderer dedicated to drawing and batching sprites. + * + * @class + * @private + * @memberof PIXI + * @extends PIXI.ObjectRenderer + * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. + */ +function CanvasSpriteRenderer(renderer) +{ + this.renderer = renderer; +} + + +CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; +module.exports = CanvasSpriteRenderer; + +CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); + +/** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ +CanvasSpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture.crop.width, + height = texture.crop.height; + + if (texture.crop.width <= 0 || texture.crop.height <= 0) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + + //inline GroupD8.isSwapWidthHeight + if ((texture.rotate & 3) === 2) { + width = texture.crop.height; + height = texture.crop.width; + } + if (texture.trim) { + dx = texture.crop.width/2 + texture.trim.x - sprite.anchor.x * texture.trim.width; + dy = texture.crop.height/2 + texture.trim.y - sprite.anchor.y * texture.trim.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture._frame.width; + dy = (0.5 - sprite.anchor.y) * texture._frame.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture.frame.x * resolution, + texture.frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } +}; diff --git a/src/core/sprites/canvas/CanvasTinter.js b/src/core/sprites/canvas/CanvasTinter.js new file mode 100644 index 0000000..640734d --- /dev/null +++ b/src/core/sprites/canvas/CanvasTinter.js @@ -0,0 +1,238 @@ +var utils = require('../../utils'), +canUseNewCanvasBlendModes = require('../../renderers/canvas/utils/canUseNewCanvasBlendModes'); +/** + * Utility methods for Sprite/Texture tinting. + * @static + * @class + * @memberof PIXI + */ +var CanvasTinter = {}; +module.exports = CanvasTinter; + +/** + * Basically this method just needs a sprite and a color and tints the sprite with the given color. + * + * @param sprite {PIXI.Sprite} the sprite to tint + * @param color {number} the color to use to tint the sprite with + * @return {HTMLCanvasElement} The tinted canvas + */ +CanvasTinter.getTintedTexture = function (sprite, color) +{ + var texture = sprite.texture; + + color = CanvasTinter.roundColor(color); + + var stringColor = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + texture.tintCache = texture.tintCache || {}; + + if (texture.tintCache[stringColor]) + { + return texture.tintCache[stringColor]; + } + + // clone texture.. + var canvas = CanvasTinter.canvas || document.createElement('canvas'); + + //CanvasTinter.tintWithPerPixel(texture, stringColor, canvas); + CanvasTinter.tintMethod(texture, color, canvas); + + if (CanvasTinter.convertTintToImage) + { + // is this better? + var tintImage = new Image(); + tintImage.src = canvas.toDataURL(); + + texture.tintCache[stringColor] = tintImage; + } + else + { + texture.tintCache[stringColor] = canvas; + // if we are not converting the texture to an image then we need to lose the reference to the canvas + CanvasTinter.canvas = null; + } + + return canvas; +}; + +/** + * Tint a texture using the 'multiply' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithMultiply = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'multiply'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + context.globalCompositeOperation = 'destination-atop'; + + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); +}; + +/** + * Tint a texture using the 'overlay' operation. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithOverlay = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.fillStyle = '#' + ('00000' + ( color | 0).toString(16)).substr(-6); + context.fillRect(0, 0, crop.width, crop.height); + + context.globalCompositeOperation = 'destination-atop'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + // context.globalCompositeOperation = 'copy'; +}; + +/** + * Tint a texture pixel per pixel. + * + * @param texture {PIXI.Texture} the texture to tint + * @param color {number} the color to use to tint the sprite with + * @param canvas {HTMLCanvasElement} the current canvas + */ +CanvasTinter.tintWithPerPixel = function (texture, color, canvas) +{ + var context = canvas.getContext( '2d' ); + + var resolution = texture.baseTexture.resolution; + + var crop = texture._frame; + + canvas.width = crop.width; + canvas.height = crop.height; + + context.globalCompositeOperation = 'copy'; + context.drawImage( + texture.baseTexture.source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + var rgbValues = utils.hex2rgb(color); + var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2]; + + var pixelData = context.getImageData(0, 0, crop.width, crop.height); + + var pixels = pixelData.data; + + for (var i = 0; i < pixels.length; i += 4) + { + pixels[i+0] *= r; + pixels[i+1] *= g; + pixels[i+2] *= b; + } + + context.putImageData(pixelData, 0, 0); +}; + +/** + * Rounds the specified color according to the CanvasTinter.cacheStepsPerColorChannel. + * + * @param color {number} the color to round, should be a hex color + */ +CanvasTinter.roundColor = function (color) +{ + var step = CanvasTinter.cacheStepsPerColorChannel; + + var rgbValues = utils.hex2rgb(color); + + rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step); + rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step); + rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step); + + return utils.rgb2hex(rgbValues); +}; + +/** + * Number of steps which will be used as a cap when rounding colors. + * + * @member + */ +CanvasTinter.cacheStepsPerColorChannel = 8; + +/** + * Tint cache boolean flag. + * + * @member + */ +CanvasTinter.convertTintToImage = false; + +/** + * Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method. + * + * @member + */ +CanvasTinter.canUseMultiply = canUseNewCanvasBlendModes(); + +/** + * The tinting method that will be used. + * + */ +CanvasTinter.tintMethod = CanvasTinter.canUseMultiply ? CanvasTinter.tintWithMultiply : CanvasTinter.tintWithPerPixel; diff --git a/src/core/sprites/renderWebGL.js b/src/core/sprites/renderWebGL.js deleted file mode 100644 index d857e8f..0000000 --- a/src/core/sprites/renderWebGL.js +++ /dev/null @@ -1,23 +0,0 @@ -var = renderWebGL = function (renderer, sprite) -{ - if(this.textureDirty) - { - this.textureDirty = false; - - this._onTextureUpdate(); - - this.vertexDirty = true; - } - - if(this.vertexDirty) - { - this.vertexDirty = false; - - // set the vertex data - this.caclulateVertices(); - - } - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index e61a97b..6d375ea 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -2,7 +2,6 @@ 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(), @@ -68,6 +67,8 @@ this._glRenderTargets = []; + this._canvasRenderTarget = null; + /** * The renderer this BaseRenderTexture uses. A BaseRenderTexture can only belong to one renderer at the moment if its webGL. * diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 29cd7fc..241c91a 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -64,40 +64,7 @@ return ((rgb[0]*255 << 16) + (rgb[1]*255 << 8) + rgb[2]*255); }, - /** - * Checks whether the Canvas BlendModes are supported by the current browser - * - * @return {boolean} whether they are supported - */ - canUseNewCanvasBlendModes: function () - { - if (typeof document === 'undefined') - { - return false; - } - - var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/'; - var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=='; - - var magenta = new Image(); - magenta.src = pngHead + 'AP804Oa6' + pngEnd; - - var yellow = new Image(); - yellow.src = pngHead + '/wCKxvRF' + pngEnd; - - var canvas = document.createElement('canvas'); - canvas.width = 6; - canvas.height = 1; - - var context = canvas.getContext('2d'); - context.globalCompositeOperation = 'multiply'; - context.drawImage(magenta, 0, 0); - context.drawImage(yellow, 2, 0); - - var data = context.getImageData(2,0,1,1).data; - - return (data[0] === 255 && data[1] === 0 && data[2] === 0); - }, + /** * Given a number, this function returns the closest number that is a power of two diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index 82dc55c..eb33116 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -144,7 +144,6 @@ // set all properties to there original so we can render to a texture this.renderWebGL = this._originalRenderWebGL; -// renderer.clear([0.5, 0.5, 0.5, 0.5]) renderer.render(this, renderTexture, true, m, true); // now restore the state be setting the new properties @@ -209,18 +208,22 @@ var cachedRenderTarget = renderer.context; - var renderTexture = new core.RenderTexture(renderer, bounds.width | 0, bounds.height | 0); + var renderTexture = new core.RenderTexture.create(bounds.width | 0, bounds.height | 0); // need to set // var m = _tempMatrix; + this.transform.worldTransform.copy(m); + m.invert(); - m.tx = -bounds.x; - m.ty = -bounds.y; + m.tx -= bounds.x; + m.ty -= bounds.y; - // set all properties to there original so we can render to a texture + //m.append(this.transform.worldTransform.) + // set all properties to there original so we can render to a texture this.renderCanvas = this._originalRenderCanvas; - renderTexture.render(this, m, true); + //renderTexture.render(this, m, true); + renderer.render(this, renderTexture, true, m, false); // now restore the state be setting the new properties renderer.context = cachedRenderTarget;