API Docs for: 1.4.0
Show:

File: src\utils\Color.ts

/**
* @module Kiwi
* @submodule Utils
* @namespace Kiwi.Utils
*/
module Kiwi.Utils {

	/**
	* Utility class used to make color management more transparent.
	* Color objects hold color and alpha values, and can get or set them
	* in a variety of ways.
	*
	* Construct this object in one of the following ways.
	*
	* - Pass 3 or 4 numbers to determine RGB or RGBA. If the numbers are in
	* the range 0-1, they will be parsed as normalized numbers.
	* If they are in the range 1-255, they will be parsed as 8-bit channels.
	*
	* - Pass 3 or 4 numbers followed by the string "hsv" or "hsl"
	* (lowercase) to parse HSV or HSL color space (with optional alpha).
	* HSV and HSL colors may be specified as normalized parameters (0-1),
	* or as an angle (0-360) and two percentages (0-100).
	*
	* - Pass a string containing a hexadecimal color with or without alpha
	* (such as "ff8040ff" or "4080ff"). You may prepend "#" or "0x", but
	* they are not necessary and will be stripped.
	*
	* - Pass a string containing a CSS color function, such as
	* "rgb(255,255,255)", "rgba( 192, 127, 64, 32 )",
	* "hsl(180, 100, 100)", or "hsla(360, 50, 50, 50)".
	*
	* - Pass 1 number to set a grayscale value, or 2 numbers to set grayscale
	* with alpha. These are interpreted as with RGB values.
	*
	* The color object stores its internal values as normalized RGBA channels.
	* This is the most mathematically useful format, and corresponds
	* with the WebGL color paradigm. When you query the color object's values,
	* such as with "r" or "red" properties, it will return normalized values.
	* You can get values in the 0-255 8-bit range by calling the
	* corresponding x255 value. For example, if r = 1, then r255 = 255.
	*
	* We advise that you work with normalized colors wherever possible.
	* While the Color object is smart enough to recognise non-normalized
	* ranges in most cases, it cannot tell the difference between 0.5 on a
	* 0-1 scale, and 0.5 on a 0-255 scale. Try to reduce ambiguity by working
	* in normalized color space.
	*
	* You can get HSV, HSL, and hexadecimal values with the functions
	* "getHsva", "getHsla", and "getHex". By default, these all include an
	* alpha term. You can omit alpha from the getHex result by calling the
	* function with the parameter "false". As getHsva and getHsla return objects
	* rather than strings, you can freely ignore the provided alpha.
	*
	* You can modify a Color object once created using its properties, methods,
	* or the "set" method as you would use the constructor.
	*
	* @class Color
	* @constructor
	* @param [...args] Any number of arguments
	* @since 1.2.0
	*/
	export class Color {

		constructor( ...args ) {
			this.set.apply( this, args );

			return this;
		}

		/**
		* Set colors from parameters, as in the class description.
		* If you supply invalid parameters, the color will be unchanged.
		* @method set
		* @param params {object} Composite parameter object
		* @return {Kiwi.Utils.Color} This object with the new color set
		* @public
		*/
		public set( ...params ) {

			if ( params.length === 3 ) {

				// RGB
				this.r = params[ 0 ];
				this.g = params[ 1 ];
				this.b = params[ 2 ];
			} else if ( params.length === 4 ) {
				if ( !isNaN( params[ 3 ] ) ) {

					// RGBA
					this.r = params[ 0 ];
					this.g = params[ 1 ];
					this.b = params[ 2 ];
					this.a = params[ 3 ];
				} else if ( params[ 3 ] === "hsv" ) {

					// HSV
					this.parseHsv( params[ 0 ], params[ 1 ], params[ 2 ] );
				} else if ( params[ 3 ] === "hsl" ) {

					// HSL
					this.parseHsl( params[ 0 ], params[ 1 ], params[ 2 ] );
				}
			} else if ( params.length === 5 ) {
				if ( params [ 4 ] === "hsv" ) {

					// HSVA
					this.parseHsv( params[ 0 ], params[ 1 ], params[ 2 ], params[ 3 ] );
				} else if ( params [ 4 ] === "hsl" ) {

					// HSLA
					this.parseHsl( params[ 0 ], params[ 1 ], params[ 2 ], params[ 3 ] );
				}
			} else if ( params.length === 1 ) {
				if ( typeof params[ 0 ] === "string" ) {

					// String format
					this.parseString( params[ 0 ] );
				} else if ( !isNaN( params[ 0 ] ) ) {

					// Grayscale
					this.r = params[ 0 ];
					this.g = params[ 0 ];
					this.b = params[ 0 ];
				}
			}
			else if ( params.length === 2 ) {

				// Grayscale and alpha
				this.r = params[ 0 ];
				this.g = params[ 0 ];
				this.b = params[ 0 ];
				this.a = params[ 1 ];
			}

			return this;
		}


