diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/test/loaders/resources/atlas@0.5x.json b/test/loaders/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/test/loaders/resources/atlas@0.5x.json b/test/loaders/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas@0.5x.png b/test/loaders/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.png Binary files differ diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/test/loaders/resources/atlas@0.5x.json b/test/loaders/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas@0.5x.png b/test/loaders/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.png Binary files differ diff --git a/test/loaders/resources/font.fnt b/test/loaders/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/test/loaders/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/test/loaders/resources/atlas@0.5x.json b/test/loaders/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas@0.5x.png b/test/loaders/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.png Binary files differ diff --git a/test/loaders/resources/font.fnt b/test/loaders/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/test/loaders/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/loaders/resources/font.png b/test/loaders/resources/font.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/test/loaders/resources/font.png Binary files differ diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/test/loaders/resources/atlas@0.5x.json b/test/loaders/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas@0.5x.png b/test/loaders/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.png Binary files differ diff --git a/test/loaders/resources/font.fnt b/test/loaders/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/test/loaders/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/loaders/resources/font.png b/test/loaders/resources/font.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/test/loaders/resources/font.png Binary files differ diff --git a/test/loaders/resources/font@0.5x.fnt b/test/loaders/resources/font@0.5x.fnt new file mode 100644 index 0000000..6c247c7 --- /dev/null +++ b/test/loaders/resources/font@0.5x.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/core/index.js b/src/core/index.js index 3265dfb..1a92622 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -33,6 +33,7 @@ export { default as CanvasGraphicsRenderer } from './graphics/canvas/CanvasGraphicsRenderer'; export { default as Spritesheet } from './textures/Spritesheet'; export { default as Texture } from './textures/Texture'; +export { default as TextureMatrix } from './textures/TextureMatrix'; export { default as BaseTexture } from './textures/BaseTexture'; export { default as RenderTexture } from './textures/RenderTexture'; export { default as BaseRenderTexture } from './textures/BaseRenderTexture'; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js index 50c2f31..e6a09e7 100644 --- a/src/core/math/GroupD8.js +++ b/src/core/math/GroupD8.js @@ -111,13 +111,14 @@ rotate180: (rotation) => rotation ^ 4, /** - * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * Direction of main vector can be horizontal, vertical or diagonal. + * Some objects work with vertical directions different. * * @memberof PIXI.GroupD8 * @param {number} rotation - The number to check. - * @returns {boolean} Whether or not the width/height should be swapped. + * @returns {boolean} Whether or not the direction is vertical */ - isSwapWidthHeight: (rotation) => (rotation & 3) === 2, + isVertical: (rotation) => (rotation & 3) === 2, /** * @memberof PIXI.GroupD8 diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index 4dcd773..8c4789c 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -199,6 +199,7 @@ // displayObject.hitArea = //TODO add a temp hit area } + context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; this._activeBlendMode = BLEND_MODES.NORMAL; @@ -235,6 +236,8 @@ displayObject.renderCanvas(this); this.context = tempContext; + context.restore(); + this.resolution = rootResolution; this.emit('postrender'); diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index e63d2b0..3f432a1 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -662,8 +662,14 @@ this.setObjectRenderer(this.emptyRenderer); this._activeShader = null; + this._activeVao = null; this._activeRenderTarget = this.rootRenderTarget; + for (let i = 0; i < this.boundTextures.length; i++) + { + this.boundTextures[i] = this.emptyTextures[i]; + } + // bind the main frame buffer (the screen); this.rootRenderTarget.activate(); diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index f955a9d..2d08dbc 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -40,13 +40,13 @@ // this will map the filter coord so that a texture can be used based on the transform of a sprite export function calculateSpriteMatrix(outputMatrix, filterArea, textureSize, sprite) { - const texture = sprite._texture.baseTexture; + const orig = sprite._texture.orig; const mappedMatrix = outputMatrix.set(textureSize.width, 0, 0, textureSize.height, filterArea.x, filterArea.y); const worldTransform = sprite.worldTransform.copy(Matrix.TEMP_MATRIX); worldTransform.invert(); mappedMatrix.prepend(worldTransform); - mappedMatrix.scale(1.0 / texture.width, 1.0 / texture.height); + mappedMatrix.scale(1.0 / orig.width, 1.0 / orig.height); mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); return mappedMatrix; diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 1e86c68..e5fd49c 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -2,6 +2,7 @@ import { Matrix } from '../../../../math'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { default as TextureMatrix } from '../../../../textures/TextureMatrix'; /** * The SpriteMaskFilter class @@ -40,10 +41,25 @@ apply(filterManager, input, output) { const maskSprite = this.maskSprite; + const tex = this.maskSprite.texture; - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite); + if (!tex.valid) + { + return; + } + if (!tex.transform) + { + // margin = 0.0, let it bleed a bit, shader code becomes easier + // assuming that atlas textures were made with 1-pixel padding + tex.transform = new TextureMatrix(tex, 0.0); + } + tex.transform.update(); + + this.uniforms.mask = tex; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite) + .prepend(tex.transform.mapCoord); this.uniforms.alpha = maskSprite.worldAlpha; + this.uniforms.maskClamp = tex.transform.uClampFrame; filterManager.applyFilter(this, input, output); } diff --git a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag index 4a419a1..0e4aef8 100644 --- a/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag +++ b/src/core/renderers/webgl/filters/spriteMask/spriteMaskFilter.frag @@ -2,16 +2,18 @@ varying vec2 vTextureCoord; uniform sampler2D uSampler; -uniform float alpha; uniform sampler2D mask; +uniform float alpha; +uniform vec4 maskClamp; void main(void) { - // check clip! this will stop the mask bleeding out from the edges - vec2 text = abs( vMaskCoord - 0.5 ); - text = step(0.5, text); + float clip = step(3.5, + step(maskClamp.x, vMaskCoord.x) + + step(maskClamp.y, vMaskCoord.y) + + step(vMaskCoord.x, maskClamp.z) + + step(vMaskCoord.y, maskClamp.w)); - float clip = 1.0 - max(text.y, text.x); vec4 original = texture2D(uSampler, vTextureCoord); vec4 masky = texture2D(mask, vMaskCoord); diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 0b13c90..2c74de1 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -54,8 +54,8 @@ this.resolution = resolution || settings.RESOLUTION; - this.width = width; - this.height = height; + this.width = Math.ceil(width); + this.height = Math.ceil(height); this.realWidth = this.width * this.resolution; this.realHeight = this.height * this.resolution; @@ -95,6 +95,9 @@ */ resize(width, height) { + width = Math.ceil(width); + height = Math.ceil(height); + if (width === this.width && height === this.height) { return; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 31d264e..4fe047f 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -97,6 +97,9 @@ */ resize(width, height, doNotResizeBaseTexture) { + width = Math.ceil(width); + height = Math.ceil(height); + // TODO - could be not required.. this.valid = (width > 0 && height > 0); diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 75eb069..c63af99 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -80,6 +80,7 @@ /** * This is the trimmed area of original texture, before it was put in atlas + * Please call `_updateUvs()` after you change coordinates of `trim` manually. * * @member {PIXI.Rectangle} */ @@ -153,8 +154,10 @@ this._updateID = 0; /** - * Extra field for extra plugins. May contain clamp settings and some matrices - * @type {Object} + * Contains data for uvs. May contain clamp settings and some matrices. + * Its a bit heavy, so by default that object is not created. + * @type {PIXI.TextureMatrix} + * @default null */ this.transform = null; @@ -266,9 +269,7 @@ } /** - * Updates the internal WebGL UV cache. - * - * @protected + * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture. */ _updateUvs() { @@ -538,6 +539,7 @@ /** * The frame specifies the region of the base texture that this texture uses. + * Please call `_updateUvs()` after you change coordinates of `frame` manually. * * @member {PIXI.Rectangle} */ diff --git a/src/core/textures/TextureMatrix.js b/src/core/textures/TextureMatrix.js new file mode 100644 index 0000000..f0b9c12 --- /dev/null +++ b/src/core/textures/TextureMatrix.js @@ -0,0 +1,149 @@ +import { default as Matrix } from '../math/Matrix'; + +const tempMat = new Matrix(); + +/** + * Class controls uv transform and frame clamp for texture + * Can be used in Texture "transform" field, or separately, you can use different clamp settings on the same texture. + * If you want to add support for texture region of certain feature or filter, that's what you're looking for. + * + * @see PIXI.Texture + * @see PIXI.mesh.Mesh + * @see PIXI.extras.TilingSprite + * @class + * @memberof PIXI + */ +export default class TextureMatrix +{ + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + */ + get texture() + { + return this._texture; + } + + set texture(value) // eslint-disable-line require-jsdoc + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * Multiplies uvs array to transform + * @param {Float32Array} uvs mesh uvs + * @param {Float32Array} [out=uvs] output + * @returns {Float32Array} output + */ + multiplyUvs(uvs, out) + { + if (out === undefined) + { + out = uvs; + } + + const mat = this.mapCoord; + + for (let i = 0; i < uvs.length; i += 2) + { + const x = uvs[i]; + const y = uvs[i + 1]; + + out[i] = (x * mat.a) + (y * mat.c) + mat.tx; + out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; + } + + return out; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + * @returns {boolean} whether or not it was updated + */ + update(forceUpdate) + { + const tex = this._texture; + + if (!tex || !tex.valid) + { + return false; + } + + if (!forceUpdate + && this._lastTextureID === tex._updateID) + { + return false; + } + + this._lastTextureID = tex._updateID; + + const uvs = tex._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + + return true; + } +} diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index f819313..86f0873 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -2,6 +2,7 @@ import { uid, BaseTextureCache } from '../utils'; import { shared } from '../ticker'; import { UPDATE_PRIORITY } from '../const'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; /** * A texture of a [playing] Video. @@ -236,15 +237,27 @@ * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ - static fromUrl(videoSrc, scaleMode) + static fromUrl(videoSrc, scaleMode, crossorigin) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); + const url = Array.isArray(videoSrc) ? (videoSrc[0].src || videoSrc[0]) : (videoSrc.src || videoSrc); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + video.crossOrigin = determineCrossOrigin(url); + } + else if (crossorigin) + { + video.crossOrigin = typeof crossorigin === 'string' ? crossorigin : 'anonymous'; + } + // array of objects or strings if (Array.isArray(videoSrc)) { @@ -256,7 +269,7 @@ // single object or string else { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + video.appendChild(createSource(url, videoSrc.mime)); } video.load(); diff --git a/src/deprecation.js b/src/deprecation.js index a7e943f..dab6e70 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -586,6 +586,28 @@ }); } + if (extras) + { + Object.defineProperties(extras, { + /** + * @class + * @name TextureTransform + * @memberof PIXI.extras + * @see PIXI.TextureMatrix + * @deprecated since version 4.6.0 + */ + TextureTransform: { + get() + { + warn('The TextureTransform class has been renamed to TextureMatrix, ' + + 'please use PIXI.TextureMatrix from now on.'); + + return core.TextureMatrix; + }, + }, + }); + } + core.DisplayObject.prototype.generateTexture = function generateTexture(renderer, scaleMode, resolution) { warn('generateTexture has moved to the renderer, please use renderer.generateTexture(displayObject)'); @@ -601,6 +623,21 @@ return this.generateCanvasTexture(scaleMode, resolution); }; + /** + * @method + * @name PIXI.GroupD8.isSwapWidthHeight + * @see PIXI.GroupD8.isVertical + * @param {number} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + * @deprecated since version 4.6.0 + */ + core.GroupD8.isSwapWidthHeight = function isSwapWidthHeight(rotation) + { + warn('GroupD8.isSwapWidthHeight was renamed to GroupD8.isVertical'); + + return core.GroupD8.isVertical(rotation); + }; + core.RenderTexture.prototype.render = function render(displayObject, matrix, clear, updateTransform) { this.legacyRenderer.render(displayObject, this, clear, matrix, !updateTransform); @@ -946,6 +983,22 @@ return core.SpriteMaskFilter; }, }, + + /** + * @class + * @private + * @name PIXI.filters.VoidFilter + * @see PIXI.filters.AlphaFilter + * @deprecated since version 4.5.7 + */ + VoidFilter: { + get() + { + warn('VoidFilter has been renamed to AlphaFilter, please use PIXI.filters.AlphaFilter'); + + return filters.AlphaFilter; + }, + }, }); /** diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index 0592b54..96d9f44 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -1,5 +1,6 @@ import * as core from '../core'; import ObservablePoint from '../core/math/ObservablePoint'; +import { getResolutionOfUrl } from '../core/utils'; import settings from '../core/settings'; /** @@ -510,7 +511,8 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const res = texture.baseTexture.resolution || settings.RESOLUTION; + const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); + const res = getResolutionOfUrl(fileName, settings.RESOLUTION); data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js deleted file mode 100644 index 16536d2..0000000 --- a/src/extras/TextureTransform.js +++ /dev/null @@ -1,144 +0,0 @@ -import { default as Matrix } from '../core/math/Matrix'; - -const tempMat = new Matrix(); - -/** - * class controls uv transform and frame clamp for texture - * - * @class - * @memberof PIXI.extras - */ -export default class TextureTransform -{ - /** - * - * @param {PIXI.Texture} texture observed texture - * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. - * @constructor - */ - constructor(texture, clampMargin) - { - this._texture = texture; - - this.mapCoord = new Matrix(); - - this.uClampFrame = new Float32Array(4); - - this.uClampOffset = new Float32Array(2); - - this._lastTextureID = -1; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to 1.5 if you texture has repeated right and bottom lines, that leads to smoother borders - * - * @default 0 - * @member {number} - */ - this.clampOffset = 0; - - /** - * Changes frame clamping - * Works with TilingSprite and Mesh - * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas - * - * @default 0.5 - * @member {number} - */ - this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; - } - - /** - * texture property - * @member {PIXI.Texture} - */ - get texture() - { - return this._texture; - } - - set texture(value) // eslint-disable-line require-jsdoc - { - this._texture = value; - this._lastTextureID = -1; - } - - /** - * Multiplies uvs array to transform - * @param {Float32Array} uvs mesh uvs - * @param {Float32Array} [out=uvs] output - * @returns {Float32Array} output - */ - multiplyUvs(uvs, out) - { - if (out === undefined) - { - out = uvs; - } - - const mat = this.mapCoord; - - for (let i = 0; i < uvs.length; i += 2) - { - const x = uvs[i]; - const y = uvs[i + 1]; - - out[i] = (x * mat.a) + (y * mat.c) + mat.tx; - out[i + 1] = (x * mat.b) + (y * mat.d) + mat.ty; - } - - return out; - } - - /** - * updates matrices if texture was changed - * @param {boolean} forceUpdate if true, matrices will be updated any case - * @returns {boolean} whether or not it was updated - */ - update(forceUpdate) - { - const tex = this._texture; - - if (!tex || !tex.valid) - { - return false; - } - - if (!forceUpdate - && this._lastTextureID === tex._updateID) - { - return false; - } - - this._lastTextureID = tex._updateID; - - const uvs = tex._uvs; - - this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); - - const orig = tex.orig; - const trim = tex.trim; - - if (trim) - { - tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, - -trim.x / trim.width, -trim.y / trim.height); - this.mapCoord.append(tempMat); - } - - const texBase = tex.baseTexture; - const frame = this.uClampFrame; - const margin = this.clampMargin / texBase.resolution; - const offset = this.clampOffset; - - frame[0] = (tex._frame.x + margin + offset) / texBase.width; - frame[1] = (tex._frame.y + margin + offset) / texBase.height; - frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; - frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; - this.uClampOffset[0] = offset / texBase.realWidth; - this.uClampOffset[1] = offset / texBase.realHeight; - - return true; - } -} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 736b207..84c0841 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,6 +1,5 @@ import * as core from '../core'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import { default as TextureTransform } from './TextureTransform'; const tempPoint = new core.Point(); @@ -58,9 +57,9 @@ /** * transform that is applied to UV to get the texture coords * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} */ - this.uvTransform = texture.transform || new TextureTransform(texture); + this.uvTransform = texture.transform || new core.TextureMatrix(texture); /** * Plugin that is responsible for rendering this element. diff --git a/src/extras/index.js b/src/extras/index.js index 5b98cb1..a34d7ec 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -3,7 +3,6 @@ * @namespace PIXI.extras */ export { default as AnimatedSprite } from './AnimatedSprite'; -export { default as TextureTransform } from './TextureTransform'; export { default as TilingSprite } from './TilingSprite'; export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; diff --git a/src/filters/alpha/AlphaFilter.js b/src/filters/alpha/AlphaFilter.js new file mode 100644 index 0000000..c2824ed --- /dev/null +++ b/src/filters/alpha/AlphaFilter.js @@ -0,0 +1,55 @@ +import * as core from '../../core'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Simplest filter - applies alpha + * + * Use this instead of Container's alpha property to avoid visual layering of individual elements. + * AlphaFilter applies alpha evenly across the entire display object and any opaque elements it contains. + * If elements are not opaque, they will blend with each other anyway. + * + * Very handy if you want to use common features of all filters: + * + * 1. Assign a blendMode to this filter, blend all elements inside display object with background. + * + * 2. To use clipping in display coordinates, assign a filterArea to the same container that has this filter. + * + * @class + * @extends PIXI.Filter + * @memberof PIXI.filters + */ +export default class AlphaFilter extends core.Filter +{ + /** + * + */ + constructor() + { + super( + // vertex shader + readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), + // fragment shader + readFileSync(join(__dirname, './alpha.frag'), 'utf8') + ); + + this.alpha = 1.0; + this.glShaderKey = 'alpha'; + } + + /** + * Coefficient for alpha multiplication + * + * @member {number} + * @default 1 + */ + get alpha() + { + return this.uniforms.uAlpha; + } + + set alpha(value) // eslint-disable-line require-jsdoc + { + this.uniforms.uAlpha = value; + } +} diff --git a/src/filters/alpha/alpha.frag b/src/filters/alpha/alpha.frag new file mode 100644 index 0000000..6db588c --- /dev/null +++ b/src/filters/alpha/alpha.frag @@ -0,0 +1,9 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform float uAlpha; + +void main(void) +{ + gl_FragColor = texture2D(uSampler, vTextureCoord) * uAlpha; +} diff --git a/src/filters/index.js b/src/filters/index.js index 7ceb50a..dca47ab 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -25,4 +25,4 @@ export { default as BlurXFilter } from './blur/BlurXFilter'; export { default as BlurYFilter } from './blur/BlurYFilter'; export { default as ColorMatrixFilter } from './colormatrix/ColorMatrixFilter'; -export { default as VoidFilter } from './void/VoidFilter'; +export { default as AlphaFilter } from './alpha/AlphaFilter'; diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js deleted file mode 100644 index b4361ac..0000000 --- a/src/filters/void/VoidFilter.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as core from '../../core'; -import { readFileSync } from 'fs'; -import { join } from 'path'; - -/** - * Does nothing. Very handy. - * - * @class - * @extends PIXI.Filter - * @memberof PIXI.filters - */ -export default class VoidFilter extends core.Filter -{ - /** - * - */ - constructor() - { - super( - // vertex shader - readFileSync(join(__dirname, '../fragments/default.vert'), 'utf8'), - // fragment shader - readFileSync(join(__dirname, './void.frag'), 'utf8') - ); - - this.glShaderKey = 'void'; - } -} diff --git a/src/filters/void/void.frag b/src/filters/void/void.frag deleted file mode 100644 index 99168fb..0000000 --- a/src/filters/void/void.frag +++ /dev/null @@ -1,8 +0,0 @@ -varying vec2 vTextureCoord; - -uniform sampler2D uSampler; - -void main(void) -{ - gl_FragColor = texture2D(uSampler, vTextureCoord); -} diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index d866f03..7006a3e 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import * as core from '../core'; -import { default as TextureTransform } from '../extras/TextureTransform'; +import Texture from '../core/textures/Texture'; const tempPoint = new core.Point(); const tempPolygon = new core.Polygon(); @@ -27,9 +27,10 @@ * The texture of the Mesh * * @member {PIXI.Texture} + * @default PIXI.Texture.EMPTY * @private */ - this._texture = texture; + this._texture = texture || Texture.EMPTY; /** * The Uvs of the Mesh @@ -129,10 +130,10 @@ * its updated independently from texture uvTransform * updates of uvs are tied to that thing * - * @member {PIXI.extras.TextureTransform} + * @member {PIXI.TextureMatrix} * @private */ - this._uvTransform = new TextureTransform(texture); + this._uvTransform = new core.TextureMatrix(this._texture); /** * whether or not upload uvTransform to shader diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 61ba5ee..f0d7575 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -172,8 +172,8 @@ const base = this._texture.baseTexture; const textureSource = base.source; - const w = base.width; - const h = base.height; + const w = base.width * base.resolution; + const h = base.height * base.resolution; this.drawSegment(context, textureSource, w, h, 0, 1, 10, 11); this.drawSegment(context, textureSource, w, h, 2, 3, 12, 13); diff --git a/src/mesh/Plane.js b/src/mesh/Plane.js index 4dd2398..2aa60f9 100644 --- a/src/mesh/Plane.js +++ b/src/mesh/Plane.js @@ -94,6 +94,8 @@ this.uvs = new Float32Array(uvs); this.colors = new Float32Array(colors); this.indices = new Uint16Array(indices); + + this.dirty++; this.indexDirty++; this.multiplyUvs(); diff --git a/test/interaction/InteractionManager.js b/test/interaction/InteractionManager.js index 3f84d6a..136640e 100644 --- a/test/interaction/InteractionManager.js +++ b/test/interaction/InteractionManager.js @@ -279,13 +279,48 @@ removeSpy.restore(); }); - it('should add and remove pointer events to element', function () + it('should add and remove pointer events to element seven times when touch events are supported', function () { const manager = new PIXI.interaction.InteractionManager(sinon.stub()); const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; manager.interactionDOMElement = element; manager.supportsPointerEvents = true; + manager.supportsTouchEvents = true; + + manager.addEvents(); + + expect(element.addEventListener).to.have.been.callCount(7); + expect(element.addEventListener).to.have.been.calledWith('pointerdown'); + expect(element.addEventListener).to.have.been.calledWith('pointerleave'); + expect(element.addEventListener).to.have.been.calledWith('pointerover'); + + expect(element.addEventListener).to.have.been.calledWith('touchstart'); + expect(element.addEventListener).to.have.been.calledWith('touchcancel'); + expect(element.addEventListener).to.have.been.calledWith('touchend'); + expect(element.addEventListener).to.have.been.calledWith('touchmove'); + + manager.removeEvents(); + + expect(element.removeEventListener).to.have.been.callCount(7); + expect(element.removeEventListener).to.have.been.calledWith('pointerdown'); + expect(element.removeEventListener).to.have.been.calledWith('pointerleave'); + expect(element.removeEventListener).to.have.been.calledWith('pointerover'); + + expect(element.removeEventListener).to.have.been.calledWith('touchstart'); + expect(element.removeEventListener).to.have.been.calledWith('touchcancel'); + expect(element.removeEventListener).to.have.been.calledWith('touchend'); + expect(element.removeEventListener).to.have.been.calledWith('touchmove'); + }); + + it('should add and remove pointer events to element three times when touch events are not supported', function () + { + const manager = new PIXI.interaction.InteractionManager(sinon.stub()); + const element = { style: {}, addEventListener: sinon.stub(), removeEventListener: sinon.stub() }; + + manager.interactionDOMElement = element; + manager.supportsPointerEvents = true; + manager.supportsTouchEvents = false; manager.addEvents(); diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 7c77343..70703fc 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -1,7 +1,81 @@ 'use strict'; +const path = require('path'); +const fs = require('fs'); + describe('PIXI.loaders.bitmapFontParser', function () { + afterEach(function () + { + for (var font in PIXI.extras.BitmapText.fonts) + { + delete PIXI.extras.BitmapText.fonts[font]; + } + for (var baseTexture in PIXI.utils.BaseTextureCache) + { + delete PIXI.utils.BaseTextureCache[baseTexture]; + } + for (var texture in PIXI.utils.TextureCache) + { + delete PIXI.utils.TextureCache[texture]; + } + }); + + before(function (done) + { + const resolveURL = (url) => path.resolve(this.resources, url); + + this.resources = path.join(__dirname, 'resources'); + this.fontXML = null; + this.fontScaledXML = null; + this.fontImage = null; + this.fontScaledImage = null; + this.atlasImage = null; + this.atlasScaledImage = null; + this.atlasJSON = require(resolveURL('atlas.json')); // eslint-disable-line global-require + this.atlasScaledJSON = require(resolveURL('atlas@0.5x.json')); // eslint-disable-line global-require + + const loadXML = (url) => new Promise((resolve) => + fs.readFile(resolveURL(url), 'utf8', (err, data) => + { + expect(err).to.be.null; + resolve((new window.DOMParser()).parseFromString(data, 'text/xml')); + })); + + const loadImage = (url) => new Promise((resolve) => + { + const image = new Image(); + + image.onload = () => resolve(image); + image.src = resolveURL(url); + }); + + Promise.all([ + loadXML('font.fnt'), + loadXML('font@0.5x.fnt'), + loadImage('font.png'), + loadImage('font@0.5x.png'), + loadImage('atlas.png'), + loadImage('atlas@0.5x.png'), + ]).then(([ + fontXML, + fontScaledXML, + fontImage, + fontScaledImage, + atlasImage, + atlasScaledImage, + ]) => + { + this.fontXML = fontXML; + this.fontScaledXML = fontScaledXML; + this.fontImage = fontImage; + this.fontScaledImage = fontScaledImage; + this.atlasImage = atlasImage; + this.atlasScaledImage = atlasScaledImage; + done(); + }); + }); + it('should exist and return a function', function () { expect(PIXI.loaders.bitmapFontParser).to.be.a('function'); @@ -33,6 +107,206 @@ // TODO: Test the texture cache code path. // TODO: Test the loading texture code path. // TODO: Test data-url code paths. + + it('should properly register bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontImage); + expect(charA.texture.frame.x).to.equal(2); + expect(charA.texture.frame.y).to.equal(2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontImage); + expect(charB.texture.frame.x).to.equal(2); + expect(charB.texture.frame.y).to.equal(24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontImage); + expect(charC.texture.frame.x).to.equal(23); + expect(charC.texture.frame.y).to.equal(2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontImage); + expect(charD.texture.frame.x).to.equal(19); + expect(charD.texture.frame.y).to.equal(24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register SCALED bitmap font', function (done) + { + const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontScaledImage, null, 0.5)); + const font = PIXI.extras.BitmapText.registerFont(this.fontScaledXML, texture); + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charA.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 + expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charB.texture.frame.x).to.equal(4); // 2 / 0.5 + expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 + expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charC.texture.frame.x).to.equal(46); // 23 / 0.5 + expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 + expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 + expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); + expect(charD.texture.frame.x).to.equal(38); // 19 / 0.5 + expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 + expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 + expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + + it('should properly register bitmap font NESTED into spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); + + it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) + { + const baseTexture = new PIXI.BaseTexture(this.atlasScaledImage, null, 1); + const spritesheet = new PIXI.Spritesheet(baseTexture, this.atlasScaledJSON); + + spritesheet.parse(() => + { + const fontTexture = PIXI.Texture.fromFrame('resources/font.png'); + const font = PIXI.extras.BitmapText.registerFont(this.fontXML, fontTexture); + const fontX = 158; // bare value from spritesheet frame + const fontY = 2; // bare value from spritesheet frame + + expect(font).to.be.an.object; + expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); + expect(font).to.have.property('chars'); + const charA = font.chars['A'.charCodeAt(0) || 65]; + + expect(charA).to.exist; + expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charA.texture.frame.x).to.equal(fontX + 2); + expect(charA.texture.frame.y).to.equal(fontY + 2); + expect(charA.texture.frame.width).to.equal(19); + expect(charA.texture.frame.height).to.equal(20); + const charB = font.chars['B'.charCodeAt(0) || 66]; + + expect(charB).to.exist; + expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charB.texture.frame.x).to.equal(fontX + 2); + expect(charB.texture.frame.y).to.equal(fontY + 24); + expect(charB.texture.frame.width).to.equal(15); + expect(charB.texture.frame.height).to.equal(20); + const charC = font.chars['C'.charCodeAt(0) || 67]; + + expect(charC).to.exist; + expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charC.texture.frame.x).to.equal(fontX + 23); + expect(charC.texture.frame.y).to.equal(fontY + 2); + expect(charC.texture.frame.width).to.equal(18); + expect(charC.texture.frame.height).to.equal(20); + const charD = font.chars['D'.charCodeAt(0) || 68]; + + expect(charD).to.exist; + expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); + expect(charD.texture.frame.x).to.equal(fontX + 19); + expect(charD.texture.frame.y).to.equal(fontY + 24); + expect(charD.texture.frame.width).to.equal(17); + expect(charD.texture.frame.height).to.equal(20); + const charE = font.chars['E'.charCodeAt(0) || 69]; + + expect(charE).to.be.undefined; + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/atlas.json b/test/loaders/resources/atlas.json new file mode 100644 index 0000000..86e65a5 --- /dev/null +++ b/test/loaders/resources/atlas.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas.png b/test/loaders/resources/atlas.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas.png Binary files differ diff --git a/test/loaders/resources/atlas@0.5x.json b/test/loaders/resources/atlas@0.5x.json new file mode 100644 index 0000000..ae990a1 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.json @@ -0,0 +1,25 @@ +{ + "meta": { + "image": "atlas@0.5x.png", + "size": {"w":256,"h":256}, + "scale": "1" + }, + "frames": { + "resources/test.png": + { + "frame": {"x":2,"y":2,"w":152,"h":188}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":74,"y":36,"w":152,"h":188}, + "sourceSize": {"w":300,"h":225} + }, + "resources/font.png": + { + "frame": {"x":158,"y":2,"w":40,"h":42}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":2,"y":2,"w":40,"h":42}, + "sourceSize": {"w":43,"h":46} + } + } +} \ No newline at end of file diff --git a/test/loaders/resources/atlas@0.5x.png b/test/loaders/resources/atlas@0.5x.png new file mode 100644 index 0000000..d5e7892 --- /dev/null +++ b/test/loaders/resources/atlas@0.5x.png Binary files differ diff --git a/test/loaders/resources/font.fnt b/test/loaders/resources/font.fnt new file mode 100644 index 0000000..56e1060 --- /dev/null +++ b/test/loaders/resources/font.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/loaders/resources/font.png b/test/loaders/resources/font.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/test/loaders/resources/font.png Binary files differ diff --git a/test/loaders/resources/font@0.5x.fnt b/test/loaders/resources/font@0.5x.fnt new file mode 100644 index 0000000..6c247c7 --- /dev/null +++ b/test/loaders/resources/font@0.5x.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/loaders/resources/font@0.5x.png b/test/loaders/resources/font@0.5x.png new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/test/loaders/resources/font@0.5x.png Binary files differ