import { uid, BaseTextureCache, TextureCache } from '@pixi/utils';
import { FORMATS, TARGETS, TYPES, SCALE_MODES } from '@pixi/constants';
import Resource from './resources/Resource';
import BufferResource from './resources/BufferResource';
import { autoDetectResource } from './resources/autoDetectResource';
import { settings } from '@pixi/settings';
import EventEmitter from 'eventemitter3';
import bitTwiddle from 'bit-twiddle';
const defaultBufferOptions = {
scaleMode: SCALE_MODES.NEAREST,
format: FORMATS.RGBA,
premultiplyAlpha: false,
};
/**
* A texture stores the information that represents an image. All textures have a base texture.
*
* @class
* @extends PIXI.utils.EventEmitter
* @memberof PIXI
* @param {PIXI.resources.Resource|string|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} [resource=null]
* The current resource to use, for things that aren't Resource objects, will be converted
* into a Resource.
* @param {Object} [options] - Collection of options
* @param {boolean} [options.mipmap=PIXI.settings.MIPMAP_TEXTURES] - If mipmapping is enabled for texture
* @param {PIXI.WRAP_MODES} [options.wrapMode=PIXI.settings.WRAP_MODE] - Wrap mode for textures
* @param {PIXI.SCALE_MODES} [options.scaleMode=PIXI.settings.SCALE_MODE] - Default scale mode, linear, nearest
* @param {PIXI.FORMATS} [options.format=PIXI.FORMATS.RGBA] - GL format type
* @param {PIXI.TYPES} [options.type=PIXI.TYPES.UNSIGNED_BYTE] - GL data type
* @param {PIXI.TARGETS} [options.target=PIXI.TARGETS.TEXTURE_2D] - GL texture target
* @param {boolean} [options.premultiplyAlpha=true] - Pre multiply the image alpha
* @param {number} [options.width=0] - Width of the texture
* @param {number} [options.height=0] - Height of the texture
* @param {object} [options.resourceOptions] - Optional resource options,
* see {@link PIXI.resources.autoDetectResource autoDetectResource}
*/
export default class BaseTexture extends EventEmitter
{
constructor(resource = null, options = null)
{
super();
options = options || {};
const { premultiplyAlpha, mipmap, scaleMode, width, height,
wrapMode, format, type, target, resolution, resourceOptions } = options;
// Convert the resource to a Resource object
if (resource && !(resource instanceof Resource))
{
resource = autoDetectResource(resource, resourceOptions);
resource.internal = true;
}
/**
* The width of the base texture set when the image has loaded
*
* @readonly
* @member {number}
*/
this.width = width || 0;
/**
* The height of the base texture set when the image has loaded
*
* @readonly
* @member {number}
*/
this.height = height || 0;
/**
* The resolution / device pixel ratio of the texture
*
* @member {number}
* @default PIXI.settings.RESOLUTION
*/
this.resolution = resolution || settings.RESOLUTION;
/**
* If mipmapping was used for this texture, enable and disable with enableMipmap()
*
* @member {boolean}
*/
this.mipmap = mipmap !== undefined ? mipmap : settings.MIPMAP_TEXTURES;
/**
* How the texture wraps
* @member {number}
*/
this.wrapMode = wrapMode || settings.WRAP_MODE;
/**
* 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;
/**
* The pixel format of the texture
*
* @member {PIXI.FORMATS}
* @default PIXI.FORMATS.RGBA
*/
this.format = format || FORMATS.RGBA;
/**
* The type of resource data
*
* @member {PIXI.TYPES}
* @default PIXI.TYPES.UNSIGNED_BYTE
*/
this.type = type || TYPES.UNSIGNED_BYTE;
/**
* The target type
*
* @member {PIXI.TARGETS}
* @default PIXI.TARGETS.TEXTURE_2D
*/
this.target = target || TARGETS.TEXTURE_2D;
/**
* Set to true to enable pre-multiplied alpha
*
* @member {boolean}
* @default true
*/
this.premultiplyAlpha = premultiplyAlpha !== false;
/**
* Global unique identifier for this BaseTexture
*
* @member {string}
* @private
*/
this.uid = uid();
/**
* TODO: fill in description
*
* @member {number}
* @private
*/
this.touched = 0;
/**
* Whether or not the texture is a power of two, try to use power of two textures as much
* as you can
*
* @readonly
* @member {boolean}
* @default false
*/
this.isPowerOfTwo = false;
this._refreshPOT();
/**
* The map of render context textures where this is bound
*
* @member {Object}
* @private
*/
this._glTextures = {};
/**
* Used by TextureSystem to only update texture to the GPU when needed.
*
* @private
* @member {number}
*/
this.dirtyId = 0;
/**
* Used by TextureSystem to only update texture style when needed.
*
* @private
* @member {number}
*/
this.dirtyStyleId = 0;
/**
* Currently default cache ID.
*
* @member {string}
*/
this.cacheId = null;
/**
* Generally speaking means when resource is loaded.
* @readonly
* @member {boolean}
*/
this.valid = width > 0 && height > 0;
/**
* The collection of alternative cache ids, since some BaseTextures
* can have more than one ID, short name and longer full URL
*
* @member {Array<string>}
* @readonly
*/
this.textureCacheIds = [];
/**
* Flag if BaseTexture has been destroyed.
*
* @member {boolean}
* @readonly
*/
this.destroyed = false;
/**
* The resource used by this BaseTexture, there can only
* be one resource per BaseTexture, but textures can share
* resources.
*
* @member {PIXI.resources.Resource}
* @readonly
*/
this.resource = null;
// Set the resource
this.setResource(resource);
/**
* Fired when a not-immediately-available source finishes loading.
*
* @protected
* @event PIXI.BaseTexture#loaded
* @param {PIXI.BaseTexture} baseTexture - Resource loaded.
*/
/**
* Fired when a not-immediately-available source fails to load.
*
* @protected
* @event PIXI.BaseTexture#error
* @param {PIXI.BaseTexture} baseTexture - Resource errored.
*/
/**
* Fired when BaseTexture is updated.
*
* @protected
* @event PIXI.BaseTexture#loaded
* @param {PIXI.BaseTexture} baseTexture - Resource loaded.
*/
/**
* Fired when BaseTexture is destroyed.
*
* @protected
* @event PIXI.BaseTexture#error
* @param {PIXI.BaseTexture} baseTexture - Resource errored.
*/
/**
* Fired when BaseTexture is updated.
*
* @protected
* @event PIXI.BaseTexture#update
* @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated.
*/
/**
* Fired when BaseTexture is destroyed.
*
* @protected
* @event PIXI.BaseTexture#dispose
* @param {PIXI.BaseTexture} baseTexture - Instance of texture being destroyed.
*/
}
/**
* Pixel width of the source of this texture
*
* @readonly
* @member {number}
*/
get realWidth()
{
return this.width * this.resolution;
}
/**
* Pixel height of the source of this texture
*
* @readonly
* @member {number}
*/
get realHeight()
{
return this.height * this.resolution;
}
/**
* Changes style options of BaseTexture
*
* @param {object} options
* @param {PIXI.SCALE_MODES} [scaleMode] - pixi scalemode
* @param {boolean} [mipmap] - enable mipmaps
* @returns {BaseTexture} this
*/
setStyle(scaleMode, mipmap)
{
let dirty;
if (scaleMode !== undefined && scaleMode !== this.scaleMode)
{
this.scaleMode = scaleMode;
dirty = true;
}
if (mipmap !== undefined && mipmap !== this.mipmap)
{
this.mipmap = mipmap;
dirty = true;
}
if (dirty)
{
this.dirtyStyleId++;
}
return this;
}
/**
* Changes w/h/resolution. Texture becomes valid if width and height are greater than zero.
*
* @param {number} width Visual width
* @param {number} height Visual height
* @param {number} [resolution] Optionally set resolution
* @returns {BaseTexture} this
*/
setSize(width, height, resolution)
{
this.resolution = resolution || this.resolution;
this.width = width;
this.height = height;
this._refreshPOT();
this.update();
return this;
}
/**
* Sets real size of baseTexture, preserves current resolution.
*
* @param {number} realWidth Full rendered width
* @param {number} realHeight Full rendered height
* @param {number} [resolution] Optionally set resolution
* @returns {BaseTexture} this
*/
setRealSize(realWidth, realHeight, resolution)
{
this.resolution = resolution || this.resolution;
this.width = realWidth / this.resolution;
this.height = realHeight / this.resolution;
this._refreshPOT();
this.update();
return this;
}
/**
* Refresh check for isPowerOfTwo texture based on size
*
* @private
*/
_refreshPOT()
{
this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight);
}
/**
* Changes resolution
*
* @param {number} [resolution] res
* @returns {BaseTexture} this
*/
setResolution(resolution)
{
const oldResolution = this.resolution;
if (oldResolution === resolution)
{
return this;
}
this.resolution = resolution;
if (this.valid)
{
this.width = this.width * oldResolution / resolution;
this.height = this.height * oldResolution / resolution;
this.emit('update');
}
this._refreshPOT();
return this;
}
/**
* Sets the resource if it wasnt set. Throws error if resource already present
*
* @param {PIXI.resources.Resource} resource - that is managing this BaseTexture
* @returns {BaseTexture} this
*/
setResource(resource)
{
if (this.resource === resource)
{
return this;
}
if (this.resource)
{
throw new Error('Resource can be set only once');
}
resource.bind(this);
this.resource = resource;
return this;
}
/**
* Invalidates the object. Texture becomes valid if width and height are greater than zero.
*/
update()
{
if (!this.valid)
{
if (this.width > 0 && this.height > 0)
{
this.valid = true;
this.emit('loaded', this);
this.emit('update', this);
}
}
else
{
this.dirtyId++;
this.dirtyStyleId++;
this.emit('update', this);
}
}
/**
* Destroys this base texture.
* The method stops if resource doesn't want this texture to be destroyed.
* Removes texture from all caches.
*/
destroy()
{
// remove and destroy the resource
if (this.resource)
{
this.resource.unbind(this);
// only destroy resourced created internally
if (this.resource.internal)
{
this.resource.destroy();
}
this.resource = null;
}
if (this.cacheId)
{
delete BaseTextureCache[this.cacheId];
delete TextureCache[this.cacheId];
this.cacheId = null;
}
// finally let the webGL renderer know..
this.dispose();
BaseTexture.removeFromCache(this);
this.textureCacheIds = null;
this.destroyed = true;
}
/**
* 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.
*
* @fires PIXI.BaseTexture#dispose
*/
dispose()
{
this.emit('dispose', this);
}
/**
* Helper function that creates a base texture based on the source you provide.
* The source can be - image url, image element, canvas element. If the
* source is an image url or an image element and not in the base texture
* cache, it will be created and loaded.
*
* @static
* @param {string|HTMLImageElement|HTMLCanvasElement|SVGElement|HTMLVideoElement} source - The
* source to create base texture from.
* @param {object} [options] See {@link PIXI.BaseTexture}'s constructor for options.
* @return {PIXI.BaseTexture} The new base texture.
*/
static from(source, options)
{
let cacheId = null;
if (typeof source === 'string')
{
cacheId = source;
}
else
{
if (!source._pixiId)
{
source._pixiId = `pixiid_${uid()}`;
}
cacheId = source._pixiId;
}
let baseTexture = BaseTextureCache[cacheId];
if (!baseTexture)
{
baseTexture = new BaseTexture(source, options);
baseTexture.cacheId = cacheId;
BaseTexture.addToCache(baseTexture, cacheId);
}
return baseTexture;
}
/**
* Create a new BaseTexture with a BufferResource from a Float32Array.
* RGBA values are floats from 0 to 1.
* @static
* @param {Float32Array|UintArray} buffer The optional array to use, if no data
* is provided, a new Float32Array is created.
* @param {number} width - Width of the resource
* @param {number} height - Height of the resource
* @param {object} [options] See {@link PIXI.BaseTexture}'s constructor for options.
* @return {PIXI.BaseTexture} The resulting new BaseTexture
*/
static fromBuffer(buffer, width, height, options)
{
buffer = buffer || new Float32Array(width * height * 4);
const resource = new BufferResource(buffer, { width, height });
const type = buffer instanceof Float32Array ? TYPES.FLOAT : TYPES.UNSIGNED_BYTE;
return new BaseTexture(resource, Object.assign(defaultBufferOptions, options || { width, height, type }));
}
/**
* Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object.
*
* @static
* @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache.
* @param {string} id - The id that the BaseTexture will be stored against.
*/
static addToCache(baseTexture, id)
{
if (id)
{
if (baseTexture.textureCacheIds.indexOf(id) === -1)
{
baseTexture.textureCacheIds.push(id);
}
if (BaseTextureCache[id])
{
// eslint-disable-next-line no-console
console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`);
}
BaseTextureCache[id] = baseTexture;
}
}
/**
* Remove a BaseTexture from the global BaseTextureCache.
*
* @static
* @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself.
* @return {PIXI.BaseTexture|null} The BaseTexture that was removed.
*/
static removeFromCache(baseTexture)
{
if (typeof baseTexture === 'string')
{
const baseTextureFromCache = BaseTextureCache[baseTexture];
if (baseTextureFromCache)
{
const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture);
if (index > -1)
{
baseTextureFromCache.textureCacheIds.splice(index, 1);
}
delete BaseTextureCache[baseTexture];
return baseTextureFromCache;
}
}
else if (baseTexture && baseTexture.textureCacheIds)
{
for (let i = 0; i < baseTexture.textureCacheIds.length; ++i)
{
delete BaseTextureCache[baseTexture.textureCacheIds[i]];
}
baseTexture.textureCacheIds.length = 0;
return baseTexture;
}
return null;
}
}