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