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