		/**
		* Red channel, stored as a normalized value between 0 and 1.
		* This is most compatible with graphics hardware.
		* @property _r
		* @type number
		* @default 0.5
		* @private
		*/
		public _r: number = 0.5;

		/**
		* Green channel, stored as a normalized value between 0 and 1.
		* This is most compatible with graphics hardware.
		* @property _g
		* @type number
		* @default 0.5
		* @private
		*/
		public _g: number = 0.5;

		/**
		* Blue channel, stored as a normalized value between 0 and 1.
		* This is most compatible with graphics hardware.
		* @property _b
		* @type number
		* @default 0.5
		* @private
		*/
		public _b: number = 0.5;

		/**
		* Alpha channel, stored as a normalized value between 0 and 1.
		* This is most compatible with graphics hardware.
		* @property _a
		* @type number
		* @default 0.5
		* @private
		*/
		public _a: number = 1;



		/**
		* Red channel, stored as a normalized value between 0 and 1.
		* @property rNorm
		* @type number
		* @public
		*/
		public get rNorm(): number {
			return this._r;
		}

		public set rNorm( value: number ) {
			if ( !isNaN( value ) ) {
				this._r = value;
			}
		}

		/**
		* Green channel, stored as a normalized value between 0 and 1.
		* @property gNorm
		* @type number
		* @public
		*/
		public get gNorm(): number {
			return this._g;
		}

		public set gNorm( value: number ) {
			if ( !isNaN( value ) ) {
				this._g = value;
			}
		}

		/**
		* Blue channel, stored as a normalized value between 0 and 1.
		* @property bNorm
		* @type number
		* @public
		*/
		public get bNorm(): number {
			return this._b;
		}

		public set bNorm( value: number ) {
			if ( !isNaN( value ) ) {
				this._b = value;
			}
		}

		/**
		* Alpha channel, stored as a normalized value between 0 and 1.
		* @property aNorm
		* @type number
		* @public
		*/
		public get aNorm(): number {
			return this._a;
		}

		public set aNorm( value: number ) {
			if ( !isNaN( value ) ) {
				this._a = value;
			}
		}

		/**
		* Red channel.
		* If set to a number in the range 0-1, is interpreted as a
		* normalized color (see rNorm).
		* If set to a number above 1, is interpreted as an 8-bit channel
		* (see r255).
		* If queried, returns a normalized number in the range 0-1.
		* @property r
		* @type number
		* @public
		*/
		public get r(): number {
			return this._r;
		}

		public set r( value: number ) {
			if ( value > 1 ) {
				this.r255 = value;
			} else {
				this.rNorm = value;
			}
		}

		/**
		* Green channel.
		* If set to a number in the range 0-1, is interpreted as a
		* normalized color (see gNorm).
		* If set to a number above 1, is interpreted as an 8-bit channel
		* (see g255).
		* If queried, returns a normalized number in the range 0-1.
		* @property g
		* @type number
		* @public
		*/
		public get g(): number {
			return this._g;
		}

		public set g( value: number ) {
			if ( value > 1 ) {
				this.g255 = value;
			} else {
				this.gNorm = value;
			}
		}

		/**
		* Blue channel.
		* If set to a number in the range 0-1, is interpreted as a
		* normalized color (see bNorm).
		* If set to a number above 1, is interpreted as an 8-bit channel
		* (see b255).
		* If queried, returns a normalized number in the range 0-1.
		* @property b
		* @type number
		* @public
		*/
		public get b(): number {
			return this._b;
		}

		public set b( value: number ) {
			if ( value > 1 ) {
				this.b255 = value;
			} else {
				this.bNorm = value;
			}
		}

