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 };