1 /**
  2  * Returns the {@link pv.Color} for the specified color format string. Colors
  3  * may have an associated opacity, or alpha channel. Color formats are specified
  4  * by CSS Color Modular Level 3, using either in RGB or HSL color space. For
  5  * example:<ul>
  6  *
  7  * <li>#f00 // #rgb
  8  * <li>#ff0000 // #rrggbb
  9  * <li>rgb(255, 0, 0)
 10  * <li>rgb(100%, 0%, 0%)
 11  * <li>hsl(0, 100%, 50%)
 12  * <li>rgba(0, 0, 255, 0.5)
 13  * <li>hsla(120, 100%, 50%, 1)
 14  *
 15  * </ul>The SVG 1.0 color keywords names are also supported, such as "aliceblue"
 16  * and "yellowgreen". The "transparent" keyword is supported for fully-
 17  * transparent black.
 18  *
 19  * <p>If the <tt>format</tt> argument is already an instance of <tt>Color</tt>,
 20  * the argument is returned with no further processing.
 21  *
 22  * @param {string} format the color specification string, such as "#f00".
 23  * @returns {pv.Color} the corresponding <tt>Color</tt>.
 24  * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color
 25  * keywords</a>
 26  * @see <a href="http://www.w3.org/TR/css3-color/">CSS3 color module</a>
 27  */
 28 pv.color = function(format) {
 29   if (format.rgb) return format.rgb();
 30 
 31   /* Handle hsl, rgb. */
 32   var m1 = /([a-z]+)\((.*)\)/i.exec(format);
 33   if (m1) {
 34     var m2 = m1[2].split(","), a = 1;
 35     switch (m1[1]) {
 36       case "hsla":
 37       case "rgba": {
 38         a = parseFloat(m2[3]);
 39         if (!a) return pv.Color.transparent;
 40         break;
 41       }
 42     }
 43     switch (m1[1]) {
 44       case "hsla":
 45       case "hsl": {
 46         var h = parseFloat(m2[0]), // degrees
 47             s = parseFloat(m2[1]) / 100, // percentage
 48             l = parseFloat(m2[2]) / 100; // percentage
 49         return (new pv.Color.Hsl(h, s, l, a)).rgb();
 50       }
 51       case "rgba":
 52       case "rgb": {
 53         function parse(c) { // either integer or percentage
 54           var f = parseFloat(c);
 55           return (c[c.length - 1] == '%') ? Math.round(f * 2.55) : f;
 56         }
 57         var r = parse(m2[0]), g = parse(m2[1]), b = parse(m2[2]);
 58         return pv.rgb(r, g, b, a);
 59       }
 60     }
 61   }
 62 
 63   /* Named colors. */
 64   var named = pv.Color.names[format];
 65   if (named) return named;
 66 
 67   /* Hexadecimal colors: #rgb and #rrggbb. */
 68   if (format.charAt(0) == "#") {
 69     var r, g, b;
 70     if (format.length == 4) {
 71       r = format.charAt(1); r += r;
 72       g = format.charAt(2); g += g;
 73       b = format.charAt(3); b += b;
 74     } else if (format.length == 7) {
 75       r = format.substring(1, 3);
 76       g = format.substring(3, 5);
 77       b = format.substring(5, 7);
 78     }
 79     return pv.rgb(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1);
 80   }
 81 
 82   /* Otherwise, pass-through unsupported colors. */
 83   return new pv.Color(format, 1);
 84 };
 85 
 86 /**
 87  * Constructs a color with the specified color format string and opacity. This
 88  * constructor should not be invoked directly; use {@link pv.color} instead.
 89  *
 90  * @class Represents an abstract (possibly translucent) color. The color is
 91  * divided into two parts: the <tt>color</tt> attribute, an opaque color format
 92  * string, and the <tt>opacity</tt> attribute, a float in [0, 1]. The color
 93  * space is dependent on the implementing class; all colors support the
 94  * {@link #rgb} method to convert to RGB color space for interpolation.
 95  *
 96  * <p>See also the <a href="../../api/Color.html">Color guide</a>.
 97  *
 98  * @param {string} color an opaque color format string, such as "#f00".
 99  * @param {number} opacity the opacity, in [0,1].
100  * @see pv.color
101  */
102 pv.Color = function(color, opacity) {
103   /**
104    * An opaque color format string, such as "#f00".
105    *
106    * @type string
107    * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color
108    * keywords</a>
109    * @see <a href="http://www.w3.org/TR/css3-color/">CSS3 color module</a>
110    */
111   this.color = color;
112 
113   /**
114    * The opacity, a float in [0, 1].
115    *
116    * @type number
117    */
118   this.opacity = opacity;
119 };
120 
121 /**
122  * Returns a new color that is a brighter version of this color. The behavior of
123  * this method may vary slightly depending on the underlying color space.
124  * Although brighter and darker are inverse operations, the results of a series
125  * of invocations of these two methods might be inconsistent because of rounding
126  * errors.
127  *
128  * @param [k] {number} an optional scale factor; defaults to 1.
129  * @see #darker
130  * @returns {pv.Color} a brighter color.
131  */
132 pv.Color.prototype.brighter = function(k) {
133   return this.rgb().brighter(k);
134 };
135 
136 /**
137  * Returns a new color that is a brighter version of this color. The behavior of
138  * this method may vary slightly depending on the underlying color space.
139  * Although brighter and darker are inverse operations, the results of a series
140  * of invocations of these two methods might be inconsistent because of rounding
141  * errors.
142  *
143  * @param [k] {number} an optional scale factor; defaults to 1.
144  * @see #brighter
145  * @returns {pv.Color} a darker color.
146  */
147 pv.Color.prototype.darker = function(k) {
148   return this.rgb().darker(k);
149 };
150 
151 /**
152  * Constructs a new RGB color with the specified channel values.
153  *
154  * @param {number} r the red channel, an integer in [0,255].
155  * @param {number} g the green channel, an integer in [0,255].
156  * @param {number} b the blue channel, an integer in [0,255].
157  * @param {number} [a] the alpha channel, a float in [0,1].
158  * @returns pv.Color.Rgb
159  */
160 pv.rgb = function(r, g, b, a) {
161   return new pv.Color.Rgb(r, g, b, (arguments.length == 4) ? a : 1);
162 };
163 
164 /**
165  * Constructs a new RGB color with the specified channel values.
166  *
167  * @class Represents a color in RGB space.
168  *
169  * @param {number} r the red channel, an integer in [0,255].
170  * @param {number} g the green channel, an integer in [0,255].
171  * @param {number} b the blue channel, an integer in [0,255].
172  * @param {number} a the alpha channel, a float in [0,1].
173  * @extends pv.Color
174  */
175 pv.Color.Rgb = function(r, g, b, a) {
176   pv.Color.call(this, a ? ("rgb(" + r + "," + g + "," + b + ")") : "none", a);
177 
178   /**
179    * The red channel, an integer in [0, 255].
180    *
181    * @type number
182    */
183   this.r = r;
184 
185   /**
186    * The green channel, an integer in [0, 255].
187    *
188    * @type number
189    */
190   this.g = g;
191 
192   /**
193    * The blue channel, an integer in [0, 255].
194    *
195    * @type number
196    */
197   this.b = b;
198 
199   /**
200    * The alpha channel, a float in [0, 1].
201    *
202    * @type number
203    */
204   this.a = a;
205 };
206 pv.Color.Rgb.prototype = pv.extend(pv.Color);
207 
208 /**
209  * Constructs a new RGB color with the same green, blue and alpha channels as
210  * this color, with the specified red channel.
211  *
212  * @param {number} r the red channel, an integer in [0,255].
213  */
214 pv.Color.Rgb.prototype.red = function(r) {
215   return pv.rgb(r, this.g, this.b, this.a);
216 };
217 
218 /**
219  * Constructs a new RGB color with the same red, blue and alpha channels as this
220  * color, with the specified green channel.
221  *
222  * @param {number} g the green channel, an integer in [0,255].
223  */
224 pv.Color.Rgb.prototype.green = function(g) {
225   return pv.rgb(this.r, g, this.b, this.a);
226 };
227 
228 /**
229  * Constructs a new RGB color with the same red, green and alpha channels as
230  * this color, with the specified blue channel.
231  *
232  * @param {number} b the blue channel, an integer in [0,255].
233  */
234 pv.Color.Rgb.prototype.blue = function(b) {
235   return pv.rgb(this.r, this.g, b, this.a);
236 };
237 
238 /**
239  * Constructs a new RGB color with the same red, green and blue channels as this
240  * color, with the specified alpha channel.
241  *
242  * @param {number} a the alpha channel, a float in [0,1].
243  */
244 pv.Color.Rgb.prototype.alpha = function(a) {
245   return pv.rgb(this.r, this.g, this.b, a);
246 };
247 
248 /**
249  * Returns the RGB color equivalent to this color. This method is abstract and
250  * must be implemented by subclasses.
251  *
252  * @returns {pv.Color.Rgb} an RGB color.
253  * @function
254  * @name pv.Color.prototype.rgb
255  */
256 
257 /**
258  * Returns this.
259  *
260  * @returns {pv.Color.Rgb} this.
261  */
262 pv.Color.Rgb.prototype.rgb = function() { return this; };
263 
264 /**
265  * Returns a new color that is a brighter version of this color. This method
266  * applies an arbitrary scale factor to each of the three RGB components of this
267  * color to create a brighter version of this color. Although brighter and
268  * darker are inverse operations, the results of a series of invocations of
269  * these two methods might be inconsistent because of rounding errors.
270  *
271  * @param [k] {number} an optional scale factor; defaults to 1.
272  * @see #darker
273  * @returns {pv.Color.Rgb} a brighter color.
274  */
275 pv.Color.Rgb.prototype.brighter = function(k) {
276   k = Math.pow(0.7, arguments.length ? k : 1);
277   var r = this.r, g = this.g, b = this.b, i = 30;
278   if (!r && !g && !b) return pv.rgb(i, i, i, this.a);
279   if (r && (r < i)) r = i;
280   if (g && (g < i)) g = i;
281   if (b && (b < i)) b = i;
282   return pv.rgb(
283       Math.min(255, Math.floor(r / k)),
284       Math.min(255, Math.floor(g / k)),
285       Math.min(255, Math.floor(b / k)),
286       this.a);
287 };
288 
289 /**
290  * Returns a new color that is a darker version of this color. This method
291  * applies an arbitrary scale factor to each of the three RGB components of this
292  * color to create a darker version of this color. Although brighter and darker
293  * are inverse operations, the results of a series of invocations of these two
294  * methods might be inconsistent because of rounding errors.
295  *
296  * @param [k] {number} an optional scale factor; defaults to 1.
297  * @see #brighter
298  * @returns {pv.Color.Rgb} a darker color.
299  */
300 pv.Color.Rgb.prototype.darker = function(k) {
301   k = Math.pow(0.7, arguments.length ? k : 1);
302   return pv.rgb(
303       Math.max(0, Math.floor(k * this.r)),
304       Math.max(0, Math.floor(k * this.g)),
305       Math.max(0, Math.floor(k * this.b)),
306       this.a);
307 };
308 
309 /**
310  * Constructs a new HSL color with the specified values.
311  *
312  * @param {number} h the hue, an integer in [0, 360].
313  * @param {number} s the saturation, a float in [0, 1].
314  * @param {number} l the lightness, a float in [0, 1].
315  * @param {number} [a] the opacity, a float in [0, 1].
316  * @returns pv.Color.Hsl
317  */
318 pv.hsl = function(h, s, l, a) {
319   return new pv.Color.Hsl(h, s, l,  (arguments.length == 4) ? a : 1);
320 };
321 
322 /**
323  * Constructs a new HSL color with the specified values.
324  *
325  * @class Represents a color in HSL space.
326  *
327  * @param {number} h the hue, an integer in [0, 360].
328  * @param {number} s the saturation, a float in [0, 1].
329  * @param {number} l the lightness, a float in [0, 1].
330  * @param {number} a the opacity, a float in [0, 1].
331  * @extends pv.Color
332  */
333 pv.Color.Hsl = function(h, s, l, a) {
334   pv.Color.call(this, "hsl(" + h + "," + (s * 100) + "%," + (l * 100) + "%)", a);
335 
336   /**
337    * The hue, an integer in [0, 360].
338    *
339    * @type number
340    */
341   this.h = h;
342 
343   /**
344    * The saturation, a float in [0, 1].
345    *
346    * @type number
347    */
348   this.s = s;
349 
350   /**
351    * The lightness, a float in [0, 1].
352    *
353    * @type number
354    */
355   this.l = l;
356 
357   /**
358    * The opacity, a float in [0, 1].
359    *
360    * @type number
361    */
362   this.a = a;
363 };
364 pv.Color.Hsl.prototype = pv.extend(pv.Color);
365 
366 /**
367  * Constructs a new HSL color with the same saturation, lightness and alpha as
368  * this color, and the specified hue.
369  *
370  * @param {number} h the hue, an integer in [0, 360].
371  */
372 pv.Color.Hsl.prototype.hue = function(h) {
373   return pv.hsl(h, this.s, this.l, this.a);
374 };
375 
376 /**
377  * Constructs a new HSL color with the same hue, lightness and alpha as this
378  * color, and the specified saturation.
379  *
380  * @param {number} s the saturation, a float in [0, 1].
381  */
382 pv.Color.Hsl.prototype.saturation = function(s) {
383   return pv.hsl(this.h, s, this.l, this.a);
384 };
385 
386 /**
387  * Constructs a new HSL color with the same hue, saturation and alpha as this
388  * color, and the specified lightness.
389  *
390  * @param {number} l the lightness, a float in [0, 1].
391  */
392 pv.Color.Hsl.prototype.lightness = function(l) {
393   return pv.hsl(this.h, this.s, l, this.a);
394 };
395 
396 /**
397  * Constructs a new HSL color with the same hue, saturation and lightness as
398  * this color, and the specified alpha.
399  *
400  * @param {number} a the opacity, a float in [0, 1].
401  */
402 pv.Color.Hsl.prototype.alpha = function(a) {
403   return pv.hsl(this.h, this.s, this.l, a);
404 };
405 
406 /**
407  * Returns the RGB color equivalent to this HSL color.
408  *
409  * @returns {pv.Color.Rgb} an RGB color.
410  */
411 pv.Color.Hsl.prototype.rgb = function() {
412   var h = this.h, s = this.s, l = this.l;
413 
414   /* Some simple corrections for h, s and l. */
415   h = h % 360; if (h < 0) h += 360;
416   s = Math.max(0, Math.min(s, 1));
417   l = Math.max(0, Math.min(l, 1));
418 
419   /* From FvD 13.37, CSS Color Module Level 3 */
420   var m2 = (l <= .5) ? (l * (1 + s)) : (l + s - l * s);
421   var m1 = 2 * l - m2;
422   function v(h) {
423     if (h > 360) h -= 360;
424     else if (h < 0) h += 360;
425     if (h < 60) return m1 + (m2 - m1) * h / 60;
426     if (h < 180) return m2;
427     if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
428     return m1;
429   }
430   function vv(h) {
431     return Math.round(v(h) * 255);
432   }
433 
434   return pv.rgb(vv(h + 120), vv(h), vv(h - 120), this.a);
435 };
436 
437 /**
438  * @private SVG color keywords, per CSS Color Module Level 3.
439  *
440  * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color
441  * keywords</a>
442  */
443 pv.Color.names = {
444   aliceblue: "#f0f8ff",
445   antiquewhite: "#faebd7",
446   aqua: "#00ffff",
447   aquamarine: "#7fffd4",
448   azure: "#f0ffff",
449   beige: "#f5f5dc",
450   bisque: "#ffe4c4",
451   black: "#000000",
452   blanchedalmond: "#ffebcd",
453   blue: "#0000ff",
454   blueviolet: "#8a2be2",
455   brown: "#a52a2a",
456   burlywood: "#deb887",
457   cadetblue: "#5f9ea0",
458   chartreuse: "#7fff00",
459   chocolate: "#d2691e",
460   coral: "#ff7f50",
461   cornflowerblue: "#6495ed",
462   cornsilk: "#fff8dc",
463   crimson: "#dc143c",
464   cyan: "#00ffff",
465   darkblue: "#00008b",
466   darkcyan: "#008b8b",
467   darkgoldenrod: "#b8860b",
468   darkgray: "#a9a9a9",
469   darkgreen: "#006400",
470   darkgrey: "#a9a9a9",
471   darkkhaki: "#bdb76b",
472   darkmagenta: "#8b008b",
473   darkolivegreen: "#556b2f",
474   darkorange: "#ff8c00",
475   darkorchid: "#9932cc",
476   darkred: "#8b0000",
477   darksalmon: "#e9967a",
478   darkseagreen: "#8fbc8f",
479   darkslateblue: "#483d8b",
480   darkslategray: "#2f4f4f",
481   darkslategrey: "#2f4f4f",
482   darkturquoise: "#00ced1",
483   darkviolet: "#9400d3",
484   deeppink: "#ff1493",
485   deepskyblue: "#00bfff",
486   dimgray: "#696969",
487   dimgrey: "#696969",
488   dodgerblue: "#1e90ff",
489   firebrick: "#b22222",
490   floralwhite: "#fffaf0",
491   forestgreen: "#228b22",
492   fuchsia: "#ff00ff",
493   gainsboro: "#dcdcdc",
494   ghostwhite: "#f8f8ff",
495   gold: "#ffd700",
496   goldenrod: "#daa520",
497   gray: "#808080",
498   green: "#008000",
499   greenyellow: "#adff2f",
500   grey: "#808080",
501   honeydew: "#f0fff0",
502   hotpink: "#ff69b4",
503   indianred: "#cd5c5c",
504   indigo: "#4b0082",
505   ivory: "#fffff0",
506   khaki: "#f0e68c",
507   lavender: "#e6e6fa",
508   lavenderblush: "#fff0f5",
509   lawngreen: "#7cfc00",
510   lemonchiffon: "#fffacd",
511   lightblue: "#add8e6",
512   lightcoral: "#f08080",
513   lightcyan: "#e0ffff",
514   lightgoldenrodyellow: "#fafad2",
515   lightgray: "#d3d3d3",
516   lightgreen: "#90ee90",
517   lightgrey: "#d3d3d3",
518   lightpink: "#ffb6c1",
519   lightsalmon: "#ffa07a",
520   lightseagreen: "#20b2aa",
521   lightskyblue: "#87cefa",
522   lightslategray: "#778899",
523   lightslategrey: "#778899",
524   lightsteelblue: "#b0c4de",
525   lightyellow: "#ffffe0",
526   lime: "#00ff00",
527   limegreen: "#32cd32",
528   linen: "#faf0e6",
529   magenta: "#ff00ff",
530   maroon: "#800000",
531   mediumaquamarine: "#66cdaa",
532   mediumblue: "#0000cd",
533   mediumorchid: "#ba55d3",
534   mediumpurple: "#9370db",
535   mediumseagreen: "#3cb371",
536   mediumslateblue: "#7b68ee",
537   mediumspringgreen: "#00fa9a",
538   mediumturquoise: "#48d1cc",
539   mediumvioletred: "#c71585",
540   midnightblue: "#191970",
541   mintcream: "#f5fffa",
542   mistyrose: "#ffe4e1",
543   moccasin: "#ffe4b5",
544   navajowhite: "#ffdead",
545   navy: "#000080",
546   oldlace: "#fdf5e6",
547   olive: "#808000",
548   olivedrab: "#6b8e23",
549   orange: "#ffa500",
550   orangered: "#ff4500",
551   orchid: "#da70d6",
552   palegoldenrod: "#eee8aa",
553   palegreen: "#98fb98",
554   paleturquoise: "#afeeee",
555   palevioletred: "#db7093",
556   papayawhip: "#ffefd5",
557   peachpuff: "#ffdab9",
558   peru: "#cd853f",
559   pink: "#ffc0cb",
560   plum: "#dda0dd",
561   powderblue: "#b0e0e6",
562   purple: "#800080",
563   red: "#ff0000",
564   rosybrown: "#bc8f8f",
565   royalblue: "#4169e1",
566   saddlebrown: "#8b4513",
567   salmon: "#fa8072",
568   sandybrown: "#f4a460",
569   seagreen: "#2e8b57",
570   seashell: "#fff5ee",
571   sienna: "#a0522d",
572   silver: "#c0c0c0",
573   skyblue: "#87ceeb",
574   slateblue: "#6a5acd",
575   slategray: "#708090",
576   slategrey: "#708090",
577   snow: "#fffafa",
578   springgreen: "#00ff7f",
579   steelblue: "#4682b4",
580   tan: "#d2b48c",
581   teal: "#008080",
582   thistle: "#d8bfd8",
583   tomato: "#ff6347",
584   turquoise: "#40e0d0",
585   violet: "#ee82ee",
586   wheat: "#f5deb3",
587   white: "#ffffff",
588   whitesmoke: "#f5f5f5",
589   yellow: "#ffff00",
590   yellowgreen: "#9acd32",
591   transparent: pv.Color.transparent = pv.rgb(0, 0, 0, 0)
592 };
593 
594 /* Initialized named colors. */
595 (function() {
596   var names = pv.Color.names;
597   for (var name in names) names[name] = pv.color(names[name]);
598 })();
599