		/**
		* Alpha channel.
		* If set to a number in the range 0-1, is interpreted as a
		* normalized color (see aNorm).
		* If set to a number above 1, is interpreted as an 8-bit channel
		* (see a255).
		* If queried, returns a normalized number in the range 0-1.
		* @property a
		* @type number
		* @public
		*/
		public get a(): number {
			return this._a;
		}

		public set a( value: number ) {
			if ( value > 1 ) {
				this.a255 = value;
			} else {
				this.aNorm = value;
			}
		}

		/**
		* Red channel, specified as an 8-bit channel in the range 0-255.
		* @property r255
		* @type number
		* @public
		*/
		public get r255(): number {
			return Math.round( this._r * 255 );
		}

		public set r255( value: number ) {
			if ( !isNaN( value ) ) {
				this._r = value / 255;
			}
		}

		/**
		* Green channel, specified as an 8-bit channel in the range 0-255.
		* @property g255
		* @type number
		* @public
		*/
		public get g255(): number {
			return Math.round( this._g * 255 );
		}

		public set g255( value: number ) {
			if ( !isNaN( value ) ) {
				this._g = value / 255;
			}
		}

		/**
		* Blue channel, specified as an 8-bit channel in the range 0-255.
		* @property b255
		* @type number
		* @public
		*/
		public get b255(): number {
			return Math.round( this._b * 255 );
		}

		public set b255( value: number ) {
			if ( !isNaN( value ) ) {
				this._b = value / 255;
			}
		}

		/**
		* Alpha channel, specified as an 8-bit channel in the range 0-255.
		* @property a255
		* @type number
		* @public
		*/
		public get a255(): number {
			return Math.round( this._a * 255 );
		}

		public set a255( value: number ) {
			if ( !isNaN( value ) ) {
				this._a = value / 255;
			}
		}

		/**
		* Red channel, alias of r
		* @property red
		* @type number
		* @public
		*/
		public get red(): number {
			return this.r;
		}

		public set red( value: number ) {
			this.r = value;
		}

		/**
		* Green channel, alias of g
		* @property green
		* @type number
		* @public
		*/
		public get green(): number {
			return this.g;
		}

		public set green( value: number ) {
			this.g = value;
		}

		/**
		* Blue channel, alias of b
		* @property blue
		* @type number
		* @public
		*/
		public get blue(): number {
			return this.b;
		}

		public set blue( value: number ) {
			this.b = value;
		}

		/**
		* Alpha channel, alias of a
		* @property alpha
		* @type number
		* @public
		*/
		public get alpha(): number {
			return this.a;
		}

		public set alpha( value: number ) {
			this.a = value;
		}

		/**
		* Parse colors from strings
		* @method parseString
		* @param color {string} A CSS color specification
		* @return {Kiwi.Utils.Color} This object with the new color set
		* @public
		*/
		public parseString( color: string ): Kiwi.Utils.Color {
			var colArray;

			color = color.toLowerCase();

			// RGBA notation
			if ( color.slice( 0, 4 ) === "rgba" ) {
				color = color.replace( "rgba", "" );
				color = color.replace( "(", "" );
				color = color.replace( ")", "" );
				colArray = color.split( "," );
				this.r = +colArray[ 0 ];
				this.g = +colArray[ 1 ];
				this.b = +colArray[ 2 ];
				this.a = +colArray[ 3 ];
			} else if ( color.slice( 0, 3 ) === "rgb" ) {
				color = color.replace( "rgb", "" );
				color = color.replace( "(", "" );
				color = color.replace( ")", "" );
				colArray = color.split( "," );
				this.r = +colArray[ 0 ];
				this.g = +colArray[ 1 ];
				this.b = +colArray[ 2 ];
			} else if ( color.slice( 0, 4 ) === "hsla" ) {
				color = color.replace( "hsla", "" );
				color = color.replace( "(", "" );
				color = color.replace( ")", "" );
				colArray = color.split( "," );
				this.parseHsl( +colArray[ 0 ], +colArray[ 1 ], +colArray[ 2 ], +colArray[ 3 ] );
			} else if ( color.slice( 0, 3 ) === "hsl" ) {
				color = color.replace( "hsl", "" );
				color = color.replace( "(", "" );
				color = color.replace( ")", "" );
				colArray = color.split( "," );
				this.parseHsl( +colArray[ 0 ], +colArray[ 1 ], +colArray[ 2 ] );
			} else {
				this.parseHex( color );
			}

			return this;
		}

