Source: display/Texture.js

//------------------------------------------------------------------------------
// Constructor scope
//------------------------------------------------------------------------------

/**
 * Creates a new texture.
 *
 * @constructor
 * @package
 *
 * @param {rune.display.Graphic} graphic Reference to the object to be associated with the texture.
 * @param {HTMLImageElement} resource Reference to the bitmap to be used for texture data.
 *
 * @class
 * @classdesc
 * 
 * Represents a 24- or 32-bit bitmap texture. A texture can be "shared" or 
 * "unique". A shared texture reads its bitmap data from the resource library 
 * and is thus read-only. A unique texture creates its own bitmap data and thus 
 * allocates more memory, but is fully writable.
 */
rune.display.Texture = function(graphic, resource) {
	
	//--------------------------------------------------------------------------
	// Private properties
	//--------------------------------------------------------------------------

	/**
	 * Reference to unique texture data.
	 *
	 * @type {rune.display.Canvas}
	 * @private
	 */
	this.m_canvas = null;
	
	/**
	 * Reference to the object using the texture.
	 *
	 * @type {rune.display.Graphic}
	 * @private
	 */
	this.m_graphic = graphic;
	
	/**
	 * Reference to shared texture data.
	 *
	 * @type {HTMLImageElement}
	 * @private
	 */
	this.m_resource = resource;
};

//------------------------------------------------------------------------------
// Public getter and setter methods
//------------------------------------------------------------------------------

/**
 * Texture data. When the texture is unique, the texture data consists of an 
 * HTMLCanvasElement object. When the texture is not unique, the texture data 
 * consists of an Image object that is referenced directly from the resource 
 * library. Non-unique data can never be changed or manipulated during runtime.
 *
 * @member {Object} data
 * @memberof rune.display.Texture
 * @instance
 * @readonly
 */
Object.defineProperty(rune.display.Texture.prototype, "data", {
	/**
	 * @this rune.display.Texture
	 * @ignore
	 */
	get : function() {
		return (this.m_canvas) ? this.m_canvas['element'] : this.m_resource;
	}
});

/**
 * The width of the texture in pixels.
 *
 * @member {number} height
 * @memberof rune.display.Texture
 * @instance
 * @readonly
 */
Object.defineProperty(rune.display.Texture.prototype, "height", {
	/**
	 * @this rune.display.Texture
	 * @ignore
	 */
	get : function() {
		return this.data.height;
	}
});

/**
 * Whether the texture should use its own pixel buffer or use a shared memory 
 * reference to an object in the resource library. Usually textures do not 
 * have to be unique, but it can be useful when you want to draw on, or 
 * otherwise change the texture during running time.
 *
 * @member {boolean} unique
 * @memberof rune.display.Texture
 * @instance
 * @readonly
 */
Object.defineProperty(rune.display.Texture.prototype, "unique", {
	/**
	 * @this rune.display.Texture
	 * @ignore
	 */
	get : function() {
		return (this.m_canvas != null);
	}
});

/**
 * The width of the texture in pixels.
 *
 * @member {number} width
 * @memberof rune.display.Texture
 * @instance
 * @readonly
 */
Object.defineProperty(rune.display.Texture.prototype, "width", {
	/**
	 * @this rune.display.Texture
	 * @ignore
	 */
	get : function() {
		return this.data.width;
	}
});

//------------------------------------------------------------------------------
// Public prototype methods (API)
//------------------------------------------------------------------------------

/**
 * Replaces one specific color with another. Useful for creating multiple 
 * versions of the same texture data with minor color variations. Note that 
 * this method converts the texture data to be unique. The color change cannot 
 * be performed if the texture data is not unique.
 *
 * @param {rune.color.Color24} c1 Old color.
 * @param {rune.color.Color24} c2 New color.
 *
 * @return {undefined}
 */
rune.display.Texture.prototype.replaceColor = function(c1, c2) {
	if (this['unique'] == false) this.m_constructCanvas();
	var image = this.m_canvas['context'].getImageData(0, 0, this.m_canvas.width, this.m_canvas.height);
	for (var i = 0; i < image.data.length; i += 4) {
		  if (image.data[i    ] == c1['r']['value'] &&
			  image.data[i + 1] == c1['g']['value'] &&
			  image.data[i + 2] == c1['b']['value']) {
			  image.data[i    ]  = c2['r']['value'];
			  image.data[i + 1]  = c2['g']['value'];
			  image.data[i + 2]  = c2['b']['value'];
		}
	}
	
	this.m_canvas['context'].putImageData(image, 0, 0);
	this.m_graphic.breakCache();
};

//------------------------------------------------------------------------------
// Internal prototype methods
//------------------------------------------------------------------------------

/**
 * Dispose texture.
 *
 * @return {undefined}
 * @package
 * @ignore
 */
rune.display.Texture.prototype.dispose = function() {
	this.m_disposeCanvas();
};

//------------------------------------------------------------------------------
// Protected prototype methods
//------------------------------------------------------------------------------

/**
 * The class constructor.
 *
 * @return {undefined}
 * @protected
 * @ignore
 */
rune.display.Texture.prototype.m_construct = function() {
	//@note: Nothing, ATM.
};

//------------------------------------------------------------------------------
// Private prototype methods
//------------------------------------------------------------------------------

/**
 * Construct texture canvas.
 *
 * @throws {Error} If no new canvas can be created.
 *
 * @return {undefined}
 * @private
 */
rune.display.Texture.prototype.m_constructCanvas = function() {
	this.m_disposeCanvas();
	if (this.m_canvas == null) {
		this.m_canvas = new rune.display.Canvas(
			this.m_resource.width,
			this.m_resource.height
		);
		
		this.m_canvas.drawImage(
			this.m_resource,
			0,
			0,
			this.m_resource.width,
			this.m_resource.height
		);
		
	} else throw new Error();
};

/**
 * Dispose texture canvas.
 *
 * @return {undefined}
 * @private
 */
rune.display.Texture.prototype.m_disposeCanvas = function() {
	if (this.m_canvas instanceof rune.display.Canvas) {
		this.m_canvas.dispose();
		this.m_canvas = null;
	}
};