1 /**
  2  * Functions for color conversions. Based on a class to parse color values by Stoyan Stefanov <sstoo@gmail.com>
  3  * @see http://www.phpied.com/rgb-color-parser-in-javascript/
  4  */
  5 
  6 /**
  7  * Converts a valid HTML/CSS color string into a rgb value array. This is the base
  8  * function for the following wrapper functions which only adjust the output to
  9  * different flavors like an object, string or hex values.
 10  * @parameter {string} color_string A valid HTML or CSS styled color value, e.g. #12ab21, #abc, black, or rgb(12, 132, 233) <strong>or</string>
 11  * @parameter {array} color_array Array containing three color values either from 0.0 to 1.0 or from 0 to 255. They will be interpreted as red, green, and blue values <strong>OR</strong>
 12  * @parameter {number} r,g,b Three color values r, g, and b like those in the array variant.
 13  * @returns {Array} RGB color values as an array [r, g, b] which component's are between 0 and 255.
 14  */
 15 JXG.rgbParser = function() {
 16     var color_string,
 17         testFloat = false,
 18         i,
 19         r, g, b;
 20 
 21     if(arguments.length === 0) {
 22         return [];
 23     }
 24 
 25     if(arguments.length >= 3) {
 26         arguments[0] = [arguments[0], arguments[1], arguments[2]];
 27         arguments.length = 1;
 28     }
 29 
 30     color_string = arguments[0];
 31     if(JXG.isArray(color_string)) {
 32         for(i = 0; i < 3; i++) {
 33             testFloat |= /\./.test(arguments[0][i].toString());
 34         }
 35 
 36         for(i = 0; i < 3; i++) {
 37             testFloat &= (arguments[0][i] >= 0.0) & (arguments[0][i] <= 1.0);
 38         }
 39 
 40         if(testFloat)
 41             return [Math.ceil(arguments[0][0] * 255), Math.ceil(arguments[0][1] * 255), Math.ceil(arguments[0][2] * 255)];
 42         else {
 43             arguments[0].length = 3;
 44             return arguments[0];
 45         }
 46     } else if(typeof arguments[0] === 'string') {
 47         color_string = arguments[0];
 48     }
 49 
 50     // strip any leading #
 51     if (color_string.charAt(0) == '#') { // remove # if any
 52         color_string = color_string.substr(1,6);
 53     }
 54 
 55     color_string = color_string.replace(/ /g,'').toLowerCase();
 56 
 57     // before getting into regexps, try simple matches
 58     // and overwrite the input
 59     var simple_colors = {
 60         aliceblue: 'f0f8ff',
 61         antiquewhite: 'faebd7',
 62         aqua: '00ffff',
 63         aquamarine: '7fffd4',
 64         azure: 'f0ffff',
 65         beige: 'f5f5dc',
 66         bisque: 'ffe4c4',
 67         black: '000000',
 68         blanchedalmond: 'ffebcd',
 69         blue: '0000ff',
 70         blueviolet: '8a2be2',
 71         brown: 'a52a2a',
 72         burlywood: 'deb887',
 73         cadetblue: '5f9ea0',
 74         chartreuse: '7fff00',
 75         chocolate: 'd2691e',
 76         coral: 'ff7f50',
 77         cornflowerblue: '6495ed',
 78         cornsilk: 'fff8dc',
 79         crimson: 'dc143c',
 80         cyan: '00ffff',
 81         darkblue: '00008b',
 82         darkcyan: '008b8b',
 83         darkgoldenrod: 'b8860b',
 84         darkgray: 'a9a9a9',
 85         darkgreen: '006400',
 86         darkkhaki: 'bdb76b',
 87         darkmagenta: '8b008b',
 88         darkolivegreen: '556b2f',
 89         darkorange: 'ff8c00',
 90         darkorchid: '9932cc',
 91         darkred: '8b0000',
 92         darksalmon: 'e9967a',
 93         darkseagreen: '8fbc8f',
 94         darkslateblue: '483d8b',
 95         darkslategray: '2f4f4f',
 96         darkturquoise: '00ced1',
 97         darkviolet: '9400d3',
 98         deeppink: 'ff1493',
 99         deepskyblue: '00bfff',
100         dimgray: '696969',
101         dodgerblue: '1e90ff',
102         feldspar: 'd19275',
103         firebrick: 'b22222',
104         floralwhite: 'fffaf0',
105         forestgreen: '228b22',
106         fuchsia: 'ff00ff',
107         gainsboro: 'dcdcdc',
108         ghostwhite: 'f8f8ff',
109         gold: 'ffd700',
110         goldenrod: 'daa520',
111         gray: '808080',
112         green: '008000',
113         greenyellow: 'adff2f',
114         honeydew: 'f0fff0',
115         hotpink: 'ff69b4',
116         indianred : 'cd5c5c',
117         indigo : '4b0082',
118         ivory: 'fffff0',
119         khaki: 'f0e68c',
120         lavender: 'e6e6fa',
121         lavenderblush: 'fff0f5',
122         lawngreen: '7cfc00',
123         lemonchiffon: 'fffacd',
124         lightblue: 'add8e6',
125         lightcoral: 'f08080',
126         lightcyan: 'e0ffff',
127         lightgoldenrodyellow: 'fafad2',
128         lightgrey: 'd3d3d3',
129         lightgreen: '90ee90',
130         lightpink: 'ffb6c1',
131         lightsalmon: 'ffa07a',
132         lightseagreen: '20b2aa',
133         lightskyblue: '87cefa',
134         lightslateblue: '8470ff',
135         lightslategray: '778899',
136         lightsteelblue: 'b0c4de',
137         lightyellow: 'ffffe0',
138         lime: '00ff00',
139         limegreen: '32cd32',
140         linen: 'faf0e6',
141         magenta: 'ff00ff',
142         maroon: '800000',
143         mediumaquamarine: '66cdaa',
144         mediumblue: '0000cd',
145         mediumorchid: 'ba55d3',
146         mediumpurple: '9370d8',
147         mediumseagreen: '3cb371',
148         mediumslateblue: '7b68ee',
149         mediumspringgreen: '00fa9a',
150         mediumturquoise: '48d1cc',
151         mediumvioletred: 'c71585',
152         midnightblue: '191970',
153         mintcream: 'f5fffa',
154         mistyrose: 'ffe4e1',
155         moccasin: 'ffe4b5',
156         navajowhite: 'ffdead',
157         navy: '000080',
158         oldlace: 'fdf5e6',
159         olive: '808000',
160         olivedrab: '6b8e23',
161         orange: 'ffa500',
162         orangered: 'ff4500',
163         orchid: 'da70d6',
164         palegoldenrod: 'eee8aa',
165         palegreen: '98fb98',
166         paleturquoise: 'afeeee',
167         palevioletred: 'd87093',
168         papayawhip: 'ffefd5',
169         peachpuff: 'ffdab9',
170         peru: 'cd853f',
171         pink: 'ffc0cb',
172         plum: 'dda0dd',
173         powderblue: 'b0e0e6',
174         purple: '800080',
175         red: 'ff0000',
176         rosybrown: 'bc8f8f',
177         royalblue: '4169e1',
178         saddlebrown: '8b4513',
179         salmon: 'fa8072',
180         sandybrown: 'f4a460',
181         seagreen: '2e8b57',
182         seashell: 'fff5ee',
183         sienna: 'a0522d',
184         silver: 'c0c0c0',
185         skyblue: '87ceeb',
186         slateblue: '6a5acd',
187         slategray: '708090',
188         snow: 'fffafa',
189         springgreen: '00ff7f',
190         steelblue: '4682b4',
191         tan: 'd2b48c',
192         teal: '008080',
193         thistle: 'd8bfd8',
194         tomato: 'ff6347',
195         turquoise: '40e0d0',
196         violet: 'ee82ee',
197         violetred: 'd02090',
198         wheat: 'f5deb3',
199         white: 'ffffff',
200         whitesmoke: 'f5f5f5',
201         yellow: 'ffff00',
202         yellowgreen: '9acd32'
203     };
204     color_string = simple_colors[color_string] || color_string;
205     // end of simple type-in colors
206 
207     // array of color definition objects
208     var color_defs = [
209         {
210             re: /^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]{1,3})\s*\)\s*$/,
211             example: ['rgba(123, 234, 45, 0.5)', 'rgba(255,234,245,1.0)'],
212             process: function (bits){
213                 return [
214                     parseInt(bits[1]),
215                     parseInt(bits[2]),
216                     parseInt(bits[3])
217                 ];
218             }
219         },
220         {
221             re: /^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$/,
222             example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
223             process: function (bits){
224                 return [
225                     parseInt(bits[1]),
226                     parseInt(bits[2]),
227                     parseInt(bits[3])
228                 ];
229             }
230         },
231         {
232             re: /^(\w{2})(\w{2})(\w{2})$/,
233             example: ['#00ff00', '336699'],
234             process: function (bits){
235                 return [
236                     parseInt(bits[1], 16),
237                     parseInt(bits[2], 16),
238                     parseInt(bits[3], 16)
239                 ];
240             }
241         },
242         {
243             re: /^(\w{1})(\w{1})(\w{1})$/,
244             example: ['#fb0', 'f0f'],
245             process: function (bits){
246                 return [
247                     parseInt(bits[1] + bits[1], 16),
248                     parseInt(bits[2] + bits[2], 16),
249                     parseInt(bits[3] + bits[3], 16)
250                 ];
251             }
252         }
253     ];
254 
255     // search through the definitions to find a match
256     for (i = 0; i < color_defs.length; i++) {
257         var re = color_defs[i].re,
258             processor = color_defs[i].process,
259             bits = re.exec(color_string),
260             channels;
261         if (bits) {
262             channels = processor(bits);
263             r = channels[0];
264             g = channels[1];
265             b = channels[2];
266         }
267 
268     }
269 
270     if (isNaN(r) || isNaN(g) || isNaN(b)) {
271         return [];
272     }
273 
274     // validate/cleanup values
275     r = (r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r);
276     g = (g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g);
277     b = (b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b);
278 
279     return [r, g, b];
280 };
281 
282 /**
283  * Returns output of JXG.rgbParser as a CSS styled rgb() string.
284  */
285 JXG.rgb2css = function () {
286     var r, g, b;
287     r = JXG.rgbParser.apply(JXG.rgbParser, arguments);
288     g = r[1];
289     b = r[2];
290     r = r[0];
291     return 'rgb(' + r + ', ' + g + ', ' + b + ')';
292 };
293 
294 /**
295  * Returns array returned by JXG.rgbParser as a HTML rgb string.
296  */
297 JXG.rgb2hex = function () {
298     var r, g, b;
299     r = JXG.rgbParser.apply(JXG.rgbParser, arguments);
300     g = r[1];
301     b = r[2];
302     r = r[0];
303     r = r.toString(16);
304     g = g.toString(16);
305     b = b.toString(16);
306     if (r.length == 1) r = '0' + r;
307     if (g.length == 1) g = '0' + g;
308     if (b.length == 1) b = '0' + b;
309     return '#' + r + g + b;
310 };
311 
312 JXG.hex2rgb = function (hex) {
313     var r, g, b;
314     if (hex.charAt(0) == '#')
315         hex = hex.slice(1);
316 
317     r = parseInt(hex.substr(0, 2), 16);
318     g = parseInt(hex.substr(2, 2), 16);
319     b = parseInt(hex.substr(4, 2), 16);
320 
321     return 'rgb(' + r + ', ' + g + ', ' + b + ')';
322 };
323 
324 /**
325 * Converts HSV color to RGB color.
326 * Based on C Code in "Computer Graphics -- Principles and Practice,"
327 * Foley et al, 1996, p. 593.
328 * See also http://www.efg2.com/Lab/Graphics/Colors/HSV.htm  
329 * @param {float} H value between 0 and 360
330 * @param {float} S value between 0.0 (shade of gray) to 1.0 (pure color)
331 * @param {float} V value between 0.0 (black) to 1.0 (white)
332 * @return {string} RGB color string
333 */
334 JXG.hsv2rgb = function(H,S,V) {
335     var R,G,B, f,i,hTemp, p,q,t;
336     H = ((H%360.0)+360.0)%360;
337     if (S==0) {
338         if (isNaN(H) || H < JXG.Math.eps) {
339             R = V;
340             G = V;
341             B = V;
342         } else {
343             return '#ffffff';
344         }
345     } else {
346         if (H>=360) {
347             hTemp = 0.0;
348         } else {
349             hTemp = H;
350         }
351         hTemp = hTemp / 60;     // h is now IN [0,6)
352         i = Math.floor(hTemp);        // largest integer <= h
353         f = hTemp - i;                  // fractional part of h
354         p = V * (1.0 - S);
355         q = V * (1.0 - (S * f));
356         t = V * (1.0 - (S * (1.0 - f)));
357         switch (i) {
358             case 0: R = V; G = t;  B = p; break;
359             case 1: R = q; G = V;  B = p; break;
360             case 2: R = p; G = V;  B = t; break;
361             case 3: R = p; G = q;  B = V; break;
362             case 4: R = t; G = p;  B = V; break;
363             case 5: R = V; G = p;  B = q; break;
364         }
365     }
366     R = Math.round(R*255).toString(16); R = (R.length==2)?R:((R.length==1)?'0'+R:'00');
367     G = Math.round(G*255).toString(16); G = (G.length==2)?G:((G.length==1)?'0'+G:'00');
368     B = Math.round(B*255).toString(16); B = (B.length==2)?B:((B.length==1)?'0'+B:'00');
369     return ['#',R,G,B].join(''); 
370 };
371 
372 /**
373  * Converts r, g, b color to h, s, v.
374  * See http://zach.in.tu-clausthal.de/teaching/cg1_0708/folien/13_color_3_4up.pdf for more information.
375  * @param {number} r Amount of red in color. Number between 0 and 255.
376  * @param {number} g Amount of green. Number between 0 and 255.
377  * @param {number} b Amount of blue. Number between 0 and 255.
378  * @type Object
379  * @return Hashmap containing h,s, and v field.
380  */
381 JXG.rgb2hsv = function() {
382     var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min, stx;
383     r = JXG.rgbParser.apply(JXG.rgbParser, arguments);
384     g = r[1];
385     b = r[2];
386     r = r[0];
387     stx = JXG.Math.Statistics;
388     fr = r/255.;
389     fg = g/255.;
390     fb = b/255.;
391     max = stx.max([r, g, b]);
392     min = stx.min([r, g, b]);
393     fmax = max/255.;
394     fmin = min/255.;
395 
396     v = fmax;
397 
398     s = 0.;
399     if(v>0) {
400         s = (v-fmin)/(v*1.);
401     }
402 
403     h = 1./(fmax-fmin);
404     if(s > 0) {
405         if(max==r)
406             h = (fg-fb)*h;
407         else if(max==g)
408             h = 2 + (fb-fr)*h;
409         else
410             h = 4 + (fr-fg)*h;
411     }
412 
413     h *= 60;
414     if(h < 0)
415         h += 360;
416 
417     if(max==min)
418         h = 0.;
419 
420     return [h, s, v];
421 };
422 
423 
424 /**
425  * Convert RGB color information to LMS color space.
426  * @param {number} r Amount of red in color. Number between 0 and 255.
427  * @param {number} g Amount of green. Number between 0 and 255.
428  * @param {number} b Amount of blue. Number between 0 and 255.
429  * @type Object
430  * @return Hashmap containing the L, M, S cone values.
431  */
432 JXG.rgb2LMS = function() {
433     var r, g, b, l, m, s, ret,
434         // constants
435         matrix = [[0.05059983, 0.08585369, 0.00952420], [0.01893033, 0.08925308, 0.01370054], [0.00292202, 0.00975732, 0.07145979]];
436 
437     r = JXG.rgbParser.apply(JXG.rgbParser, arguments);
438     g = r[1];
439     b = r[2];
440     r = r[0];
441 
442     // de-gamma
443     // Maybe this can be made faster by using a cache
444     r = Math.pow(r, 0.476190476);
445     g = Math.pow(g, 0.476190476);
446     b = Math.pow(b, 0.476190476);
447 
448     l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2];
449     m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2];
450     s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2];
451 
452     ret = [l, m, s];
453     ret.l = l;
454     ret.m = m;
455     ret.s = s;
456 
457     return ret;
458 };
459 /**
460  * Convert color information from LMS to RGB color space.
461  * @param {number} l Amount of l value.
462  * @param {number} m Amount of m value.
463  * @param {number} s Amount of s value.
464  * @type Object
465  * @return Hashmap containing the r, g, b values.
466  */
467 JXG.LMS2rgb = function(l, m, s) {
468     var r, g, b, ret,
469         // constants
470         matrix = [[30.830854, -29.832659, 1.610474], [-6.481468, 17.715578, -2.532642], [-0.375690, -1.199062, 14.273846]];
471 
472     // transform back to rgb
473     r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2];
474     g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2];
475     b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2];
476 
477     // re-gamma, inspired by GIMP modules/display-filter-color-blind.c:
478     // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>,
479     //                         Sven Neumann <sven@gimp.org>,
480     //                         Robert Dougherty <bob@vischeck.com> and
481     //                         Alex Wade <alex@vischeck.com>
482     // This code is an implementation of an algorithm described by Hans Brettel,
483     // Francoise Vienot and John Mollon in the Journal of the Optical Society of
484     // America V14(10), pg 2647. (See http://vischeck.com/ for more info.)
485     var lut_lookup = function (value) {
486         var offset = 127, step = 64;
487 
488         while (step > 0) {
489             if (Math.pow(offset, 0.476190476) > value) {
490                 offset -= step;
491             } else {
492                 if (Math.pow(offset+1, 0.476190476) > value)
493                     return offset;
494 
495                 offset += step;
496             }
497 
498             step /= 2;
499         }
500 
501         /*  the algorithm above can't reach 255  */
502         if (offset == 254 && 13.994955247 < value)
503             return 255;
504 
505         return offset;
506     };
507 
508 
509     r = lut_lookup(r);
510     g = lut_lookup(g);
511     b = lut_lookup(b);
512 
513     ret = [r, g, b];
514     ret.r = r;
515     ret.g = g;
516     ret.b = b;
517 
518     return ret;
519 };
520 
521 /**
522  * Splits a RGBA color value like #112233AA into it's RGB and opacity parts.
523  * @param {String} rgba A RGBA color value
524  * @returns {Array} An array containing the rgb color value in the first and the opacity in the second field.
525  */
526 JXG.rgba2rgbo = function (rgba) {
527     var opacity;
528 
529     if (rgba.length == 9 && rgba.charAt(0) == '#') {
530         opacity = parseInt(rgba.substr(7, 2).toUpperCase(), 16) / 255;
531         rgba = rgba.substr(0, 7);
532     } else {
533         opacity = 1;
534     }
535 
536     return [rgba, opacity];
537 };
538 
539 /**
540  * Generates a RGBA color value like #112233AA from it's RGB and opacity parts.
541  * @param {String} rgb A RGB color value.
542  * @param {Float} o The desired opacity >=0, <=1.
543  * @returns {String} The RGBA color value.
544  */
545 JXG.rgbo2rgba = function (rgb, o) {
546     var rgba;
547 
548     if (rgb == 'none')
549         return rgb;
550 
551     rgba = Math.round(o*255).toString(16);
552     if (rgba.length == 1)
553         rgba = "0" + rgba;
554 
555     return rgb + rgba;
556 };
557 
558 /**
559  * Decolorizes the given color.
560  * @param {String} color HTML string containing the HTML color code.
561  * @type String
562  * @return Returns a HTML color string
563  */
564 JXG.rgb2bw = function(color) {
565     if(color == 'none') {
566         return color;
567     }
568     var x, HexChars="0123456789ABCDEF", tmp, arr;
569     arr = JXG.rgbParser(color);
570     x = 0.3*arr[0] + 0.59*arr[1] + 0.11*arr[2];
571     tmp = HexChars.charAt((x>>4)&0xf)+HexChars.charAt(x&0xf);
572     color = "#" + tmp + "" + tmp + "" + tmp;
573     return color;
574 };
575 
576 /**
577  * Converts a color into how a colorblind human approximately would see it.
578  * @param {String} color HTML string containing the HTML color code.
579  * @param {String} deficiency The type of color blindness. Possible
580  * options are <i>protanopia</i>, <i>deuteranopia</i>, and <i>tritanopia</i>.
581  * @type String
582  * @return Returns a HTML color string
583  */
584 JXG.rgb2cb = function(color, deficiency) {
585     if(color == 'none') {
586         return color;
587     }
588 
589     var rgb, l, m, s, lms, tmp,
590         a1, b1, c1, a2, b2, c2,
591         inflection;
592 
593     lms = JXG.rgb2LMS(color);
594     l = lms.l; m = lms.m; s = lms.s;
595 
596     deficiency = deficiency.toLowerCase();
597 
598     switch(deficiency) {
599         case "protanopia":
600             a1 = -0.06150039994295001;
601             b1 = 0.08277001656812001;
602             c1 = -0.013200141220000003;
603             a2 = 0.05858939668799999;
604             b2 = -0.07934519995360001;
605             c2 = 0.013289415272000003;
606             inflection = 0.6903216543277437;
607 
608             tmp = s/m;
609             if (tmp < inflection)
610                 l = -(b1 * m + c1 * s) / a1;
611             else
612                 l = -(b2 * m + c2 * s) / a2;
613             break;
614         case "tritanopia":
615             a1 = -0.00058973116217;
616             b1 = 0.007690316482;
617             c1 = -0.01011703519052;
618             a2 = 0.025495080838999994;
619             b2 = -0.0422740347;
620             c2 = 0.017005316784;
621             inflection = 0.8349489908460004;
622 
623             tmp = m / l;
624             if (tmp < inflection)
625               s = -(a1 * l + b1 * m) / c1;
626             else
627               s = -(a2 * l + b2 * m) / c2;
628             break;
629         default:
630             a1 = -0.06150039994295001;
631             b1 = 0.08277001656812001;
632             c1 = -0.013200141220000003;
633             a2 = 0.05858939668799999;
634             b2 = -0.07934519995360001;
635             c2 = 0.013289415272000003;
636             inflection = 0.5763833686400911;
637 
638             tmp = s/l;
639             if(tmp < inflection)
640                 m = -(a1 * l + c1 * s) / b1;
641             else
642                 m = -(a2 * l + c2 * s) / b2;
643             break;
644     }
645 
646     rgb = JXG.LMS2rgb(l, m, s);
647 
648     var HexChars="0123456789ABCDEF";
649     tmp = HexChars.charAt((rgb.r>>4)&0xf)+HexChars.charAt(rgb.r&0xf);
650     color = "#" + tmp;
651     tmp = HexChars.charAt((rgb.g>>4)&0xf)+HexChars.charAt(rgb.g&0xf);
652     color += tmp;
653     tmp = HexChars.charAt((rgb.b>>4)&0xf)+HexChars.charAt(rgb.b&0xf);
654     color += tmp;
655 
656     return color;
657 };
658