		/**
		* Parse hexadecimal colors from strings
		* @method parseHex
		* @param color {string} A hexadecimal color such as "ffffff" (no alpha)
		*	or "ffffffff" (with alpha). Also supports "fff" and "ffff"
		*	with 4-bit channels.
		* @return {Kiwi.Utils.Color} This object with the new color set
		* @public
		*/
		public parseHex( color: string ): Kiwi.Utils.Color {
			var bigint,
				r = this.r255,
				g = this.g255,
				b = this.b255,
				a = this.a255;

			// Strip leading signifiers
			if ( color.charAt( 0 ) === "#" ) {
				color = color.slice( 1 );
			}
			if ( color.slice( 0, 2 ) === "0x" ) {
				color = color.slice( 2 );
			}

			bigint = parseInt( color, 16 );

			if ( color.length === 3 ) {
				r = 17 * ( ( bigint >> 8 ) & 15 );
				g = 17 * ( ( bigint >> 4 ) & 15 );
				b = 17 * ( bigint & 15 );
			} else if ( color.length === 4 ) {
				r = 17 * ( ( bigint >> 12 ) & 15 );
				g = 17 * ( ( bigint >> 8 ) & 15 );
				b = 17 * ( ( bigint >> 4 ) & 15 );
				a = 17 * ( bigint & 15 );
			} else if ( color.length === 6 ) {
				r = ( bigint >> 16 ) & 255;
				g = ( bigint >> 8 ) & 255;
				b = bigint & 255;
				a = 255;
			} else if ( color.length === 8 ) {
				r = ( bigint >> 24 ) & 255;
				g = ( bigint >> 16 ) & 255;
				b = ( bigint >> 8 ) & 255;
				a = bigint & 255;
			}

			this.r255 = r;
			this.g255 = g;
			this.b255 = b;
			this.a255 = a;

			return this;
		}

		/**
		* Returns color as a hexadecimal string
		* @method getHex
		* @param [alpha=true] {boolean} Whether to include the alpha
		* @return {string} A hexadecimal color such as "13579bdf"
		* @public
		*/
		public getHex( alpha: boolean = true ): string {
			var subStr,
				str = "";

			subStr = this.r255.toString( 16 );
			while( subStr.length < 2 ) {
				subStr = "0" + subStr;
			}
			str += subStr;

			subStr = this.g255.toString( 16 );
			while( subStr.length < 2 ) {
				subStr = "0" + subStr;
			}
			str += subStr;

			subStr = this.b255.toString( 16 );
			while( subStr.length < 2 ) {
				subStr = "0" + subStr;
			}
			str += subStr;

			if ( alpha ) {
				subStr = this.a255.toString( 16 );
				while( subStr.length < 2 ) {
					subStr = "0" + subStr;
				}
				str += subStr;
			}

			return str;
		}

		/**
		* Parses normalized HSV values into the Color.
		* Interprets either normalized values, or H in degrees (0-360)
		* and S and V in % (0-100).
		*
		* Based on algorithms at
		* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
		* @method parseHsv
		* @param h {number} Hue
		* @param s {number} Saturation
		* @param v {number} Value
		* @param a {number} Alpha
		* @return {Kiwi.Utils.Color} This object with the new color set
		* @public
		*/
		public parseHsv( h: number, s: number, v: number, a: number = 1 ):
				Kiwi.Utils.Color {
			var r, g, b, i, f, p, q, t;

			if ( isNaN( h ) || isNaN( s ) || isNaN( v ) || isNaN( a ) ) {
				return this;
			}

			if ( h > 1 ) {
				h /= 360;
			}
			if ( s > 1 ) {
				s /= 100;
			}
			if ( v > 1 ) {
				v /= 100;
			}
			if ( a > 1 ) {
				a /= 255;
			}

			i = Math.floor( h * 6 );
			f = h * 6 - i;
			p = v * ( 1 - s );
			q = v * ( 1 - f * s );
			t = v * ( 1 - (1 - f ) * s );

			switch ( i % 6 ) {
				case 0:
					r = v;
					g = t;
					b = p;
					break;
				case 1:
					r = q;
					g = v;
					b = p;
					break;
				case 2:
					r = p;
					g = v;
					b = t;
					break;
				case 3:
					r = p;
					g = q;
					b = v;
					break;
				case 4:
					r = t;
					g = p;
					b = v;
					break;
				case 5:
					r = v;
					g = p;
					b = q;
					break;
			}

			this._r = r;
			this._g = g;
			this._b = b;
			this._a = a;

			return this;
		}

