diff --git a/packages/canvas/canvas-renderer/src/CanvasRenderer.js b/packages/canvas/canvas-renderer/src/CanvasRenderer.js index 61a9273..1d51aa0 100644 --- a/packages/canvas/canvas-renderer/src/CanvasRenderer.js +++ b/packages/canvas/canvas-renderer/src/CanvasRenderer.js @@ -105,6 +105,7 @@ */ this.blendModes = mapCanvasBlendModesToPixi(); this._activeBlendMode = null; + this._outerBlend = false; this.renderingToScreen = false; @@ -208,6 +209,7 @@ context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; + this._outerBlend = false; context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; if (navigator.isCocoonJS && this.view.screencanvas) @@ -274,15 +276,28 @@ * Sets the blend mode of the renderer. * * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. */ - setBlendMode(blendMode) + setBlendMode(blendMode, readyForOuterBlend) { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + if (this._activeBlendMode === blendMode) { return; } this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; this.context.globalCompositeOperation = this.blendModes[blendMode]; } diff --git a/packages/canvas/canvas-renderer/src/CanvasRenderer.js b/packages/canvas/canvas-renderer/src/CanvasRenderer.js index 61a9273..1d51aa0 100644 --- a/packages/canvas/canvas-renderer/src/CanvasRenderer.js +++ b/packages/canvas/canvas-renderer/src/CanvasRenderer.js @@ -105,6 +105,7 @@ */ this.blendModes = mapCanvasBlendModesToPixi(); this._activeBlendMode = null; + this._outerBlend = false; this.renderingToScreen = false; @@ -208,6 +209,7 @@ context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; + this._outerBlend = false; context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; if (navigator.isCocoonJS && this.view.screencanvas) @@ -274,15 +276,28 @@ * Sets the blend mode of the renderer. * * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. */ - setBlendMode(blendMode) + setBlendMode(blendMode, readyForOuterBlend) { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + if (this._activeBlendMode === blendMode) { return; } this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; this.context.globalCompositeOperation = this.blendModes[blendMode]; } diff --git a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js index c8f6eea..66bd2eb 100644 --- a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js +++ b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js @@ -58,5 +58,17 @@ array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; + // composite operations + array[BLEND_MODES.SRC_IN] = 'source-in'; + array[BLEND_MODES.SRC_OUT] = 'source-out'; + array[BLEND_MODES.SRC_ATOP] = 'source-atop'; + array[BLEND_MODES.DST_OVER] = 'destination-over'; + array[BLEND_MODES.DST_IN] = 'destination-in'; + array[BLEND_MODES.DST_OUT] = 'destination-out'; + array[BLEND_MODES.DST_ATOP] = 'destination-atop'; + + // SUBTRACT from flash, does not exist in canvas + array[BLEND_MODES.SUBTRACT] = 'source-over'; + return array; } diff --git a/packages/canvas/canvas-renderer/src/CanvasRenderer.js b/packages/canvas/canvas-renderer/src/CanvasRenderer.js index 61a9273..1d51aa0 100644 --- a/packages/canvas/canvas-renderer/src/CanvasRenderer.js +++ b/packages/canvas/canvas-renderer/src/CanvasRenderer.js @@ -105,6 +105,7 @@ */ this.blendModes = mapCanvasBlendModesToPixi(); this._activeBlendMode = null; + this._outerBlend = false; this.renderingToScreen = false; @@ -208,6 +209,7 @@ context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; + this._outerBlend = false; context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; if (navigator.isCocoonJS && this.view.screencanvas) @@ -274,15 +276,28 @@ * Sets the blend mode of the renderer. * * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. */ - setBlendMode(blendMode) + setBlendMode(blendMode, readyForOuterBlend) { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + if (this._activeBlendMode === blendMode) { return; } this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; this.context.globalCompositeOperation = this.blendModes[blendMode]; } diff --git a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js index c8f6eea..66bd2eb 100644 --- a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js +++ b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js @@ -58,5 +58,17 @@ array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; + // composite operations + array[BLEND_MODES.SRC_IN] = 'source-in'; + array[BLEND_MODES.SRC_OUT] = 'source-out'; + array[BLEND_MODES.SRC_ATOP] = 'source-atop'; + array[BLEND_MODES.DST_OVER] = 'destination-over'; + array[BLEND_MODES.DST_IN] = 'destination-in'; + array[BLEND_MODES.DST_OUT] = 'destination-out'; + array[BLEND_MODES.DST_ATOP] = 'destination-atop'; + + // SUBTRACT from flash, does not exist in canvas + array[BLEND_MODES.SUBTRACT] = 'source-over'; + return array; } diff --git a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js index 09fde6d..ebf7397 100644 --- a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js +++ b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js @@ -1,4 +1,4 @@ -import { SCALE_MODES } from '@pixi/constants'; +import { SCALE_MODES, BLEND_MODES } from '@pixi/constants'; import { Matrix, GroupD8 } from '@pixi/math'; import CanvasTinter from './CanvasTinter'; @@ -47,6 +47,7 @@ { const texture = sprite._texture; const renderer = this.renderer; + const context = renderer.context; const width = texture._frame.width; const height = texture._frame.height; @@ -62,111 +63,133 @@ return; } - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) + if (!texture.valid) { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; - - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) - { - dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); - dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); - } - else - { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - - if (texture.rotate) - { - wt.copyTo(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 (sprite.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 - ); - } - - const resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - source, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - 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 - ); - } + return; } + + renderer.setBlendMode(sprite.blendMode, true); + + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; + + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) + { + dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); + dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); + } + else + { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + + if (texture.rotate) + { + wt.copyTo(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 (sprite.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 + ); + } + + const resolution = texture.baseTexture.resolution; + const outerBlend = renderer._outerBlend; + + if (outerBlend) + { + context.save(); + context.beginPath(); + context.rect( + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + context.clip(); + } + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + context.drawImage( + source, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + context.drawImage( + 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 + ); + } + + if (outerBlend) + { + context.restore(); + } + // just in case, leaking outer blend here will be catastrophic! + renderer.setBlendMode(BLEND_MODES.NORMAL); } /** diff --git a/packages/canvas/canvas-renderer/src/CanvasRenderer.js b/packages/canvas/canvas-renderer/src/CanvasRenderer.js index 61a9273..1d51aa0 100644 --- a/packages/canvas/canvas-renderer/src/CanvasRenderer.js +++ b/packages/canvas/canvas-renderer/src/CanvasRenderer.js @@ -105,6 +105,7 @@ */ this.blendModes = mapCanvasBlendModesToPixi(); this._activeBlendMode = null; + this._outerBlend = false; this.renderingToScreen = false; @@ -208,6 +209,7 @@ context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; + this._outerBlend = false; context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; if (navigator.isCocoonJS && this.view.screencanvas) @@ -274,15 +276,28 @@ * Sets the blend mode of the renderer. * * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. */ - setBlendMode(blendMode) + setBlendMode(blendMode, readyForOuterBlend) { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + if (this._activeBlendMode === blendMode) { return; } this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; this.context.globalCompositeOperation = this.blendModes[blendMode]; } diff --git a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js index c8f6eea..66bd2eb 100644 --- a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js +++ b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js @@ -58,5 +58,17 @@ array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; + // composite operations + array[BLEND_MODES.SRC_IN] = 'source-in'; + array[BLEND_MODES.SRC_OUT] = 'source-out'; + array[BLEND_MODES.SRC_ATOP] = 'source-atop'; + array[BLEND_MODES.DST_OVER] = 'destination-over'; + array[BLEND_MODES.DST_IN] = 'destination-in'; + array[BLEND_MODES.DST_OUT] = 'destination-out'; + array[BLEND_MODES.DST_ATOP] = 'destination-atop'; + + // SUBTRACT from flash, does not exist in canvas + array[BLEND_MODES.SUBTRACT] = 'source-over'; + return array; } diff --git a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js index 09fde6d..ebf7397 100644 --- a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js +++ b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js @@ -1,4 +1,4 @@ -import { SCALE_MODES } from '@pixi/constants'; +import { SCALE_MODES, BLEND_MODES } from '@pixi/constants'; import { Matrix, GroupD8 } from '@pixi/math'; import CanvasTinter from './CanvasTinter'; @@ -47,6 +47,7 @@ { const texture = sprite._texture; const renderer = this.renderer; + const context = renderer.context; const width = texture._frame.width; const height = texture._frame.height; @@ -62,111 +63,133 @@ return; } - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) + if (!texture.valid) { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; - - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) - { - dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); - dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); - } - else - { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - - if (texture.rotate) - { - wt.copyTo(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 (sprite.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 - ); - } - - const resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - source, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - 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 - ); - } + return; } + + renderer.setBlendMode(sprite.blendMode, true); + + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; + + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) + { + dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); + dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); + } + else + { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + + if (texture.rotate) + { + wt.copyTo(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 (sprite.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 + ); + } + + const resolution = texture.baseTexture.resolution; + const outerBlend = renderer._outerBlend; + + if (outerBlend) + { + context.save(); + context.beginPath(); + context.rect( + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + context.clip(); + } + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + context.drawImage( + source, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + context.drawImage( + 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 + ); + } + + if (outerBlend) + { + context.restore(); + } + // just in case, leaking outer blend here will be catastrophic! + renderer.setBlendMode(BLEND_MODES.NORMAL); } /** diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index f06f4a5..d29f4d9 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -63,6 +63,16 @@ * @property {number} ADD_NPM * @property {number} SCREEN_NPM * @property {number} NONE + * @property {number} SRC_IN + * @property {number} SRC_OUT + * @property {number} SRC_ATOP + * @property {number} DST_OVER + * @property {number} DST_IN + * @property {number} DST_OUT + * @property {number} DST_ATOP + * @property {number} SUBTRACT + * @property {number} SRC_OVER + * @property {number} ERASE */ export const BLEND_MODES = { NORMAL: 0, @@ -86,6 +96,17 @@ ADD_NPM: 18, SCREEN_NPM: 19, NONE: 20, + + SRC_OVER: 0, + SRC_IN: 21, + SRC_OUT: 22, + SRC_ATOP: 23, + DST_OVER: 24, + DST_IN: 25, + DST_OUT: 26, + DST_ATOP: 27, + ERASE: 26, + SUBTRACT: 28, }; /** diff --git a/packages/canvas/canvas-renderer/src/CanvasRenderer.js b/packages/canvas/canvas-renderer/src/CanvasRenderer.js index 61a9273..1d51aa0 100644 --- a/packages/canvas/canvas-renderer/src/CanvasRenderer.js +++ b/packages/canvas/canvas-renderer/src/CanvasRenderer.js @@ -105,6 +105,7 @@ */ this.blendModes = mapCanvasBlendModesToPixi(); this._activeBlendMode = null; + this._outerBlend = false; this.renderingToScreen = false; @@ -208,6 +209,7 @@ context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; + this._outerBlend = false; context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; if (navigator.isCocoonJS && this.view.screencanvas) @@ -274,15 +276,28 @@ * Sets the blend mode of the renderer. * * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. */ - setBlendMode(blendMode) + setBlendMode(blendMode, readyForOuterBlend) { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + if (this._activeBlendMode === blendMode) { return; } this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; this.context.globalCompositeOperation = this.blendModes[blendMode]; } diff --git a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js index c8f6eea..66bd2eb 100644 --- a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js +++ b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js @@ -58,5 +58,17 @@ array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; + // composite operations + array[BLEND_MODES.SRC_IN] = 'source-in'; + array[BLEND_MODES.SRC_OUT] = 'source-out'; + array[BLEND_MODES.SRC_ATOP] = 'source-atop'; + array[BLEND_MODES.DST_OVER] = 'destination-over'; + array[BLEND_MODES.DST_IN] = 'destination-in'; + array[BLEND_MODES.DST_OUT] = 'destination-out'; + array[BLEND_MODES.DST_ATOP] = 'destination-atop'; + + // SUBTRACT from flash, does not exist in canvas + array[BLEND_MODES.SUBTRACT] = 'source-over'; + return array; } diff --git a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js index 09fde6d..ebf7397 100644 --- a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js +++ b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js @@ -1,4 +1,4 @@ -import { SCALE_MODES } from '@pixi/constants'; +import { SCALE_MODES, BLEND_MODES } from '@pixi/constants'; import { Matrix, GroupD8 } from '@pixi/math'; import CanvasTinter from './CanvasTinter'; @@ -47,6 +47,7 @@ { const texture = sprite._texture; const renderer = this.renderer; + const context = renderer.context; const width = texture._frame.width; const height = texture._frame.height; @@ -62,111 +63,133 @@ return; } - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) + if (!texture.valid) { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; - - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) - { - dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); - dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); - } - else - { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - - if (texture.rotate) - { - wt.copyTo(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 (sprite.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 - ); - } - - const resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - source, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - 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 - ); - } + return; } + + renderer.setBlendMode(sprite.blendMode, true); + + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; + + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) + { + dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); + dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); + } + else + { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + + if (texture.rotate) + { + wt.copyTo(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 (sprite.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 + ); + } + + const resolution = texture.baseTexture.resolution; + const outerBlend = renderer._outerBlend; + + if (outerBlend) + { + context.save(); + context.beginPath(); + context.rect( + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + context.clip(); + } + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + context.drawImage( + source, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + context.drawImage( + 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 + ); + } + + if (outerBlend) + { + context.restore(); + } + // just in case, leaking outer blend here will be catastrophic! + renderer.setBlendMode(BLEND_MODES.NORMAL); } /** diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index f06f4a5..d29f4d9 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -63,6 +63,16 @@ * @property {number} ADD_NPM * @property {number} SCREEN_NPM * @property {number} NONE + * @property {number} SRC_IN + * @property {number} SRC_OUT + * @property {number} SRC_ATOP + * @property {number} DST_OVER + * @property {number} DST_IN + * @property {number} DST_OUT + * @property {number} DST_ATOP + * @property {number} SUBTRACT + * @property {number} SRC_OVER + * @property {number} ERASE */ export const BLEND_MODES = { NORMAL: 0, @@ -86,6 +96,17 @@ ADD_NPM: 18, SCREEN_NPM: 19, NONE: 20, + + SRC_OVER: 0, + SRC_IN: 21, + SRC_OUT: 22, + SRC_ATOP: 23, + DST_OVER: 24, + DST_IN: 25, + DST_OUT: 26, + DST_ATOP: 27, + ERASE: 26, + SUBTRACT: 28, }; /** diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js index 6493994..90c4ed6 100755 --- a/packages/core/src/state/StateSystem.js +++ b/packages/core/src/state/StateSystem.js @@ -78,6 +78,13 @@ this.blendMode = BLEND_MODES.NONE; /** + * Whether current blend equation is different + * @member {boolean} + * @protected + */ + this._blendEq = false; + + /** * Collection of calls * @member {function[]} * @readonly @@ -240,14 +247,25 @@ this.blendMode = value; const mode = this.blendModes[value]; + const gl = this.gl; if (mode.length === 2) { - this.gl.blendFunc(mode[0], mode[1]); + gl.blendFunc(mode[0], mode[1]); } else { - this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } + if (mode.length === 6) + { + this._blendEq = true; + gl.blendEquationSeparate(mode[4], mode[5]); + } + else if (this._blendEq) + { + this._blendEq = false; + gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); } } @@ -302,6 +320,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this._blendEq = true; + this.setBlendMode(0); // TODO? diff --git a/packages/canvas/canvas-renderer/src/CanvasRenderer.js b/packages/canvas/canvas-renderer/src/CanvasRenderer.js index 61a9273..1d51aa0 100644 --- a/packages/canvas/canvas-renderer/src/CanvasRenderer.js +++ b/packages/canvas/canvas-renderer/src/CanvasRenderer.js @@ -105,6 +105,7 @@ */ this.blendModes = mapCanvasBlendModesToPixi(); this._activeBlendMode = null; + this._outerBlend = false; this.renderingToScreen = false; @@ -208,6 +209,7 @@ context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; + this._outerBlend = false; context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; if (navigator.isCocoonJS && this.view.screencanvas) @@ -274,15 +276,28 @@ * Sets the blend mode of the renderer. * * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. */ - setBlendMode(blendMode) + setBlendMode(blendMode, readyForOuterBlend) { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + if (this._activeBlendMode === blendMode) { return; } this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; this.context.globalCompositeOperation = this.blendModes[blendMode]; } diff --git a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js index c8f6eea..66bd2eb 100644 --- a/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js +++ b/packages/canvas/canvas-renderer/src/utils/mapCanvasBlendModesToPixi.js @@ -58,5 +58,17 @@ array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; + // composite operations + array[BLEND_MODES.SRC_IN] = 'source-in'; + array[BLEND_MODES.SRC_OUT] = 'source-out'; + array[BLEND_MODES.SRC_ATOP] = 'source-atop'; + array[BLEND_MODES.DST_OVER] = 'destination-over'; + array[BLEND_MODES.DST_IN] = 'destination-in'; + array[BLEND_MODES.DST_OUT] = 'destination-out'; + array[BLEND_MODES.DST_ATOP] = 'destination-atop'; + + // SUBTRACT from flash, does not exist in canvas + array[BLEND_MODES.SUBTRACT] = 'source-over'; + return array; } diff --git a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js index 09fde6d..ebf7397 100644 --- a/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js +++ b/packages/canvas/canvas-sprite/src/CanvasSpriteRenderer.js @@ -1,4 +1,4 @@ -import { SCALE_MODES } from '@pixi/constants'; +import { SCALE_MODES, BLEND_MODES } from '@pixi/constants'; import { Matrix, GroupD8 } from '@pixi/math'; import CanvasTinter from './CanvasTinter'; @@ -47,6 +47,7 @@ { const texture = sprite._texture; const renderer = this.renderer; + const context = renderer.context; const width = texture._frame.width; const height = texture._frame.height; @@ -62,111 +63,133 @@ return; } - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) + if (!texture.valid) { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; - - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) - { - dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); - dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); - } - else - { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - - if (texture.rotate) - { - wt.copyTo(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 (sprite.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 - ); - } - - const resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - source, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - renderer.context.drawImage( - 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 - ); - } + return; } + + renderer.setBlendMode(sprite.blendMode, true); + + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; + + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) + { + dx = (texture.trim.width / 2) + texture.trim.x - (sprite.anchor.x * texture.orig.width); + dy = (texture.trim.height / 2) + texture.trim.y - (sprite.anchor.y * texture.orig.height); + } + else + { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + + if (texture.rotate) + { + wt.copyTo(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 (sprite.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 + ); + } + + const resolution = texture.baseTexture.resolution; + const outerBlend = renderer._outerBlend; + + if (outerBlend) + { + context.save(); + context.beginPath(); + context.rect( + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + context.clip(); + } + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint || sprite.tintedTexture.tintId !== sprite._texture._updateID) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + context.drawImage( + source, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + context.drawImage( + 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 + ); + } + + if (outerBlend) + { + context.restore(); + } + // just in case, leaking outer blend here will be catastrophic! + renderer.setBlendMode(BLEND_MODES.NORMAL); } /** diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index f06f4a5..d29f4d9 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -63,6 +63,16 @@ * @property {number} ADD_NPM * @property {number} SCREEN_NPM * @property {number} NONE + * @property {number} SRC_IN + * @property {number} SRC_OUT + * @property {number} SRC_ATOP + * @property {number} DST_OVER + * @property {number} DST_IN + * @property {number} DST_OUT + * @property {number} DST_ATOP + * @property {number} SUBTRACT + * @property {number} SRC_OVER + * @property {number} ERASE */ export const BLEND_MODES = { NORMAL: 0, @@ -86,6 +96,17 @@ ADD_NPM: 18, SCREEN_NPM: 19, NONE: 20, + + SRC_OVER: 0, + SRC_IN: 21, + SRC_OUT: 22, + SRC_ATOP: 23, + DST_OVER: 24, + DST_IN: 25, + DST_OUT: 26, + DST_ATOP: 27, + ERASE: 26, + SUBTRACT: 28, }; /** diff --git a/packages/core/src/state/StateSystem.js b/packages/core/src/state/StateSystem.js index 6493994..90c4ed6 100755 --- a/packages/core/src/state/StateSystem.js +++ b/packages/core/src/state/StateSystem.js @@ -78,6 +78,13 @@ this.blendMode = BLEND_MODES.NONE; /** + * Whether current blend equation is different + * @member {boolean} + * @protected + */ + this._blendEq = false; + + /** * Collection of calls * @member {function[]} * @readonly @@ -240,14 +247,25 @@ this.blendMode = value; const mode = this.blendModes[value]; + const gl = this.gl; if (mode.length === 2) { - this.gl.blendFunc(mode[0], mode[1]); + gl.blendFunc(mode[0], mode[1]); } else { - this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } + if (mode.length === 6) + { + this._blendEq = true; + gl.blendEquationSeparate(mode[4], mode[5]); + } + else if (this._blendEq) + { + this._blendEq = false; + gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); } } @@ -302,6 +320,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this._blendEq = true; + this.setBlendMode(0); // TODO? diff --git a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js index c307d79..5a2ddd4 100644 --- a/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js +++ b/packages/core/src/state/utils/mapWebGLBlendModesToPixi.js @@ -15,9 +15,9 @@ // TODO - premultiply alpha would be different. // add a boolean for that! array[BLEND_MODES.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + array[BLEND_MODES.ADD] = [gl.ONE, gl.DST_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.SCREEN] = [gl.ONE, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; @@ -38,10 +38,17 @@ array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; - // not-premultiplied blend modes - array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; - array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; - array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + // composite operations + array[BLEND_MODES.SRC_IN] = [gl.DST_ALPHA, gl.ZERO]; + array[BLEND_MODES.SRC_OUT] = [gl.ONE_MINUS_DST_ALPHA, gl.ZERO]; + array[BLEND_MODES.SRC_ATOP] = [gl.DST_ALPHA, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DST_OVER] = [gl.ONE_MINUS_DST_ALPHA, gl.ONE]; + array[BLEND_MODES.DST_IN] = [gl.ZERO, gl.SRC_ALPHA]; + array[BLEND_MODES.DST_OUT] = [gl.ZERO, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.DST_ATOP] = [gl.ONE_MINUS_DST_ALPHA, gl.SRC_ALPHA]; + + // SUBTRACT from flash + array[BLEND_MODES.SUBTRACT] = [gl.ONE, gl.ONE, gl.ONE, gl.ONE, gl.FUNC_REVERSE_SUBTRACT, gl.FUNC_ADD]; return array; }