diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/textures/new/resources/BufferResource.js b/src/core/textures/new/resources/BufferResource.js deleted file mode 100644 index 56ec6dc..0000000 --- a/src/core/textures/new/resources/BufferResource.js +++ /dev/null @@ -1,23 +0,0 @@ - -export default class BufferResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - this.uploadable = false; - - this.load = new Promise((resolve, reject) => { - - resolve(this); - - }) - } - - static from(array) - { - return new BufferResource(array); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/textures/new/resources/BufferResource.js b/src/core/textures/new/resources/BufferResource.js deleted file mode 100644 index 56ec6dc..0000000 --- a/src/core/textures/new/resources/BufferResource.js +++ /dev/null @@ -1,23 +0,0 @@ - -export default class BufferResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - this.uploadable = false; - - this.load = new Promise((resolve, reject) => { - - resolve(this); - - }) - } - - static from(array) - { - return new BufferResource(array); - } -} \ No newline at end of file diff --git a/src/core/textures/new/resources/ImageResource.js b/src/core/textures/new/resources/ImageResource.js deleted file mode 100644 index 9fa1c58..0000000 --- a/src/core/textures/new/resources/ImageResource.js +++ /dev/null @@ -1,46 +0,0 @@ - -export default class ImageResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - - this.uploadable = true; - - this.load = new Promise((resolve, reject) => { - - const source = this.source; - - source.onload = () => { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - - if(source.complete && source.src) - { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - }) - } - - static from(url) - { - var image = new Image(); - image.src = url; - return new ImageResource(image); - } - - -} \ No newline at end of file diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/textures/new/resources/BufferResource.js b/src/core/textures/new/resources/BufferResource.js deleted file mode 100644 index 56ec6dc..0000000 --- a/src/core/textures/new/resources/BufferResource.js +++ /dev/null @@ -1,23 +0,0 @@ - -export default class BufferResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - this.uploadable = false; - - this.load = new Promise((resolve, reject) => { - - resolve(this); - - }) - } - - static from(array) - { - return new BufferResource(array); - } -} \ No newline at end of file diff --git a/src/core/textures/new/resources/ImageResource.js b/src/core/textures/new/resources/ImageResource.js deleted file mode 100644 index 9fa1c58..0000000 --- a/src/core/textures/new/resources/ImageResource.js +++ /dev/null @@ -1,46 +0,0 @@ - -export default class ImageResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - - this.uploadable = true; - - this.load = new Promise((resolve, reject) => { - - const source = this.source; - - source.onload = () => { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - - if(source.complete && source.src) - { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - }) - } - - static from(url) - { - var image = new Image(); - image.src = url; - return new ImageResource(image); - } - - -} \ No newline at end of file diff --git a/src/core/textures/resources/BufferResource.js b/src/core/textures/resources/BufferResource.js new file mode 100644 index 0000000..72cf410 --- /dev/null +++ b/src/core/textures/resources/BufferResource.js @@ -0,0 +1,23 @@ + +export default class BufferResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = -1; + this.height = -1; + this.uploadable = false; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + + }) + } + + static from(array) + { + return new BufferResource(array); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/textures/new/resources/BufferResource.js b/src/core/textures/new/resources/BufferResource.js deleted file mode 100644 index 56ec6dc..0000000 --- a/src/core/textures/new/resources/BufferResource.js +++ /dev/null @@ -1,23 +0,0 @@ - -export default class BufferResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - this.uploadable = false; - - this.load = new Promise((resolve, reject) => { - - resolve(this); - - }) - } - - static from(array) - { - return new BufferResource(array); - } -} \ No newline at end of file diff --git a/src/core/textures/new/resources/ImageResource.js b/src/core/textures/new/resources/ImageResource.js deleted file mode 100644 index 9fa1c58..0000000 --- a/src/core/textures/new/resources/ImageResource.js +++ /dev/null @@ -1,46 +0,0 @@ - -export default class ImageResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - - this.uploadable = true; - - this.load = new Promise((resolve, reject) => { - - const source = this.source; - - source.onload = () => { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - - if(source.complete && source.src) - { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - }) - } - - static from(url) - { - var image = new Image(); - image.src = url; - return new ImageResource(image); - } - - -} \ No newline at end of file diff --git a/src/core/textures/resources/BufferResource.js b/src/core/textures/resources/BufferResource.js new file mode 100644 index 0000000..72cf410 --- /dev/null +++ b/src/core/textures/resources/BufferResource.js @@ -0,0 +1,23 @@ + +export default class BufferResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = -1; + this.height = -1; + this.uploadable = false; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + + }) + } + + static from(array) + { + return new BufferResource(array); + } +} \ No newline at end of file diff --git a/src/core/textures/resources/CanvasResource.js b/src/core/textures/resources/CanvasResource.js new file mode 100644 index 0000000..0d91551 --- /dev/null +++ b/src/core/textures/resources/CanvasResource.js @@ -0,0 +1,26 @@ +import determineCrossOrigin from '../../utils/determineCrossOrigin'; + +export default class CanvasResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = source.width; + this.height = source.height; + + this.uploadable = true; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + }) + } + + static from(canvas) + { + return new CanvasResource(canvas); + } + + +} \ No newline at end of file diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/textures/new/resources/BufferResource.js b/src/core/textures/new/resources/BufferResource.js deleted file mode 100644 index 56ec6dc..0000000 --- a/src/core/textures/new/resources/BufferResource.js +++ /dev/null @@ -1,23 +0,0 @@ - -export default class BufferResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - this.uploadable = false; - - this.load = new Promise((resolve, reject) => { - - resolve(this); - - }) - } - - static from(array) - { - return new BufferResource(array); - } -} \ No newline at end of file diff --git a/src/core/textures/new/resources/ImageResource.js b/src/core/textures/new/resources/ImageResource.js deleted file mode 100644 index 9fa1c58..0000000 --- a/src/core/textures/new/resources/ImageResource.js +++ /dev/null @@ -1,46 +0,0 @@ - -export default class ImageResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - - this.uploadable = true; - - this.load = new Promise((resolve, reject) => { - - const source = this.source; - - source.onload = () => { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - - if(source.complete && source.src) - { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - }) - } - - static from(url) - { - var image = new Image(); - image.src = url; - return new ImageResource(image); - } - - -} \ No newline at end of file diff --git a/src/core/textures/resources/BufferResource.js b/src/core/textures/resources/BufferResource.js new file mode 100644 index 0000000..72cf410 --- /dev/null +++ b/src/core/textures/resources/BufferResource.js @@ -0,0 +1,23 @@ + +export default class BufferResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = -1; + this.height = -1; + this.uploadable = false; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + + }) + } + + static from(array) + { + return new BufferResource(array); + } +} \ No newline at end of file diff --git a/src/core/textures/resources/CanvasResource.js b/src/core/textures/resources/CanvasResource.js new file mode 100644 index 0000000..0d91551 --- /dev/null +++ b/src/core/textures/resources/CanvasResource.js @@ -0,0 +1,26 @@ +import determineCrossOrigin from '../../utils/determineCrossOrigin'; + +export default class CanvasResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = source.width; + this.height = source.height; + + this.uploadable = true; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + }) + } + + static from(canvas) + { + return new CanvasResource(canvas); + } + + +} \ No newline at end of file diff --git a/src/core/textures/resources/ImageResource.js b/src/core/textures/resources/ImageResource.js new file mode 100644 index 0000000..c969c15 --- /dev/null +++ b/src/core/textures/resources/ImageResource.js @@ -0,0 +1,54 @@ +import determineCrossOrigin from '../../utils/determineCrossOrigin'; + +export default class ImageResource +{ + constructor(source) + { + this.source = source; + this.loaded = false; // TODO rename to ready? + this.width = -1; + this.height = -1; + + this.uploadable = true; + + this.load = new Promise((resolve, reject) => { + + const source = this.source; + + source.onload = () => { + this.loaded = true; + source.onload = null; + source.onerror = null; + this.width = source.width; + this.height = source.height; + resolve(this); + + } + + if(source.complete && source.src) + { + this.loaded = true; + source.onload = null; + source.onerror = null; + this.width = source.width; + this.height = source.height; + resolve(this); + } + }) + } + + static from(url, crossorigin) + { + var image = new Image(); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(url); + } + + image.src = url; + return new ImageResource(image); + } + + +} \ No newline at end of file diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index d192842..ef7e2da 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -58,6 +58,7 @@ bind(texture, location) { + const gl = this.gl; location = location || 0; @@ -70,7 +71,7 @@ if(texture && texture.valid) { - const glTexture = texture.glTextures[this.CONTEXT_UID] || this.initTexture(texture); + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); gl.bindTexture(texture.target, glTexture.texture); @@ -118,14 +119,14 @@ // guarentee an update.. glTexture.dirtyId = -1; - texture.glTextures[this.CONTEXT_UID] = glTexture; + texture._glTextures[this.CONTEXT_UID] = glTexture; return glTexture; } updateTexture(texture) { - const glTexture = texture.glTextures[this.CONTEXT_UID]; + const glTexture = texture._glTextures[this.CONTEXT_UID]; const gl = this.gl; //console.log(gl); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 121bf58..8e757b1 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -234,6 +234,9 @@ // xy vertexData[6] = (a * w1) + (c * h0) + tx; vertexData[7] = (d * h0) + (b * w1) + ty; + + // console.log(orig.width) + // console.log(vertexData, this.texture.baseTexture) } /** @@ -483,6 +486,11 @@ return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); } + static fromSVG(svgId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromSVG(svgId, crossorigin, scaleMode)); + } + /** * The width of the sprite, setting this will actually modify the scale to achieve the value set * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7c352e2..7ec96b9 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -115,6 +115,8 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } + this.MAX_TEXTURES = 1; + const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // create a couple of buffers @@ -428,7 +430,7 @@ // lets do a quick check.. if (rendererBoundTextures[group.ids[j]] !== currentTexture) { - this.renderer.bindTexture(currentTexture, group.ids[j], true); + this.renderer.texture.bind(currentTexture, group.ids[j], true); } // reset the virtualId.. diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index b4b5539..8955233 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -2,34 +2,42 @@ uid, getUrlFileExtension, decomposeDataUri, getSvgSize, getResolutionOfUrl, BaseTextureCache, TextureCache, } from '../utils'; + +import ImageResource from './resources/ImageResource'; +import BufferResource from './resources/BufferResource'; +import CanvasResource from './resources/CanvasResource'; +import SVGResource from './resources/SVGResource'; + import settings from '../settings'; import EventEmitter from 'eventemitter3'; -import determineCrossOrigin from '../utils/determineCrossOrigin'; import bitTwiddle from 'bit-twiddle'; -/** - * A texture stores the information that represents an image. All textures have a base texture. - * - * @class - * @extends EventEmitter - * @memberof PIXI - */ export default class BaseTexture extends EventEmitter { - /** - * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. - * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture - */ - constructor(source, scaleMode, resolution) + constructor(width, height, format, type, resolution) { + super(); + this.uid = uid(); this.touched = 0; /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** * The resolution / device pixel ratio of the texture * * @member {number} @@ -38,123 +46,6 @@ this.resolution = resolution || settings.RESOLUTION; /** - * The width of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @readonly - * @member {number} - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @readonly - * @member {number} - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @readonly - * @member {number} - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.settings.SCALE_MODE - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @readonly - * @member {boolean} - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @readonly - * @member {boolean} - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @readonly - * @member {HTMLImageElement|HTMLCanvasElement} - */ - this.source = null; // set in loadSource, if at all - - /** - * The image source that is used to create the texture. This is used to - * store the original Svg source when it is replaced with a canvas element. - * - * TODO: Currently not in use but could be used when re-scaling svg. - * - * @readonly - * @member {Image} - */ - this.origSource = null; // set in loadSvg, if at all - - /** - * Type of image defined in source, eg. `png` or `svg` - * - * @readonly - * @member {string} - */ - this.imageType = null; // set in updateImageType - - /** - * Scale for source image. Used with Svg images to scale them before rasterization. - * - * @readonly - * @member {number} - */ - this.sourceScale = 1.0; - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** * Whether or not the texture is a power of two, try to use power of two textures as much * as you can * @@ -163,475 +54,107 @@ */ this.isPowerOfTwo = false; - // used for webGL - /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() * - * Set this to true if a mipmap of this texture needs to be generated. This value needs - * to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES + * @member {Boolean} */ - this.mipmap = settings.MIPMAP_TEXTURES; + this.mipmap = false;//settings.MIPMAP_TEXTURES; /** + * Set to true to enable pre-multiplied alpha * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES + * @member {Boolean} + */ + this.premultiplyAlpha = true; + + /** + * [wrapMode description] + * @type {[type]} */ this.wrapMode = settings.WRAP_MODE; /** - * A map of renderer IDs to webgl textures + * The scale mode to apply when scaling this texture * - * @private - * @member {object} + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES */ + this.scaleMode = settings.SCALE_MODE; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || 6408//gl.RGBA; + this.type = type || 5121; //UNSIGNED_BYTE + + this.target = 3553; // gl.TEXTURE_2D + this._glTextures = {}; - // 0 = color : 1 = depth : 2 = cube; + this._new = true; - this.type = 0; + this.resource = null; - this._enabled = 0; - this._virtalBoundId = -1; + this.dirtyId = 0; - // if no source passed don't try to load - if (source) - { - this.loadSource(source); - } + this.valid = false; - /** - * Fired when a not-immediately-available source finishes loading. - * - * @protected - * @event loaded - * @memberof PIXI.BaseTexture# - */ - - /** - * Fired when a not-immediately-available source fails to load. - * - * @protected - * @event error - * @memberof PIXI.BaseTexture# - */ - - - // temp hacky API - this._newTexture = null; + this.validate(); } - /** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ - update() + setResource(resource) { - // Svg size is handled during load - if (this.imageType !== 'svg') - { - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + this.resource = resource; - this._updateDimensions(); - } + this.resource.load.then((resource) => { - this.emit('update', this); - } - - /** - * Update dimensions from real values - */ - _updateDimensions() - { - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; - - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - } - - /** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. - */ - loadSource(source) - { - const wasLoading = this.isLoading; - - this.hasLoaded = false; - this.isLoading = false; - - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } - - const firstSourceLoaded = !this.source; - - this.source = source; - - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if (((source.src && source.complete) || source.getContext) && source.width && source.height) - { - this._updateImageType(); - - if (this.imageType === 'svg') + if(this.resource === resource) { - this._loadSvgSource(); - } - else - { - this._sourceLoaded(); - } - - if (firstSourceLoaded) - { - // send loaded event if previous source was null and we have been passed a pre-loaded IMG element - this.emit('loaded', this); - } - } - else if (!source.getContext) - { - // Image fail / not ready - this.isLoading = true; - - const scope = this; - - source.onload = () => - { - scope._updateImageType(); - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) + if(resource.width !== -1 && resource.hight !== -1) { - return; + this.width = resource.width / this.resolution; + this.height = resource.height / this.resolution; } - scope.isLoading = false; - scope._sourceLoaded(); + this.validate(); - if (scope.imageType === 'svg') + if(this.valid) { - scope._loadSvgSource(); + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); - return; - } - - scope.emit('loaded', scope); - }; - - source.onerror = () => - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (scope.imageType === 'svg') - { - scope._loadSvgSource(); - - return; - } - - this.isLoading = false; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - // If any previous subscribers possible - else if (wasLoading) - { - this.emit('error', this); + // we have not swapped half way! + this.dirtyId++; + this.emit('loaded', this); } } - } + + }) } - /** - * Updates type of the source image. - */ - _updateImageType() + resize(width, height) { - if (!this.imageUrl) - { - return; - } + this.width = width; + this.height = height; - const dataUri = decomposeDataUri(this.imageUrl); - let imageType; - - if (dataUri && dataUri.mediaType === 'image') - { - // Check for subType validity - const firstSubType = dataUri.subType.split('+')[0]; - - imageType = getUrlFileExtension(`.${firstSubType}`); - - if (!imageType) - { - throw new Error('Invalid image type in data URI.'); - } - } - else - { - imageType = getUrlFileExtension(this.imageUrl); - - if (!imageType) - { - imageType = 'png'; - } - } - - this.imageType = imageType; + this.dirtyId++; } - /** - * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls - * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. - */ - _loadSvgSource() + validate() { - if (this.imageType !== 'svg') + let valid = true; + + if(this.width === -1 || this.height === -1) { - // Do nothing if source is not svg - return; + valid = false; } - const dataUri = decomposeDataUri(this.imageUrl); - - if (dataUri) - { - this._loadSvgSourceUsingDataUri(dataUri); - } - else - { - // We got an URL, so we need to do an XHR to check the svg size - this._loadSvgSourceUsingXhr(); - } - } - - /** - * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. - * - * @param {string} dataUri - The data uri to load from. - */ - _loadSvgSourceUsingDataUri(dataUri) - { - let svgString; - - if (dataUri.encoding === 'base64') - { - if (!atob) - { - throw new Error('Your browser doesn\'t support base64 conversions.'); - } - svgString = atob(dataUri.data); - } - else - { - svgString = dataUri.data; - } - - this._loadSvgSourceUsingString(svgString); - } - - /** - * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. - */ - _loadSvgSourceUsingXhr() - { - const svgXhr = new XMLHttpRequest(); - - // This throws error on IE, so SVG Document can't be used - // svgXhr.responseType = 'document'; - - // This is not needed since we load the svg as string (breaks IE too) - // but overrideMimeType() can be used to force the response to be parsed as XML - // svgXhr.overrideMimeType('image/svg+xml'); - - svgXhr.onload = () => - { - if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) - { - throw new Error('Failed to load SVG using XHR.'); - } - - this._loadSvgSourceUsingString(svgXhr.response); - }; - - svgXhr.onerror = () => this.emit('error', this); - - svgXhr.open('GET', this.imageUrl, true); - svgXhr.send(); - } - - /** - * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the - * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by - * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. - * - * @param {string} svgString SVG source as string - * - * @fires loaded - */ - _loadSvgSourceUsingString(svgString) - { - const svgSize = getSvgSize(svgString); - - const svgWidth = svgSize.width; - const svgHeight = svgSize.height; - - if (!svgWidth || !svgHeight) - { - throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); - } - - // Scale realWidth and realHeight - this.realWidth = Math.round(svgWidth * this.sourceScale); - this.realHeight = Math.round(svgHeight * this.sourceScale); - - this._updateDimensions(); - - // Create a canvas element - const canvas = document.createElement('canvas'); - - canvas.width = this.realWidth; - canvas.height = this.realHeight; - canvas._pixiId = `canvas_${uid()}`; - - // Draw the Svg to the canvas - canvas - .getContext('2d') - .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); - - // Replace the original source image with the canvas - this.origSource = this.source; - this.source = canvas; - - // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) - BaseTextureCache[canvas._pixiId] = this; - - this.isLoading = false; - this._sourceLoaded(); - this.emit('loaded', this); - } - - /** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ - _sourceLoaded() - { - this.hasLoaded = true; - this.update(); - } - - /** - * Destroys this base texture - * - */ - destroy() - { - if (this.imageUrl) - { - delete BaseTextureCache[this.imageUrl]; - delete TextureCache[this.imageUrl]; - - this.imageUrl = null; - - if (!navigator.isCocoonJS) - { - this.source.src = ''; - } - } - // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here - if (this.source && this.source._pixiId) - { - delete BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); - } - - /** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ - dispose() - { - this.emit('dispose', this); - } - - /** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param {string} newSrc - the path of the image - */ - updateSourceImage(newSrc) - { - this.source.src = newSrc; - - this.loadSource(this.source); + this.valid = valid; } /** @@ -642,10 +165,9 @@ * @param {string} imageUrl - The image url of the texture * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values - * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. * @return {PIXI.BaseTexture} The new base texture. */ - static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + static fromImage(imageUrl, crossorigin, scaleMode) { let baseTexture = BaseTextureCache[imageUrl]; @@ -653,26 +175,12 @@ { // new Image() breaks tex loading in some versions of Chrome. // See https://code.google.com/p/chromium/issues/detail?id=238071 - const image = new Image();// document.createElement('img'); + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); - } - - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; - - if (sourceScale) - { - baseTexture.sourceScale = sourceScale; - } - - // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture = new BaseTexture();//image, scaleMode); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; baseTexture.resolution = getResolutionOfUrl(imageUrl); - - image.src = imageUrl; // Setting this triggers load - + baseTexture.setResource(resource); BaseTextureCache[imageUrl] = baseTexture; } @@ -689,6 +197,7 @@ */ static fromCanvas(canvas, scaleMode) { + if (!canvas._pixiId) { canvas._pixiId = `canvas_${uid()}`; @@ -698,7 +207,13 @@ if (!baseTexture) { - baseTexture = new BaseTexture(canvas, scaleMode); + + const resource = CanvasResource.from(canvas);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + BaseTextureCache[canvas._pixiId] = baseTexture; } @@ -706,6 +221,62 @@ } /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromSVG(url, scale, scaleMode) + { + let baseTexture = BaseTextureCache[url]; + + if (!baseTexture) + { + const resource = SVGResource.from(url);// document.createElement('img'); + + baseTexture = new BaseTexture(); + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.setResource(resource); + + BaseTextureCache[url] = baseTexture; + } + + return baseTexture; + } + get realWidth() + { + return this.width * this.resolution; + } + + get realHeight() + { + return this.height * this.resolution; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromNEW(url) + { + var texture = new Texture(); + + var image = new Image(); + image.src = url; + texture.setResource(new ImageResource(image)); + + return texture; + } + + /** * Helper function that creates a base texture based on the source you provide. * The source can be - image url, image element, canvas element. * @@ -752,4 +323,27 @@ // lets assume its a base texture! return source; } -} + + static fromFloat32Array(width, height, float32Array) + { + var texture = new Texture(width, height, 6408, 5126); + + float32Array = float32Array || new Float32Array(width*height*4); + + texture.setResource(new BufferResource(float32Array)); + + return texture; + } + + static fromUint8Array(width, height, uint8Array) + { + var texture = new Texture(width, height, 6408, 5121); + + uint8Array = uint8Array || new Uint8Array(width*height*4); + + texture.setResource(new BufferResource(uint8Array)); + + return texture; + } + +} \ No newline at end of file diff --git a/src/core/textures/BaseTexture_old.js b/src/core/textures/BaseTexture_old.js new file mode 100644 index 0000000..51b15a5 --- /dev/null +++ b/src/core/textures/BaseTexture_old.js @@ -0,0 +1,751 @@ +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../utils'; +import settings from '../settings'; +import EventEmitter from 'eventemitter3'; +import determineCrossOrigin from '../utils/determineCrossOrigin'; +import bitTwiddle from 'bit-twiddle'; + +/** + * A texture stores the information that represents an image. All textures have a base texture. + * + * @class + * @extends EventEmitter + * @memberof PIXI + */ +export default class BaseTexture extends EventEmitter +{ + /** + * @param {HTMLImageElement|HTMLCanvasElement} [source] - the source object of the texture. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [resolution=1] - The resolution / device pixel ratio of the texture + */ + constructor(source, scaleMode, resolution) + { + super(); + + this.uid = uid(); + + this.touched = 0; + + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * The width of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.width = 100; + + /** + * The height of the base texture set when the image has loaded + * + * @readonly + * @member {number} + */ + this.height = 100; + + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @readonly + * @member {number} + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @readonly + * @member {number} + */ + this.realHeight = 100; + + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.settings.SCALE_MODE + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode !== undefined ? scaleMode : settings.SCALE_MODE; + + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @readonly + * @member {boolean} + */ + this.hasLoaded = false; + + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @readonly + * @member {boolean} + */ + this.isLoading = false; + + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @readonly + * @member {HTMLImageElement|HTMLCanvasElement} + */ + this.source = null; // set in loadSource, if at all + + /** + * The image source that is used to create the texture. This is used to + * store the original Svg source when it is replaced with a canvas element. + * + * TODO: Currently not in use but could be used when re-scaling svg. + * + * @readonly + * @member {Image} + */ + this.origSource = null; // set in loadSvg, if at all + + /** + * Type of image defined in source, eg. `png` or `svg` + * + * @readonly + * @member {string} + */ + this.imageType = null; // set in updateImageType + + /** + * Scale for source image. Used with Svg images to scale them before rasterization. + * + * @readonly + * @member {number} + */ + this.sourceScale = 1.0; + + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; + + /** + * The image url of the texture + * + * @member {string} + */ + this.imageUrl = null; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs + * to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = settings.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = settings.WRAP_MODE; + + /** + * A map of renderer IDs to webgl textures + * + * @private + * @member {object} + */ + this._glTextures = {}; + + // 0 = color : 1 = depth : 2 = cube; + + this.type = 0; + + + this._enabled = 0; + this._virtalBoundId = -1; + + + // if no source passed don't try to load + if (source) + { + this.loadSource(source); + } + + /** + * Fired when a not-immediately-available source finishes loading. + * + * @protected + * @event loaded + * @memberof PIXI.BaseTexture# + */ + + /** + * Fired when a not-immediately-available source fails to load. + * + * @protected + * @event error + * @memberof PIXI.BaseTexture# + */ + } + + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() + { + // Svg size is handled during load + if (this.imageType !== 'svg') + { + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this._updateDimensions(); + } + + this.emit('update', this); + } + + /** + * Update dimensions from real values + */ + _updateDimensions() + { + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + } + + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param {HTMLImageElement|HTMLCanvasElement} source - the source object of the texture. + */ + loadSource(source) + { + const wasLoading = this.isLoading; + + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + const firstSourceLoaded = !this.source; + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if (((source.src && source.complete) || source.getContext) && source.width && source.height) + { + this._updateImageType(); + + if (this.imageType === 'svg') + { + this._loadSvgSource(); + } + else + { + this._sourceLoaded(); + } + + if (firstSourceLoaded) + { + // send loaded event if previous source was null and we have been passed a pre-loaded IMG element + this.emit('loaded', this); + } + } + else if (!source.getContext) + { + // Image fail / not ready + this.isLoading = true; + + const scope = this; + + source.onload = () => + { + scope._updateImageType(); + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + scope.emit('loaded', scope); + }; + + source.onerror = () => + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (scope.imageType === 'svg') + { + scope._loadSvgSource(); + + return; + } + + this.isLoading = false; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + // If any previous subscribers possible + else if (wasLoading) + { + this.emit('error', this); + } + } + } + } + + /** + * Updates type of the source image. + */ + _updateImageType() + { + if (!this.imageUrl) + { + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + let imageType; + + if (dataUri && dataUri.mediaType === 'image') + { + // Check for subType validity + const firstSubType = dataUri.subType.split('+')[0]; + + imageType = getUrlFileExtension(`.${firstSubType}`); + + if (!imageType) + { + throw new Error('Invalid image type in data URI.'); + } + } + else + { + imageType = getUrlFileExtension(this.imageUrl); + + if (!imageType) + { + imageType = 'png'; + } + } + + this.imageType = imageType; + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + if (this.imageType !== 'svg') + { + // Do nothing if source is not svg + return; + } + + const dataUri = decomposeDataUri(this.imageUrl); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Reads an SVG string from data URI and then calls `_loadSvgSourceUsingString`. + * + * @param {string} dataUri - The data uri to load from. + */ + _loadSvgSourceUsingDataUri(dataUri) + { + let svgString; + + if (dataUri.encoding === 'base64') + { + if (!atob) + { + throw new Error('Your browser doesn\'t support base64 conversions.'); + } + svgString = atob(dataUri.data); + } + else + { + svgString = dataUri.data; + } + + this._loadSvgSourceUsingString(svgString); + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.imageUrl, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.realWidth = Math.round(svgWidth * this.sourceScale); + this.realHeight = Math.round(svgHeight * this.sourceScale); + + this._updateDimensions(); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.realWidth; + canvas.height = this.realHeight; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(this.source, 0, 0, svgWidth, svgHeight, 0, 0, this.realWidth, this.realHeight); + + // Replace the original source image with the canvas + this.origSource = this.source; + this.source = canvas; + + // Add also the canvas in cache (destroy clears by `imageUrl` and `source._pixiId`) + BaseTextureCache[canvas._pixiId] = this; + + this.isLoading = false; + this._sourceLoaded(); + this.emit('loaded', this); + } + + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete BaseTextureCache[this.imageUrl]; + delete TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + // An svg source has both `imageUrl` and `__pixiId`, so no `else if` here + if (this.source && this.source._pixiId) + { + delete BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param {string} newSrc - the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const image = new Image();// document.createElement('img'); + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; + } +} diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index afe4254..31f8990 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -125,7 +125,7 @@ throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually'); } - if (baseTexture.hasLoaded) + if (baseTexture.valid) { if (this.noFrame) { @@ -199,6 +199,7 @@ */ onBaseTextureUpdated(baseTexture) { + this._updateID++; this._frame.width = baseTexture.width; @@ -296,6 +297,20 @@ return texture; } + + static fromSVG(svgUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[svgUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromSVG(svgUrl, crossorigin, scaleMode, sourceScale)); + TextureCache[svgUrl] = texture; + } + + return texture; + } + /** * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId * The frame ids are created when a Texture packer file has been loaded @@ -503,7 +518,7 @@ } // this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; - this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; + this.valid = frame && frame.width && frame.height && this.baseTexture.valid; if (!this.trim && !this.rotate) { diff --git a/src/core/textures/new/CubeTexture.js b/src/core/textures/new/CubeTexture.js index c8d4fdf..c3cde40 100644 --- a/src/core/textures/new/CubeTexture.js +++ b/src/core/textures/new/CubeTexture.js @@ -1,5 +1,5 @@ -import Texture from './Texture'; -import ImageResource from './resources/ImageResource'; +import Texture from '../BaseTexture'; +import ImageResource from '../resources/ImageResource'; export default class CubeTexture extends Texture { diff --git a/src/core/textures/new/Texture.js b/src/core/textures/new/Texture.js index d88032e..b4d849f 100644 --- a/src/core/textures/new/Texture.js +++ b/src/core/textures/new/Texture.js @@ -1,12 +1,25 @@ -import ImageResource from './resources/ImageResource'; -import BufferResource from './resources/BufferResource'; +import { + uid, getUrlFileExtension, decomposeDataUri, getSvgSize, + getResolutionOfUrl, BaseTextureCache, TextureCache, +} from '../../utils'; + +import ImageResource from '../resources/ImageResource'; +import BufferResource from '../resources/BufferResource'; import settings from '../../settings'; +import EventEmitter from 'eventemitter3'; - -export default class Texture +export default class Texture extends EventEmitter { - constructor(width, height, format, type) + constructor(width, height, format, type, resolution) { + + super(); + + + this.uid = uid(); + + this.touched = 0; + /** * The width of texture * @@ -20,6 +33,23 @@ */ this.height = height || -1; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || settings.RESOLUTION; + + /** + * Whether or not the texture is a power of two, try to use power of two textures as much + * as you can + * + * @private + * @member {boolean} + */ + this.isPowerOfTwo = false; + /** * If mipmapping was used for this texture, enable and disable with enableMipmap() * @@ -74,6 +104,16 @@ this.validate(); } + get realWidth() + { + return this.width / this.resolution; + } + + get realHeight() + { + return this.height / this.resolution; + } + setResource(resource) { this.resource = resource; @@ -120,15 +160,113 @@ this.valid = valid; } - static from(url) + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromImage(imageUrl, crossorigin, scaleMode) { - var texture = new Texture(); + let baseTexture = BaseTextureCache[imageUrl]; - var image = new Image(); - image.src = url; - texture.setResource(new ImageResource(image)); + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + const resource = ImageResource.from(imageUrl, crossorigin);// document.createElement('img'); - return texture; + baseTexture = new Texture();//image, scaleMode); + + baseTexture.scaleMode = scaleMode || baseTexture.scaleMode; + baseTexture.resolution = getResolutionOfUrl(imageUrl); + baseTexture.setResource(new ImageResource(image)); + + image.src = imageUrl; // Setting this triggers load + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.BaseTexture} The new base texture. + */ + static fromCanvas(canvas, scaleMode) + { + + if (!canvas._pixiId) + { + canvas._pixiId = `canvas_${uid()}`; + } + + let baseTexture = BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture based on the source you provide. + * The source can be - image url, image element, canvas element. + * + * @static + * @param {string|HTMLImageElement|HTMLCanvasElement} source - The source to create base texture from. + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with Svg images. + * @return {PIXI.BaseTexture} The new base texture. + */ + static from(source, scaleMode, sourceScale) + { + if (typeof source === 'string') + { + return BaseTexture.fromImage(source, undefined, scaleMode, sourceScale); + } + else if (source instanceof HTMLImageElement) + { + const imageUrl = source.src; + let baseTexture = BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(source, scaleMode); + baseTexture.imageUrl = imageUrl; + + if (sourceScale) + { + baseTexture.sourceScale = sourceScale; + } + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = getResolutionOfUrl(imageUrl); + + BaseTextureCache[imageUrl] = baseTexture; + } + + return baseTexture; + } + else if (source instanceof HTMLCanvasElement) + { + return BaseTexture.fromCanvas(source, scaleMode); + } + + // lets assume its a base texture! + return source; } static fromFloat32Array(width, height, float32Array) diff --git a/src/core/textures/new/resources/BufferResource.js b/src/core/textures/new/resources/BufferResource.js deleted file mode 100644 index 56ec6dc..0000000 --- a/src/core/textures/new/resources/BufferResource.js +++ /dev/null @@ -1,23 +0,0 @@ - -export default class BufferResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - this.uploadable = false; - - this.load = new Promise((resolve, reject) => { - - resolve(this); - - }) - } - - static from(array) - { - return new BufferResource(array); - } -} \ No newline at end of file diff --git a/src/core/textures/new/resources/ImageResource.js b/src/core/textures/new/resources/ImageResource.js deleted file mode 100644 index 9fa1c58..0000000 --- a/src/core/textures/new/resources/ImageResource.js +++ /dev/null @@ -1,46 +0,0 @@ - -export default class ImageResource -{ - constructor(source) - { - this.source = source; - this.loaded = false; // TODO rename to ready? - this.width = -1; - this.height = -1; - - this.uploadable = true; - - this.load = new Promise((resolve, reject) => { - - const source = this.source; - - source.onload = () => { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - - if(source.complete && source.src) - { - this.loaded = true; - source.onload = null; - source.onerror = null; - this.width = source.width; - this.height = source.height; - resolve(this); - } - }) - } - - static from(url) - { - var image = new Image(); - image.src = url; - return new ImageResource(image); - } - - -} \ No newline at end of file diff --git a/src/core/textures/resources/BufferResource.js b/src/core/textures/resources/BufferResource.js new file mode 100644 index 0000000..72cf410 --- /dev/null +++ b/src/core/textures/resources/BufferResource.js @@ -0,0 +1,23 @@ + +export default class BufferResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = -1; + this.height = -1; + this.uploadable = false; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + + }) + } + + static from(array) + { + return new BufferResource(array); + } +} \ No newline at end of file diff --git a/src/core/textures/resources/CanvasResource.js b/src/core/textures/resources/CanvasResource.js new file mode 100644 index 0000000..0d91551 --- /dev/null +++ b/src/core/textures/resources/CanvasResource.js @@ -0,0 +1,26 @@ +import determineCrossOrigin from '../../utils/determineCrossOrigin'; + +export default class CanvasResource +{ + constructor(source) + { + this.source = source; + this.loaded = true; // TODO rename to ready? + this.width = source.width; + this.height = source.height; + + this.uploadable = true; + + this.load = new Promise((resolve, reject) => { + + resolve(this); + }) + } + + static from(canvas) + { + return new CanvasResource(canvas); + } + + +} \ No newline at end of file diff --git a/src/core/textures/resources/ImageResource.js b/src/core/textures/resources/ImageResource.js new file mode 100644 index 0000000..c969c15 --- /dev/null +++ b/src/core/textures/resources/ImageResource.js @@ -0,0 +1,54 @@ +import determineCrossOrigin from '../../utils/determineCrossOrigin'; + +export default class ImageResource +{ + constructor(source) + { + this.source = source; + this.loaded = false; // TODO rename to ready? + this.width = -1; + this.height = -1; + + this.uploadable = true; + + this.load = new Promise((resolve, reject) => { + + const source = this.source; + + source.onload = () => { + this.loaded = true; + source.onload = null; + source.onerror = null; + this.width = source.width; + this.height = source.height; + resolve(this); + + } + + if(source.complete && source.src) + { + this.loaded = true; + source.onload = null; + source.onerror = null; + this.width = source.width; + this.height = source.height; + resolve(this); + } + }) + } + + static from(url, crossorigin) + { + var image = new Image(); + + if (crossorigin === undefined && url.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(url); + } + + image.src = url; + return new ImageResource(image); + } + + +} \ No newline at end of file diff --git a/src/core/textures/resources/SVGResource.js b/src/core/textures/resources/SVGResource.js new file mode 100644 index 0000000..f00fee0 --- /dev/null +++ b/src/core/textures/resources/SVGResource.js @@ -0,0 +1,134 @@ +import determineCrossOrigin from '../../utils/determineCrossOrigin'; +import { + decomposeDataUri, getSvgSize, uid +} from '../../utils'; + +export default class SVGResource +{ + constructor(svgSource, scale) + { + + this.source = null;//source; + this.loaded = false; // TODO rename to ready? + this.width = -1; + this.height = -1; + + this.svgSource = svgSource; + this.scale = 1 || scale; + this.uploadable = true; + + this.resolve = null; + + this.load = new Promise((resolve, reject) => { + + this.resolve = resolve; + this._loadSvgSourceUsingXhr(); + + }) + } + + /** + * Checks if `source` is an SVG image and whether it's loaded via a URL or a data URI. Then calls + * `_loadSvgSourceUsingDataUri` or `_loadSvgSourceUsingXhr`. + */ + _loadSvgSource() + { + const dataUri = decomposeDataUri(this.svgSource); + + if (dataUri) + { + this._loadSvgSourceUsingDataUri(dataUri); + } + else + { + // We got an URL, so we need to do an XHR to check the svg size + this._loadSvgSourceUsingXhr(); + } + } + + /** + * Loads an SVG string from `imageUrl` using XHR and then calls `_loadSvgSourceUsingString`. + */ + _loadSvgSourceUsingXhr() + { + const svgXhr = new XMLHttpRequest(); + + // This throws error on IE, so SVG Document can't be used + // svgXhr.responseType = 'document'; + + // This is not needed since we load the svg as string (breaks IE too) + // but overrideMimeType() can be used to force the response to be parsed as XML + // svgXhr.overrideMimeType('image/svg+xml'); + + svgXhr.onload = () => + { + if (svgXhr.readyState !== svgXhr.DONE || svgXhr.status !== 200) + { + throw new Error('Failed to load SVG using XHR.'); + } + + this._loadSvgSourceUsingString(svgXhr.response); + }; + + svgXhr.onerror = () => this.emit('error', this); + + svgXhr.open('GET', this.svgSource, true); + svgXhr.send(); + } + + /** + * Loads texture using an SVG string. The original SVG Image is stored as `origSource` and the + * created canvas is the new `source`. The SVG is scaled using `sourceScale`. Called by + * `_loadSvgSourceUsingXhr` or `_loadSvgSourceUsingDataUri`. + * + * @param {string} svgString SVG source as string + * + * @fires loaded + */ + _loadSvgSourceUsingString(svgString) + { + const svgSize = getSvgSize(svgString); + + + // TODO do we need to wait for this to load? + // seems instant! + // + const tempImage = new Image(); + tempImage.src = 'data:image/svg+xml,' + svgString; + + const svgWidth = svgSize.width; + const svgHeight = svgSize.height; + + if (!svgWidth || !svgHeight) + { + throw new Error('The SVG image must have width and height defined (in pixels), canvas API needs them.'); + } + + // Scale realWidth and realHeight + this.width = Math.round(svgWidth * this.scale); + this.height = Math.round(svgHeight * this.scale); + + // Create a canvas element + const canvas = document.createElement('canvas'); + + canvas.width = this.width; + canvas.height = this.height; + canvas._pixiId = `canvas_${uid()}`; + + // Draw the Svg to the canvas + canvas + .getContext('2d') + .drawImage(tempImage, 0, 0, svgWidth, svgHeight, 0, 0, this.width, this.height); + + this.source = canvas; + + this.resolve(this); + } + + static from(url, crossorigin) + { + return new SVGResource(url); + } + + +} \ No newline at end of file