1 /** 2 * @fileoverview 3 * ColorRGB, ColorHSV 4 * 5 * For color conversion and parsing of color strings to colors. 6 * 7 * @author Garrett Smith 8 */ 9 APE.namespace("APE.color"); 10 11 /** ColorRGB 12 * @constructor 13 * @param {uint} r 0-255 14 * @param {uint} g 0-255 15 * @param {uint} b 0-255 16 */ 17 APE.color.ColorRGB = function(r, g, b) { 18 this.r = r; 19 this.g = g; 20 this.b = b; 21 }; 22 23 /** Returns a ColorRGB based on a number. 24 * @param {uint} number from 0 - 0xFFFFFF 25 * @static 26 */ 27 APE.color.ColorRGB.fromNumber = function(n) { 28 return new APE.color.ColorRGB((0xFF0000 & n) >> 16, (0xFF00 & n) >> 8, 0xFF & n); 29 }; 30 31 /** @return {ColorRGB} based on a hex string: 32 * #FFFFFF or #FFF 33 * @static 34 */ 35 APE.color.ColorRGB.fromHexString = function(hex) { 36 if(!this.hexPattern.test(hex)) 37 throw Error("ColorRGB.fromHexString(hex) invalid input: " + hex); 38 var n = parseInt(hex.substring(1), 16), 39 r, g, b, 40 ColorRGB = APE.color.ColorRGB; 41 42 if(hex.length == 4) { 43 r = n & 0xF00, g = n & 0x0F0, b = n & 0x00F; 44 return new ColorRGB( 45 (r >> 4) + (r >> 8), 46 g + (g >> 4), 47 (b << 4) + b 48 ); 49 } 50 return ColorRGB.fromNumber(n); 51 }; 52 53 /** 54 * @type {RegExp} matches 3 or 6 digit color hex 55 * @static 56 */ 57 APE.color.ColorRGB.hexPattern = /(?:#)[0-9a-f]{3,6}/i; 58 /** 59 * @type {RegExp} matches an rgb(i, i, i) String with optional percent signs. 60 * @static 61 */ 62 APE.color.ColorRGB.rgbPattern = /rgb\(([\d]{1,3})\,\s?([\d]{1,3})\,\s?([\d]{1,3})\)/; 63 64 /** 65 * @static 66 * @return {ColorRGB} color based on the input. 67 * If the input is invalid, it creates an invalid Color. 68 * use isValid to verify. 69 */ 70 APE.color.ColorRGB.fromString = function(inp) { 71 var ColorRGB = APE.color.ColorRGB; 72 return (ColorRGB.hexPattern.test(inp) ? ColorRGB.fromHexString(inp) : ColorRGB.fromRgbString(inp)); 73 }; 74 75 /** 76 * @param {String} rgbString 77 * @return {ColorRGB} object representing rgbString is returned (possibly invalid); 78 * @static 79 */ 80 APE.color.ColorRGB.fromRgbString = function(rgbString) { 81 82 var ColorRGB = APE.color.ColorRGB, 83 rgb = rgbString.match(ColorRGB.rgbPattern); 84 if(rgb != null) { 85 return new ColorRGB(rgb[1], rgb[2], rgb[3]); 86 } 87 return new ColorRGB(); 88 }; 89 90 /** Takes a midpoint color between two colors. Useful for animation. 91 * @param {ColorRGB} startColor the color to transition from. 92 * @param {ColorRGB} endColor the color to transition to. 93 * @param {ufloat} rationalValue ratio [0..1] of endColor:startColor color for result. 94 * @param {ColorRGB} [mixedColor] - a color can be passed in for reuse (this is useful for 95 * animation, to avoid creation of many objects). 96 * 97 * @return APE.color.ColorRGB new color that is a blend of 98 * 99 * rationalValue/1 new color and 1-rationalValue startColor. 100 */ 101 APE.color.ColorRGB.blend = function(startColor, endColor, rationalValue, mixedColor) { 102 var inverse = (rationalValue > 1 ? 0 : 1 - rationalValue), 103 104 r = startColor.r * inverse + endColor.r * rationalValue, 105 g = startColor.g * inverse + endColor.g * rationalValue, 106 b = startColor.b * inverse + endColor.b * rationalValue; 107 108 // Math.abs.floor: (x ^ (x >> 31)) - (x >> 31); 109 // converts result to int (floor). 110 r = (r ^ (r >> 31)) - (r >> 31); 111 g = (g ^ (g >> 31)) - (g >> 31); 112 b = (b ^ (b >> 31)) - (b >> 31); 113 114 if(mixedColor && 'r'in mixedColor) { 115 mixedColor.r = r; 116 mixedColor.g = g; 117 mixedColor.b = b; 118 return mixedColor; 119 } 120 return new this(r, g, b); 121 }; 122 123 APE.color.ColorRGB.prototype = { 124 125 r : 0xff, g : 0xff, b : 0xff, 126 127 /** 128 * @return {ColorHSV} color object. 129 */ 130 toHSV : function() { 131 132 var max = Math.max(this.r, this.g, this.b), 133 min = Math.min(this.r, this.g, this.b), 134 135 s = 0, 136 h = 0; 137 138 if(max > min) { 139 switch(max) { 140 case this.r : 141 h = (this.g - this.b) / (max-min); 142 break; 143 case this.g : 144 h = 2 + ((this.b - this.r) / (max-min)); 145 break; 146 case this.b : 147 h = 4 + ((this.r - this.g) / (max-min)); 148 break; 149 } 150 s = (max - min) / max; 151 } 152 h *= 60; 153 if(h < 0) 154 h += 360; 155 156 return new APE.color.ColorHSV(h, s, max/255); 157 }, 158 159 /** @return {String} helpful debugging info. */ 160 toString : function() { 161 return "rgb(" + this.r 162 + ", " + this.g 163 + ", " + this.b + ")"; 164 }, 165 166 /** 167 * @return {String} six digit hex string like #336699 168 */ 169 toHexString : function() { 170 return "#" + toHexByte(this.r) + toHexByte(this.g) + toHexByte(this.b); 171 172 function toHexByte(bite) { 173 var hex = bite.toString(16); 174 return (hex.length == 2 ? hex : "0" + hex); 175 } 176 }, 177 178 /** 179 * @return {boolean} true if this r,g,b equal other's r,g,b. 180 */ 181 equals : function(c) { 182 return (this.r == c.r) && (this.b == c.b) && (this.g == c.g); 183 }, 184 185 /** @return {boolean} if r,g,b are all numbers 0-255. 186 * invalid colors can be used for initialization and caching. 187 */ 188 isValid : function() { 189 return validComponent(this.r) && validComponent(this.g) && validComponent(this.b); 190 function validComponent(c) { 191 return isFinite(c) && c >= 0 && c <= 255; 192 } 193 }, 194 195 /** 196 * @return {Number} numerical representation of the Color; like hex value, but in decimal. 197 */ 198 valueOf : function() { 199 return (this.r << 16) + (this.g << 8) + this.b; 200 } 201 }; 202 203 /** 204 * @constructor 205 * @param {uint} h hue: 0-360 206 * @param {uint} s hue: 0-1 207 * @param {uint} v hue: 0-1 208 */ 209 APE.color.ColorHSV = function(h, s, v) { 210 this.h = h; 211 this.s = s; 212 this.v = v; 213 }; 214 215 /** 216 * @return {ColorRGB} a fully saturated, bright-as-possible color for the hue; 217 * @static 218 */ 219 APE.color.ColorHSV.rgbForHue = function(hue) { 220 if(hue === 360) hue = 0; 221 return new APE.color.ColorHSV(hue, 1, 1).toRGB(); 222 }; 223 224 APE.color.ColorHSV.prototype = { 225 226 h : 360, s: 1.0, v : 1.0, 227 228 /** 229 * @return {ColorRGB} Converts to other colorspace. 230 * this.toRGB().toHSV() should return an equivalent color 231 */ 232 toRGB : function() { 233 var H = this.h/360, 234 S = this.s, 235 V = this.v; 236 237 var R, G, B; 238 239 if ( S == 0 ) { //HSV values = From 0 to 1 240 R = G = B = V; 241 } 242 else { 243 H *= 6; 244 245 // Math.floor: Right shift by 0, faster than Math.floor. 246 var i = H >> 0, 247 var1 = V * ( 1 - S ), 248 var2 = V * ( 1 - S * ( H - i ) ), 249 var3 = V * ( 1 - S * ( 1 - ( H - i ) ) ); 250 251 switch(i) { 252 case 0 : 253 R = V; G = var3 ; B = var1; 254 break; 255 case 1 : 256 R = var2; G = V; B = var1; 257 break; 258 case 2 : 259 R = var1 ; G = V; B = var3; 260 break; 261 case 3 : 262 R = var1 ; G = var2 ; B = V; 263 break; 264 case 4 : 265 R = var3 ; G = var1 ; B = V; 266 break; 267 default : 268 R = V; G = var1 ; B = var2; 269 } 270 } 271 return new APE.color.ColorRGB( R*255 >> 0, G*255 >> 0, B*255 >> 0); 272 }, 273 274 /** @return {String} helpful debugging info. */ 275 toString : function() { 276 return "[ " + this.h.toFixed(0) 277 + ", " + this.s.toFixed(2) 278 + ", " + this.v.toFixed(2) + " ]"; 279 } 280 };