diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 8509505..679f8ec 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -75,28 +75,28 @@ out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]) + a[4]; // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]) + a[9]; // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]) + a[14]; // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]) + a[19]; return out; } diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 8509505..679f8ec 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -75,28 +75,28 @@ out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]) + a[4]; // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]) + a[9]; // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]) + a[14]; // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]) + a[19]; return out; } diff --git a/src/filters/colormatrix/colorMatrix.frag b/src/filters/colormatrix/colorMatrix.frag index 3aaf2b3..c73c0e9 100644 --- a/src/filters/colormatrix/colorMatrix.frag +++ b/src/filters/colormatrix/colorMatrix.frag @@ -4,32 +4,38 @@ void main(void) { - vec4 c = texture2D(uSampler, vTextureCoord); + // Un-premultiply alpha before applying the color matrix. See issue #3539. + if (c.a > 0.0) { + c.rgb /= c.a; + } + vec4 result; + result.r = (m[0] * c.r); + result.r += (m[1] * c.g); + result.r += (m[2] * c.b); + result.r += (m[3] * c.a); + result.r += m[4]; - gl_FragColor.r = (m[0] * c.r); - gl_FragColor.r += (m[1] * c.g); - gl_FragColor.r += (m[2] * c.b); - gl_FragColor.r += (m[3] * c.a); - gl_FragColor.r += m[4] * c.a; + result.g = (m[5] * c.r); + result.g += (m[6] * c.g); + result.g += (m[7] * c.b); + result.g += (m[8] * c.a); + result.g += m[9]; - gl_FragColor.g = (m[5] * c.r); - gl_FragColor.g += (m[6] * c.g); - gl_FragColor.g += (m[7] * c.b); - gl_FragColor.g += (m[8] * c.a); - gl_FragColor.g += m[9] * c.a; + result.b = (m[10] * c.r); + result.b += (m[11] * c.g); + result.b += (m[12] * c.b); + result.b += (m[13] * c.a); + result.b += m[14]; - gl_FragColor.b = (m[10] * c.r); - gl_FragColor.b += (m[11] * c.g); - gl_FragColor.b += (m[12] * c.b); - gl_FragColor.b += (m[13] * c.a); - gl_FragColor.b += m[14] * c.a; + result.a = (m[15] * c.r); + result.a += (m[16] * c.g); + result.a += (m[17] * c.b); + result.a += (m[18] * c.a); + result.a += m[19]; - gl_FragColor.a = (m[15] * c.r); - gl_FragColor.a += (m[16] * c.g); - gl_FragColor.a += (m[17] * c.b); - gl_FragColor.a += (m[18] * c.a); - gl_FragColor.a += m[19] * c.a; + // Premultiply alpha again. + result.rgb *= result.a; -// gl_FragColor = vec4(m[0]); + gl_FragColor = result; } diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 8509505..679f8ec 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -75,28 +75,28 @@ out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]) + a[4]; // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]) + a[9]; // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]) + a[14]; // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]) + a[19]; return out; } diff --git a/src/filters/colormatrix/colorMatrix.frag b/src/filters/colormatrix/colorMatrix.frag index 3aaf2b3..c73c0e9 100644 --- a/src/filters/colormatrix/colorMatrix.frag +++ b/src/filters/colormatrix/colorMatrix.frag @@ -4,32 +4,38 @@ void main(void) { - vec4 c = texture2D(uSampler, vTextureCoord); + // Un-premultiply alpha before applying the color matrix. See issue #3539. + if (c.a > 0.0) { + c.rgb /= c.a; + } + vec4 result; + result.r = (m[0] * c.r); + result.r += (m[1] * c.g); + result.r += (m[2] * c.b); + result.r += (m[3] * c.a); + result.r += m[4]; - gl_FragColor.r = (m[0] * c.r); - gl_FragColor.r += (m[1] * c.g); - gl_FragColor.r += (m[2] * c.b); - gl_FragColor.r += (m[3] * c.a); - gl_FragColor.r += m[4] * c.a; + result.g = (m[5] * c.r); + result.g += (m[6] * c.g); + result.g += (m[7] * c.b); + result.g += (m[8] * c.a); + result.g += m[9]; - gl_FragColor.g = (m[5] * c.r); - gl_FragColor.g += (m[6] * c.g); - gl_FragColor.g += (m[7] * c.b); - gl_FragColor.g += (m[8] * c.a); - gl_FragColor.g += m[9] * c.a; + result.b = (m[10] * c.r); + result.b += (m[11] * c.g); + result.b += (m[12] * c.b); + result.b += (m[13] * c.a); + result.b += m[14]; - gl_FragColor.b = (m[10] * c.r); - gl_FragColor.b += (m[11] * c.g); - gl_FragColor.b += (m[12] * c.b); - gl_FragColor.b += (m[13] * c.a); - gl_FragColor.b += m[14] * c.a; + result.a = (m[15] * c.r); + result.a += (m[16] * c.g); + result.a += (m[17] * c.b); + result.a += (m[18] * c.a); + result.a += m[19]; - gl_FragColor.a = (m[15] * c.r); - gl_FragColor.a += (m[16] * c.g); - gl_FragColor.a += (m[17] * c.b); - gl_FragColor.a += (m[18] * c.a); - gl_FragColor.a += m[19] * c.a; + // Premultiply alpha again. + result.rgb *= result.a; -// gl_FragColor = vec4(m[0]); + gl_FragColor = result; } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index da5d553..34a1423 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -10,7 +10,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class MeshRenderer extends core.ObjectRenderer { +export default class MeshRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 8509505..679f8ec 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -75,28 +75,28 @@ out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]) + a[4]; // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]) + a[9]; // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]) + a[14]; // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]) + a[19]; return out; } diff --git a/src/filters/colormatrix/colorMatrix.frag b/src/filters/colormatrix/colorMatrix.frag index 3aaf2b3..c73c0e9 100644 --- a/src/filters/colormatrix/colorMatrix.frag +++ b/src/filters/colormatrix/colorMatrix.frag @@ -4,32 +4,38 @@ void main(void) { - vec4 c = texture2D(uSampler, vTextureCoord); + // Un-premultiply alpha before applying the color matrix. See issue #3539. + if (c.a > 0.0) { + c.rgb /= c.a; + } + vec4 result; + result.r = (m[0] * c.r); + result.r += (m[1] * c.g); + result.r += (m[2] * c.b); + result.r += (m[3] * c.a); + result.r += m[4]; - gl_FragColor.r = (m[0] * c.r); - gl_FragColor.r += (m[1] * c.g); - gl_FragColor.r += (m[2] * c.b); - gl_FragColor.r += (m[3] * c.a); - gl_FragColor.r += m[4] * c.a; + result.g = (m[5] * c.r); + result.g += (m[6] * c.g); + result.g += (m[7] * c.b); + result.g += (m[8] * c.a); + result.g += m[9]; - gl_FragColor.g = (m[5] * c.r); - gl_FragColor.g += (m[6] * c.g); - gl_FragColor.g += (m[7] * c.b); - gl_FragColor.g += (m[8] * c.a); - gl_FragColor.g += m[9] * c.a; + result.b = (m[10] * c.r); + result.b += (m[11] * c.g); + result.b += (m[12] * c.b); + result.b += (m[13] * c.a); + result.b += m[14]; - gl_FragColor.b = (m[10] * c.r); - gl_FragColor.b += (m[11] * c.g); - gl_FragColor.b += (m[12] * c.b); - gl_FragColor.b += (m[13] * c.a); - gl_FragColor.b += m[14] * c.a; + result.a = (m[15] * c.r); + result.a += (m[16] * c.g); + result.a += (m[17] * c.b); + result.a += (m[18] * c.a); + result.a += m[19]; - gl_FragColor.a = (m[15] * c.r); - gl_FragColor.a += (m[16] * c.g); - gl_FragColor.a += (m[17] * c.b); - gl_FragColor.a += (m[18] * c.a); - gl_FragColor.a += m[19] * c.a; + // Premultiply alpha again. + result.rgb *= result.a; -// gl_FragColor = vec4(m[0]); + gl_FragColor = result; } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index da5d553..34a1423 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -10,7 +10,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class MeshRenderer extends core.ObjectRenderer { +export default class MeshRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/prepare/limiters/CountLimiter.js b/src/prepare/limiters/CountLimiter.js index 7fd0b70..265c46c 100644 --- a/src/prepare/limiters/CountLimiter.js +++ b/src/prepare/limiters/CountLimiter.js @@ -5,7 +5,8 @@ * @class * @memberof PIXI */ -export default class CountLimiter { +export default class CountLimiter +{ /** * @param {number} maxItemsPerFrame - The maximum number of items that can be prepared each frame. */ diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 8509505..679f8ec 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -75,28 +75,28 @@ out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]) + a[4]; // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]) + a[9]; // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]) + a[14]; // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]) + a[19]; return out; } diff --git a/src/filters/colormatrix/colorMatrix.frag b/src/filters/colormatrix/colorMatrix.frag index 3aaf2b3..c73c0e9 100644 --- a/src/filters/colormatrix/colorMatrix.frag +++ b/src/filters/colormatrix/colorMatrix.frag @@ -4,32 +4,38 @@ void main(void) { - vec4 c = texture2D(uSampler, vTextureCoord); + // Un-premultiply alpha before applying the color matrix. See issue #3539. + if (c.a > 0.0) { + c.rgb /= c.a; + } + vec4 result; + result.r = (m[0] * c.r); + result.r += (m[1] * c.g); + result.r += (m[2] * c.b); + result.r += (m[3] * c.a); + result.r += m[4]; - gl_FragColor.r = (m[0] * c.r); - gl_FragColor.r += (m[1] * c.g); - gl_FragColor.r += (m[2] * c.b); - gl_FragColor.r += (m[3] * c.a); - gl_FragColor.r += m[4] * c.a; + result.g = (m[5] * c.r); + result.g += (m[6] * c.g); + result.g += (m[7] * c.b); + result.g += (m[8] * c.a); + result.g += m[9]; - gl_FragColor.g = (m[5] * c.r); - gl_FragColor.g += (m[6] * c.g); - gl_FragColor.g += (m[7] * c.b); - gl_FragColor.g += (m[8] * c.a); - gl_FragColor.g += m[9] * c.a; + result.b = (m[10] * c.r); + result.b += (m[11] * c.g); + result.b += (m[12] * c.b); + result.b += (m[13] * c.a); + result.b += m[14]; - gl_FragColor.b = (m[10] * c.r); - gl_FragColor.b += (m[11] * c.g); - gl_FragColor.b += (m[12] * c.b); - gl_FragColor.b += (m[13] * c.a); - gl_FragColor.b += m[14] * c.a; + result.a = (m[15] * c.r); + result.a += (m[16] * c.g); + result.a += (m[17] * c.b); + result.a += (m[18] * c.a); + result.a += m[19]; - gl_FragColor.a = (m[15] * c.r); - gl_FragColor.a += (m[16] * c.g); - gl_FragColor.a += (m[17] * c.b); - gl_FragColor.a += (m[18] * c.a); - gl_FragColor.a += m[19] * c.a; + // Premultiply alpha again. + result.rgb *= result.a; -// gl_FragColor = vec4(m[0]); + gl_FragColor = result; } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index da5d553..34a1423 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -10,7 +10,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class MeshRenderer extends core.ObjectRenderer { +export default class MeshRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/prepare/limiters/CountLimiter.js b/src/prepare/limiters/CountLimiter.js index 7fd0b70..265c46c 100644 --- a/src/prepare/limiters/CountLimiter.js +++ b/src/prepare/limiters/CountLimiter.js @@ -5,7 +5,8 @@ * @class * @memberof PIXI */ -export default class CountLimiter { +export default class CountLimiter +{ /** * @param {number} maxItemsPerFrame - The maximum number of items that can be prepared each frame. */ diff --git a/src/prepare/limiters/TimeLimiter.js b/src/prepare/limiters/TimeLimiter.js index 8908aba..5f40686 100644 --- a/src/prepare/limiters/TimeLimiter.js +++ b/src/prepare/limiters/TimeLimiter.js @@ -5,7 +5,8 @@ * @class * @memberof PIXI */ -export default class TimeLimiter { +export default class TimeLimiter +{ /** * @param {number} maxMilliseconds - The maximum milliseconds that can be spent preparing items each frame. */ diff --git a/scripts/release.js b/scripts/release.js index c4c8e36..156620c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,7 +12,8 @@ 'dist/**/*', 'lib/**/*', 'src/**/*', - 'scripts/**/*', + 'scripts/*', + 'scripts/renders/*', 'test/**/*', '*.json', '*.md', diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 3de0aef..b409891 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -1,5 +1,4 @@ import settings from '../../../settings'; -const { RESOLUTION } = settings; /** * Creates a Canvas element of the given size. @@ -30,7 +29,7 @@ */ this.context = this.canvas.getContext('2d'); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.resize(width, height); } diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index 93a5d2a..c051375 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -1,5 +1,6 @@ import Shader from '../../../shader/Shader'; import { BLEND_MODES } from '../../../const'; +import settings from '../../../settings'; // let math = require('../../../math'); /** @@ -35,7 +36,7 @@ * * @member {number} */ - this.resolution = 1; + this.resolution = settings.RESOLUTION; /** * If enabled is true the filter is applied, if false it will not. diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 7d4b849..9761b99 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -185,7 +185,7 @@ flop = t; } - filters[i].apply(this, flip, lastState.renderTarget, true); + filters[i].apply(this, flip, lastState.renderTarget, false); this.freePotRenderTarget(flip); this.freePotRenderTarget(flop); diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 013625a..5e7bc0b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { texture: true, @@ -57,7 +58,7 @@ /** * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} + * @member {CanvasRenderingContext2D} */ this.context = this.canvas.getContext('2d'); @@ -189,10 +190,11 @@ if (style.dropShadow) { + this.context.shadowBlur = style.dropShadowBlur; + if (style.dropShadowBlur > 0) { this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; } else { @@ -240,6 +242,9 @@ // set canvas text styles this.context.fillStyle = this._generateFillStyle(style, lines); + // remove blur if set for the drop shadow + this.context.shadowBlur = 0; + // draw lines line by line for (let i = 0; i < lines.length; i++) { @@ -326,6 +331,15 @@ */ updateTexture() { + if (this._style.trim) + { + const trimmed = trimCanvas(this.canvas); + + this.canvas.width = trimmed.width; + this.canvas.height = trimmed.height; + this.context.putImageData(trimmed.data, 0, 0); + } + const texture = this._texture; const style = this._style; @@ -525,6 +539,29 @@ const width = this.canvas.width / this.resolution; const height = this.canvas.height / this.resolution; + // make a copy of the style settings, so we can manipulate them later + const fill = style.fill.slice(); + const fillGradientStops = style.fillGradientStops.slice(); + + // wanting to evenly distribute the fills. So an array of 4 colours should give fills of 0.25, 0.5 and 0.75 + if (!fillGradientStops.length) + { + const lengthPlus1 = fill.length + 1; + + for (let i = 1; i < lengthPlus1; ++i) + { + fillGradientStops.push(i / lengthPlus1); + } + } + + // stop the bleeding of the last gradient on the line above to the top gradient of the this line + // by hard defining the first gradient colour at point 0, and last gradient colour at point 1 + fill.unshift(style.fill[0]); + fillGradientStops.unshift(0); + + fill.push(style.fill[style.fill.length - 1]); + fillGradientStops.push(1); + if (style.fillGradientType === TEXT_GRADIENT.LINEAR_VERTICAL) { // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas @@ -532,15 +569,22 @@ // we need to repeat the gradient so that each individual line of text has the same vertical gradient effect // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = (style.fill.length + 1) * lines.length; + totalIterations = (fill.length + 1) * lines.length; currentIteration = 0; for (let i = 0; i < lines.length; i++) { currentIteration += 1; - for (let j = 0; j < style.fill.length; j++) + for (let j = 0; j < fill.length; j++) { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); + if (fillGradientStops[j]) + { + stop = (fillGradientStops[j] / lines.length) + (i / lines.length); + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[j]); currentIteration++; } } @@ -552,13 +596,20 @@ // can just evenly space out the gradients in this case, as multiple lines makes no difference // to an even left to right gradient - totalIterations = style.fill.length + 1; + totalIterations = fill.length + 1; currentIteration = 1; - for (let i = 0; i < style.fill.length; i++) + for (let i = 0; i < fill.length; i++) { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); + if (fillGradientStops[i]) + { + stop = fillGradientStops[i]; + } + else + { + stop = currentIteration / totalIterations; + } + gradient.addColorStop(stop, fill[i]); currentIteration++; } } @@ -569,7 +620,7 @@ /** * Destroys this text object. * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. + * the majority of the time the texture will not be shared with any other Sprites. * * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options * have been set to that value diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 3b25528..eec2820 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -14,6 +14,7 @@ dropShadowDistance: 5, fill: 'black', fillGradientType: TEXT_GRADIENT.LINEAR_VERTICAL, + fillGradientStops: [], fontFamily: 'Arial', fontSize: 26, fontStyle: 'normal', @@ -27,6 +28,7 @@ stroke: 'black', strokeThickness: 0, textBaseline: 'alphabetic', + trim: false, wordWrap: false, wordWrapWidth: 100, }; @@ -55,8 +57,10 @@ * fillstyle that will be used on the text e.g 'red', '#00FF00'. Can be an array to create a gradient * eg ['#000000','#FFFFFF'] * {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle|MDN} - * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fills styles are - * supplied, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} for possible values + * @param {number} [style.fillGradientType=PIXI.TEXT_GRADIENT.LINEAR_VERTICAL] - If fill is an array of colours + * to create a gradient, this can change the type/direction of the gradient. See {@link PIXI.TEXT_GRADIENT} + * @param {number[]} [style.fillGradientStops] - If fill is an array of colours to create a gradient, this array can set + * the stop points (numbers between 0 and 1) for the color, overriding the default behaviour of evenly spacing them. * @param {string|string[]} [style.fontFamily='Arial'] - The font family * @param {number|string} [style.fontSize=26] - The font size (as a number it converts to px, but as a string, * equivalents are '26px','20pt','160%' or '1.6em') @@ -76,6 +80,7 @@ * e.g 'blue', '#FCFF00' * @param {number} [style.strokeThickness=0] - A number that represents the thickness of the stroke. * Default is 0 (no stroke) + * @param {boolean} [style.trim=false] - Trim transparent borders * @param {string} [style.textBaseline='alphabetic'] - The baseline of the text that is rendered. * @param {boolean} [style.wordWrap=false] - Indicates if word wrap should be used * @param {number} [style.wordWrapWidth=100] - The width at which text will wrap, it needs wordWrap to be set to true @@ -232,6 +237,19 @@ } } + get fillGradientStops() + { + return this._fillGradientStops; + } + set fillGradientStops(fillGradientStops) + { + if (!areArraysEqual(this._fillGradientStops,fillGradientStops)) + { + this._fillGradientStops = fillGradientStops; + this.styleID++; + } + } + get fontFamily() { return this._fontFamily; @@ -402,6 +420,19 @@ } } + get trim() + { + return this._trim; + } + set trim(trim) + { + if (this._trim !== trim) + { + this._trim = trim; + this.styleID++; + } + } + get wordWrap() { return this._wordWrap; @@ -475,3 +506,34 @@ return color; } } + +/** + * Utility function to convert hexadecimal colors to strings, and simply return the color if it's a string. + * This version can also convert array of colors + * + * @param {Array} array1 First array to compared + * @param {Array} array1 Second array to compare + * @return {boolean} Do the arrays contain the same values in the same order + */ +function areArraysEqual(array1, array2) +{ + if (!Array.isArray(array1) || !Array.isArray(array2)) + { + return false; + } + + if (array1.length !== array2.length) + { + return false; + } + + for (let i = 0; i < array1.length; ++i) + { + if (array1[i] !== array2[i]) + { + return false; + } + } + + return true; +} diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 5d50146..2fa08c4 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -1,7 +1,7 @@ import BaseTexture from './BaseTexture'; import settings from '../settings'; -const { RESOLUTION, SCALE_MODE } = settings; +const { SCALE_MODE } = settings; /** * A BaseRenderTexture is a special texture that allows any Pixi display object to be rendered to it. @@ -54,7 +54,7 @@ { super(null, scaleMode); - this.resolution = resolution || RESOLUTION; + this.resolution = resolution || settings.RESOLUTION; this.width = width; this.height = height; diff --git a/src/core/utils/trimCanvas.js b/src/core/utils/trimCanvas.js new file mode 100644 index 0000000..37a0378 --- /dev/null +++ b/src/core/utils/trimCanvas.js @@ -0,0 +1,76 @@ +/** + * Trim transparent borders from a canvas + * + * @memberof PIXI + * @function trimCanvas + * @private + * @param {HTMLCanvasElement} canvas - the canvas to trim + * @returns {object} Trim data + */ +export default function trimCanvas(canvas) +{ + // https://gist.github.com/remy/784508 + const context = canvas.getContext('2d'); + const pixels = context.getImageData(0, 0, canvas.width, canvas.height); + const l = pixels.data.length; + const bound = { + top: null, + left: null, + right: null, + bottom: null, + }; + let i; + let x; + let y; + + for (i = 0; i < l; i += 4) + { + if (pixels.data[i + 3] !== 0) + { + x = (i / 4) % canvas.width; + y = ~~((i / 4) / canvas.width); + + if (bound.top === null) + { + bound.top = y; + } + + if (bound.left === null) + { + bound.left = x; + } + else if (x < bound.left) + { + bound.left = x; + } + + if (bound.right === null) + { + bound.right = x + 1; + } + else if (bound.right < x) + { + bound.right = x + 1; + } + + if (bound.bottom === null) + { + bound.bottom = y; + } + else if (bound.bottom < y) + { + bound.bottom = y; + } + } + } + + const height = bound.bottom - bound.top + 1; + const width = bound.right - bound.left; + const data = context.getImageData(bound.left, bound.top, width, height); + + return { + height, + width, + data, + }; +} diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js index 58623b1..78e6e40 100644 --- a/src/extras/TextureTransform.js +++ b/src/extras/TextureTransform.js @@ -8,7 +8,8 @@ * @class * @memberof PIXI.extras */ -export default class TextureTransform { +export default class TextureTransform +{ /** * * @param {PIXI.Texture} texture observed texture diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 518d8e5..bf3aeb5 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -14,7 +14,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class TilingSpriteRenderer extends core.ObjectRenderer { +export default class TilingSpriteRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 340fc58..1575db9 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -24,10 +24,9 @@ this.blurXFilter = new BlurXFilter(strength, quality, resolution, kernelSize); this.blurYFilter = new BlurYFilter(strength, quality, resolution, kernelSize); - this.resolution = 1; this.padding = 0; - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this.quality = quality || 4; this.blur = strength || 8; } diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index def99c3..0f48889 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index 638e0f0..6d489c7 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -31,7 +31,7 @@ fragSrc ); - this.resolution = resolution || 1; + this.resolution = resolution || core.settings.RESOLUTION; this._quality = 0; diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 8509505..679f8ec 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -75,28 +75,28 @@ out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]) + a[4]; // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]) + a[9]; // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]) + a[14]; // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]) + a[19]; return out; } diff --git a/src/filters/colormatrix/colorMatrix.frag b/src/filters/colormatrix/colorMatrix.frag index 3aaf2b3..c73c0e9 100644 --- a/src/filters/colormatrix/colorMatrix.frag +++ b/src/filters/colormatrix/colorMatrix.frag @@ -4,32 +4,38 @@ void main(void) { - vec4 c = texture2D(uSampler, vTextureCoord); + // Un-premultiply alpha before applying the color matrix. See issue #3539. + if (c.a > 0.0) { + c.rgb /= c.a; + } + vec4 result; + result.r = (m[0] * c.r); + result.r += (m[1] * c.g); + result.r += (m[2] * c.b); + result.r += (m[3] * c.a); + result.r += m[4]; - gl_FragColor.r = (m[0] * c.r); - gl_FragColor.r += (m[1] * c.g); - gl_FragColor.r += (m[2] * c.b); - gl_FragColor.r += (m[3] * c.a); - gl_FragColor.r += m[4] * c.a; + result.g = (m[5] * c.r); + result.g += (m[6] * c.g); + result.g += (m[7] * c.b); + result.g += (m[8] * c.a); + result.g += m[9]; - gl_FragColor.g = (m[5] * c.r); - gl_FragColor.g += (m[6] * c.g); - gl_FragColor.g += (m[7] * c.b); - gl_FragColor.g += (m[8] * c.a); - gl_FragColor.g += m[9] * c.a; + result.b = (m[10] * c.r); + result.b += (m[11] * c.g); + result.b += (m[12] * c.b); + result.b += (m[13] * c.a); + result.b += m[14]; - gl_FragColor.b = (m[10] * c.r); - gl_FragColor.b += (m[11] * c.g); - gl_FragColor.b += (m[12] * c.b); - gl_FragColor.b += (m[13] * c.a); - gl_FragColor.b += m[14] * c.a; + result.a = (m[15] * c.r); + result.a += (m[16] * c.g); + result.a += (m[17] * c.b); + result.a += (m[18] * c.a); + result.a += m[19]; - gl_FragColor.a = (m[15] * c.r); - gl_FragColor.a += (m[16] * c.g); - gl_FragColor.a += (m[17] * c.b); - gl_FragColor.a += (m[18] * c.a); - gl_FragColor.a += m[19] * c.a; + // Premultiply alpha again. + result.rgb *= result.a; -// gl_FragColor = vec4(m[0]); + gl_FragColor = result; } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index da5d553..34a1423 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -10,7 +10,8 @@ * @memberof PIXI * @extends PIXI.ObjectRenderer */ -export default class MeshRenderer extends core.ObjectRenderer { +export default class MeshRenderer extends core.ObjectRenderer +{ /** * constructor for renderer diff --git a/src/prepare/limiters/CountLimiter.js b/src/prepare/limiters/CountLimiter.js index 7fd0b70..265c46c 100644 --- a/src/prepare/limiters/CountLimiter.js +++ b/src/prepare/limiters/CountLimiter.js @@ -5,7 +5,8 @@ * @class * @memberof PIXI */ -export default class CountLimiter { +export default class CountLimiter +{ /** * @param {number} maxItemsPerFrame - The maximum number of items that can be prepared each frame. */ diff --git a/src/prepare/limiters/TimeLimiter.js b/src/prepare/limiters/TimeLimiter.js index 8908aba..5f40686 100644 --- a/src/prepare/limiters/TimeLimiter.js +++ b/src/prepare/limiters/TimeLimiter.js @@ -5,7 +5,8 @@ * @class * @memberof PIXI */ -export default class TimeLimiter { +export default class TimeLimiter +{ /** * @param {number} maxMilliseconds - The maximum milliseconds that can be spent preparing items each frame. */ diff --git a/test/interaction/MockPointer.js b/test/interaction/MockPointer.js index 5bac0d3..61fb0d8 100644 --- a/test/interaction/MockPointer.js +++ b/test/interaction/MockPointer.js @@ -5,7 +5,8 @@ * * @class */ -class MockPointer { +class MockPointer +{ /** * @param {PIXI.Container} stage - The root of the scene tree * @param {number} [width=100] - Width of the renderer