		/**
		* Returns HSV value of the Color.
		* Based on algorithms at
		* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
		* @method getHsva
		* @return {object} Object with normalized h, s, v, a properties.
		*/
		public getHsva(): any {
			var h, s, v, d,
				r = this._r,
				g = this._g,
				b = this._b,
				max = Math.max( r, g, b ),
				min = Math.min( r, g, b );

			h = max;
			s = max;
			v = max;

			d = max - min;
			s = max === 0 ? 0 : d / max;

			if ( max === min ) {

				// Achromatic
				h = 0;
			} else {
				switch( max ) {
					case r:
						h = ( g - b ) / d + ( g < b ?  6 : 0 );
						break;
					case g:
						h = ( b - r ) / d + 2;
						break;
					case b:
						h = ( r - g ) / d + 4;
						break;
				}
				h /= 6;
			}

			return { h: h, s: s, v: v, a: this._a };
		}


		/**
		* Parses HSL value onto the Color.
		* Interprets either normalized values, or H in degrees (0-360)
		* and S and L in % (0-100).
		* 
		* Based on algorithms at
		* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
		* @method parseHsl
		* @param h {number} Hue
		* @param s {number} Saturation
		* @param l {number} Lightness
		* @param a {number} Alpha
		* @return {Kiwi.Utils.Color} This object with the new color set
		* @public
		*/
		public parseHsl( h: number, s: number, l: number, a: number = 1 ):
				Kiwi.Utils.Color {
			var q, p,
				r = this._r,
				g = this._g,
				b = this._b;

			// Sanitize values

			if ( isNaN( h ) || isNaN( s ) || isNaN( l ) || isNaN( a ) ) {
				return this;
			}

			if ( h > 1 ) {
				h /= 360;
			}
			if ( s > 1 ) {
				s /= 100;
			}
			if ( l > 1 ) {
				l /= 100;
			}
			if ( a > 1 ) {
				a /= 255;
			}

			if ( s === 0 ) {

				// Achromatic
				r = l;
				g = l;
				b = l;
			} else {
				q = l < 0.5 ? l * ( 1 + s) : l + s - l * s;
				p = 2 * l - q;
				r = this._hue2rgb( p, q, h + 1 / 3 );
				g = this._hue2rgb( p, q, h );
				b = this._hue2rgb( p, q, h - 1 / 3 );
			}

			this._r = r;
			this._g = g;
			this._b = b;
			this._a = a;

			return this;
		}


		/**
		* Returns HSL value of the Color.
		* Based on algorithms at
		* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
		* @method getHsla
		* @return {object} Object with normalized h, s, l, a properties.
		* @public
		*/
		public getHsla(): any {
			var d,
				r = this._r,
				g = this._g,
				b = this._b,
				max = Math.max( r, g, b ),
				min = Math.min( r, g, b ),
				h = ( max + min ) / 2,
				s = ( max + min ) / 2,
				l = ( max + min ) / 2;

			if ( max == min ) {

				// Achromatic
				h = 0;
				s = 0;
			} else {
				d = max - min;
				s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min );
				switch( max ) {
					case r:
						h = ( g - b ) / d + ( g < b ? 6 : 0 );
						break;
					case g:
						h = ( b - r ) / d + 2;
						break;
					case b:
						h = ( r - g ) / d + 4;
						break;
				}
				h /= 6;
			}

			return { h: h, s: s, l: l, a: this._a };
		}


		/**
		* Method used for computing HSL values.
		* Based on algorithms at
		* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
		* @method _hue2rgb
		* @param p {number}
		* @param q {number}
		* @param t {number}
		* @return number
		* @private
		*/
		private _hue2rgb( p: number, q: number, t: number ): number {
			if ( t < 0 ) {
				t += 1;
			}
			if ( t > 1 ) {
				t -= 1;
			}
			if ( t < 1 / 6 ) {
				return p + ( q - p ) * 6 * t;
			}
			if ( t < 1 / 2 ) {
				return q;
			}
			if ( t < 2 / 3 ) {
				return p + ( q - p ) * ( 2 / 3 - t ) * 6;
			}
			return p;
		}

	}
}