1 /*
  2     Copyright 2008-2021
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Andreas Walter,
  9         Peter Wilfahrt
 10 
 11     This file is part of JSXGraph.
 12 
 13     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 14 
 15     You can redistribute it and/or modify it under the terms of the
 16 
 17       * GNU Lesser General Public License as published by
 18         the Free Software Foundation, either version 3 of the License, or
 19         (at your option) any later version
 20       OR
 21       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 22 
 23     JSXGraph is distributed in the hope that it will be useful,
 24     but WITHOUT ANY WARRANTY; without even the implied warranty of
 25     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 26     GNU Lesser General Public License for more details.
 27 
 28     You should have received a copy of the GNU Lesser General Public License and
 29     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 30     and <http://opensource.org/licenses/MIT/>.
 31  */
 32 
 33 /*global JXG: true, define: true*/
 34 
 35 /*jslint nomen: true, plusplus: true*/
 36 
 37 /* depends:
 38  jxg
 39  utils/type
 40  math/math
 41  */
 42 
 43 /**
 44  * Functions for color conversions. This was originally based on a class to parse color values by
 45  * Stoyan Stefanov <sstoo@gmail.com> (see http://www.phpied.com/rgb-color-parser-in-javascript/)
 46  */
 47 
 48 define(['jxg', 'utils/type', 'math/math'], function (JXG, Type, Mat) {
 49 
 50     "use strict";
 51 
 52     // private constants and helper functions
 53 
 54     // simple colors contains string color constants that can be used in various browser
 55     // in javascript
 56     var simpleColors = {
 57             aliceblue: 'f0f8ff',
 58             antiquewhite: 'faebd7',
 59             aqua: '00ffff',
 60             aquamarine: '7fffd4',
 61             azure: 'f0ffff',
 62             beige: 'f5f5dc',
 63             bisque: 'ffe4c4',
 64             black: '000000',
 65             blanchedalmond: 'ffebcd',
 66             blue: '0000ff',
 67             blueviolet: '8a2be2',
 68             brown: 'a52a2a',
 69             burlywood: 'deb887',
 70             cadetblue: '5f9ea0',
 71             chartreuse: '7fff00',
 72             chocolate: 'd2691e',
 73             coral: 'ff7f50',
 74             cornflowerblue: '6495ed',
 75             cornsilk: 'fff8dc',
 76             crimson: 'dc143c',
 77             cyan: '00ffff',
 78             darkblue: '00008b',
 79             darkcyan: '008b8b',
 80             darkgoldenrod: 'b8860b',
 81             darkgray: 'a9a9a9',
 82             darkgreen: '006400',
 83             darkkhaki: 'bdb76b',
 84             darkmagenta: '8b008b',
 85             darkolivegreen: '556b2f',
 86             darkorange: 'ff8c00',
 87             darkorchid: '9932cc',
 88             darkred: '8b0000',
 89             darksalmon: 'e9967a',
 90             darkseagreen: '8fbc8f',
 91             darkslateblue: '483d8b',
 92             darkslategray: '2f4f4f',
 93             darkturquoise: '00ced1',
 94             darkviolet: '9400d3',
 95             deeppink: 'ff1493',
 96             deepskyblue: '00bfff',
 97             dimgray: '696969',
 98             dodgerblue: '1e90ff',
 99             feldspar: 'd19275',
100             firebrick: 'b22222',
101             floralwhite: 'fffaf0',
102             forestgreen: '228b22',
103             fuchsia: 'ff00ff',
104             gainsboro: 'dcdcdc',
105             ghostwhite: 'f8f8ff',
106             gold: 'ffd700',
107             goldenrod: 'daa520',
108             gray: '808080',
109             green: '008000',
110             greenyellow: 'adff2f',
111             honeydew: 'f0fff0',
112             hotpink: 'ff69b4',
113             indianred: 'cd5c5c',
114             indigo: '4b0082',
115             ivory: 'fffff0',
116             khaki: 'f0e68c',
117             lavender: 'e6e6fa',
118             lavenderblush: 'fff0f5',
119             lawngreen: '7cfc00',
120             lemonchiffon: 'fffacd',
121             lightblue: 'add8e6',
122             lightcoral: 'f08080',
123             lightcyan: 'e0ffff',
124             lightgoldenrodyellow: 'fafad2',
125             lightgrey: 'd3d3d3',
126             lightgreen: '90ee90',
127             lightpink: 'ffb6c1',
128             lightsalmon: 'ffa07a',
129             lightseagreen: '20b2aa',
130             lightskyblue: '87cefa',
131             lightslateblue: '8470ff',
132             lightslategray: '778899',
133             lightsteelblue: 'b0c4de',
134             lightyellow: 'ffffe0',
135             lime: '00ff00',
136             limegreen: '32cd32',
137             linen: 'faf0e6',
138             magenta: 'ff00ff',
139             maroon: '800000',
140             mediumaquamarine: '66cdaa',
141             mediumblue: '0000cd',
142             mediumorchid: 'ba55d3',
143             mediumpurple: '9370d8',
144             mediumseagreen: '3cb371',
145             mediumslateblue: '7b68ee',
146             mediumspringgreen: '00fa9a',
147             mediumturquoise: '48d1cc',
148             mediumvioletred: 'c71585',
149             midnightblue: '191970',
150             mintcream: 'f5fffa',
151             mistyrose: 'ffe4e1',
152             moccasin: 'ffe4b5',
153             navajowhite: 'ffdead',
154             navy: '000080',
155             oldlace: 'fdf5e6',
156             olive: '808000',
157             olivedrab: '6b8e23',
158             orange: 'ffa500',
159             orangered: 'ff4500',
160             orchid: 'da70d6',
161             palegoldenrod: 'eee8aa',
162             palegreen: '98fb98',
163             paleturquoise: 'afeeee',
164             palevioletred: 'd87093',
165             papayawhip: 'ffefd5',
166             peachpuff: 'ffdab9',
167             peru: 'cd853f',
168             pink: 'ffc0cb',
169             plum: 'dda0dd',
170             powderblue: 'b0e0e6',
171             purple: '800080',
172             red: 'ff0000',
173             rosybrown: 'bc8f8f',
174             royalblue: '4169e1',
175             saddlebrown: '8b4513',
176             salmon: 'fa8072',
177             sandybrown: 'f4a460',
178             seagreen: '2e8b57',
179             seashell: 'fff5ee',
180             sienna: 'a0522d',
181             silver: 'c0c0c0',
182             skyblue: '87ceeb',
183             slateblue: '6a5acd',
184             slategray: '708090',
185             snow: 'fffafa',
186             springgreen: '00ff7f',
187             steelblue: '4682b4',
188             tan: 'd2b48c',
189             teal: '008080',
190             thistle: 'd8bfd8',
191             tomato: 'ff6347',
192             turquoise: '40e0d0',
193             violet: 'ee82ee',
194             violetred: 'd02090',
195             wheat: 'f5deb3',
196             white: 'ffffff',
197             whitesmoke: 'f5f5f5',
198             yellow: 'ffff00',
199             yellowgreen: '9acd32'
200         },
201         // array of color definition objects
202         colorDefs = [{
203             re: /^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]{1,3})\s*\)\s*$/,
204             example: ['rgba(123, 234, 45, 0.5)', 'rgba(255,234,245,1.0)'],
205             process: function (bits) {
206                 return [
207                     parseInt(bits[1], 10),
208                     parseInt(bits[2], 10),
209                     parseInt(bits[3], 10)
210                 ];
211             }
212         }, {
213             re: /^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$/,
214             example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
215             process: function (bits) {
216                 return [
217                     parseInt(bits[1], 10),
218                     parseInt(bits[2], 10),
219                     parseInt(bits[3], 10)
220                 ];
221             }
222         }, {
223             re: /^(\w{2})(\w{2})(\w{2})$/,
224             example: ['#00ff00', '336699'],
225             process: function (bits) {
226                 return [
227                     parseInt(bits[1], 16),
228                     parseInt(bits[2], 16),
229                     parseInt(bits[3], 16)
230                 ];
231             }
232         }, {
233             re: /^(\w{1})(\w{1})(\w{1})$/,
234             example: ['#fb0', 'f0f'],
235             process: function (bits) {
236                 return [
237                     parseInt(bits[1] + bits[1], 16),
238                     parseInt(bits[2] + bits[2], 16),
239                     parseInt(bits[3] + bits[3], 16)
240                 ];
241             }
242         }];
243 
244     /**
245      * Converts a valid HTML/CSS color string into a rgb value array. This is the base
246      * function for the following wrapper functions which only adjust the output to
247      * different flavors like an object, string or hex values.
248      * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
249      * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
250      * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
251      * expects the parameters ag and ab.
252      * @param {Number} ag
253      * @param {Number} ab
254      * @returns {Array} RGB color values as an array [r, g, b] with values ranging from 0 to 255.
255      */
256     JXG.rgbParser = function (color, ag, ab) {
257         var color_string, channels, re, processor, bits, i,
258             r, g, b,
259             values = color,
260             testFloat;
261 
262         if (!Type.exists(color)) {
263             return [];
264         }
265 
266         if (Type.exists(ag) && Type.exists(ab)) {
267             values = [color, ag, ab];
268         }
269 
270         color_string = values;
271 
272         testFloat = false;
273         if (Type.isArray(color_string)) {
274             for (i = 0; i < 3; i++) {
275                 testFloat = testFloat || /\./.test(values[i].toString());
276             }
277 
278             for (i = 0; i < 3; i++) {
279                 testFloat = testFloat && (values[i] >= 0.0) && (values[i] <= 1.0);
280             }
281 
282             if (testFloat) {
283                 return [Math.ceil(values[0] * 255), Math.ceil(values[1] * 255), Math.ceil(values[2] * 255)];
284             }
285 
286             return values;
287         }
288 
289         if (typeof values === 'string') {
290             color_string = values;
291         }
292 
293         // strip any leading #
294         if (color_string.charAt(0) === '#') { // remove # if any
295             color_string = color_string.substr(1, 6);
296         }
297 
298         color_string = color_string.replace(/ /g, '').toLowerCase();
299 
300         // before getting into regexps, try simple matches
301         // and overwrite the input
302         color_string = simpleColors[color_string] || color_string;
303 
304         // search through the colorDefs definitions to find a match
305         for (i = 0; i < colorDefs.length; i++) {
306             re = colorDefs[i].re;
307             processor = colorDefs[i].process;
308             bits = re.exec(color_string);
309 
310             if (bits) {
311                 channels = processor(bits);
312                 r = channels[0];
313                 g = channels[1];
314                 b = channels[2];
315             }
316 
317         }
318 
319         if (isNaN(r) || isNaN(g) || isNaN(b)) {
320             return [];
321         }
322 
323         // validate/cleanup values
324         r = (r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r);
325         g = (g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g);
326         b = (b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b);
327 
328         return [r, g, b];
329     };
330 
331     /**
332      * Converts a valid HTML/CSS color string into a string of the 'rgb(r, g, b)' format.
333      * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
334      * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
335      * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
336      * expects the parameters ag and ab.
337      * @param {Number} ag
338      * @param {Number} ab
339      * @returns {String} A 'rgb(r, g, b)' formatted string
340      */
341     JXG.rgb2css = function (color, ag, ab) {
342         var r;
343 
344         r = JXG.rgbParser(color, ag, ab);
345 
346         return 'rgb(' + r[0] + ', ' + r[1] + ', ' + r[2] + ')';
347     };
348 
349     /**
350      * Converts a valid HTML/CSS color string into a HTML rgb string.
351      * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
352      * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
353      * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
354      * expects the parameters ag and ab.
355      * @param {Number} ag
356      * @param {Number} ab
357      * @returns {String} A '#rrggbb' formatted string
358      */
359     JXG.rgb2hex = function (color, ag, ab) {
360         var r, g, b;
361 
362         r = JXG.rgbParser(color, ag, ab);
363         g = r[1];
364         b = r[2];
365         r = r[0];
366         r = r.toString(16);
367         g = g.toString(16);
368         b = b.toString(16);
369 
370         if (r.length === 1) {
371             r = '0' + r;
372         }
373 
374         if (g.length === 1) {
375             g = '0' + g;
376         }
377 
378         if (b.length === 1) {
379             b = '0' + b;
380         }
381 
382         return '#' + r + g + b;
383     };
384 
385     /**
386      * Converts a valid HTML/CSS color string from the '#rrggbb' format into the 'rgb(r, g, b)' format.
387      * @param {String} hex A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', or 'black'
388      * @deprecated Use {@link JXG#rgb2css} instead.
389      * @returns {String} A 'rgb(r, g, b)' formatted string
390      */
391     JXG.hex2rgb = function (hex) {
392         JXG.deprecated('JXG.hex2rgb()', 'JXG.rgb2css()');
393         return JXG.rgb2css(hex);
394     };
395 
396     /**
397      * Converts HSV color to RGB color.
398      * Based on C Code in "Computer Graphics -- Principles and Practice,"
399      * Foley et al, 1996, p. 593.
400      * See also http://www.efg2.com/Lab/Graphics/Colors/HSV.htm
401      * @param {Number} H value between 0 and 360
402      * @param {Number} S value between 0.0 (shade of gray) to 1.0 (pure color)
403      * @param {Number} V value between 0.0 (black) to 1.0 (white)
404      * @returns {String} RGB color string
405      */
406     JXG.hsv2rgb = function (H, S, V) {
407         var R, G, B, f, i, hTemp, p, q, t;
408 
409         H = ((H % 360.0) + 360.0) % 360;
410 
411         if (S === 0) {
412             if (isNaN(H) || H < Mat.eps) {
413                 R = V;
414                 G = V;
415                 B = V;
416             } else {
417                 return '#ffffff';
418             }
419         } else {
420             if (H >= 360) {
421                 hTemp = 0.0;
422             } else {
423                 hTemp = H;
424             }
425 
426             // h is now IN [0,6)
427             hTemp = hTemp / 60;
428             // largest integer <= h
429             i = Math.floor(hTemp);
430             // fractional part of h
431             f = hTemp - i;
432             p = V * (1.0 - S);
433             q = V * (1.0 - (S * f));
434             t = V * (1.0 - (S * (1.0 - f)));
435 
436             switch (i) {
437                 case 0:
438                     R = V;
439                     G = t;
440                     B = p;
441                     break;
442                 case 1:
443                     R = q;
444                     G = V;
445                     B = p;
446                     break;
447                 case 2:
448                     R = p;
449                     G = V;
450                     B = t;
451                     break;
452                 case 3:
453                     R = p;
454                     G = q;
455                     B = V;
456                     break;
457                 case 4:
458                     R = t;
459                     G = p;
460                     B = V;
461                     break;
462                 case 5:
463                     R = V;
464                     G = p;
465                     B = q;
466                     break;
467             }
468         }
469 
470         R = Math.round(R * 255).toString(16);
471         R = (R.length === 2) ? R : ((R.length === 1) ? '0' + R : '00');
472         G = Math.round(G * 255).toString(16);
473         G = (G.length === 2) ? G : ((G.length === 1) ? '0' + G : '00');
474         B = Math.round(B * 255).toString(16);
475         B = (B.length === 2) ? B : ((B.length === 1) ? '0' + B : '00');
476 
477         return ['#', R, G, B].join('');
478     };
479 
480     /**
481      * Converts a color from the RGB color space into the HSV space. Input can be any valid HTML/CSS color definition.
482      * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
483      * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
484      * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
485      * expects the parameters ag and ab.
486      * @param {Number} ag
487      * @param {Number} ab
488      * @returns {Array} Contains the h, s, and v value in this order.
489      * @see http://zach.in.tu-clausthal.de/teaching/cg1_0708/folien/13_color_3_4up.pdf
490      */
491     JXG.rgb2hsv = function (color, ag, ab) {
492         var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min;
493 
494         r = JXG.rgbParser(color, ag, ab);
495 
496         g = r[1];
497         b = r[2];
498         r = r[0];
499         fr = r / 255.0;
500         fg = g / 255.0;
501         fb = b / 255.0;
502         max = Math.max(r, g, b);
503         min = Math.min(r, g, b);
504         fmax = max / 255.0;
505         fmin = min / 255.0;
506 
507         v = fmax;
508         s = 0.0;
509 
510         if (v > 0) {
511             s = (v - fmin) / v;
512         }
513 
514         h = 1.0 / (fmax - fmin);
515 
516         if (s > 0) {
517             if (max === r) {
518                 h = (fg - fb) * h;
519             } else if (max === g) {
520                 h = 2 + (fb - fr) * h;
521             } else {
522                 h = 4 + (fr - fg) * h;
523             }
524         }
525 
526         h *= 60;
527 
528         if (h < 0) {
529             h += 360;
530         }
531 
532         if (max === min) {
533             h = 0.0;
534         }
535 
536         return [h, s, v];
537     };
538 
539     /**
540      * Converts a color from the RGB color space into the LMS space. Input can be any valid HTML/CSS color definition.
541      * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
542      * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
543      * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
544      * expects the parameters ag and ab.
545      * @param {Number} ag
546      * @param {Number} ab
547      * @returns {Array} Contains the l, m, and s value in this order.
548      */
549     JXG.rgb2LMS = function (color, ag, ab) {
550         var r, g, b, l, m, s, ret,
551             // constants
552             matrix = [[0.05059983, 0.08585369, 0.00952420],
553                 [0.01893033, 0.08925308, 0.01370054],
554                 [0.00292202, 0.00975732, 0.07145979]];
555 
556         r = JXG.rgbParser(color, ag, ab);
557         g = r[1];
558         b = r[2];
559         r = r[0];
560 
561         // de-gamma
562         // Maybe this can be made faster by using a cache
563         r = Math.pow(r, 0.476190476);
564         g = Math.pow(g, 0.476190476);
565         b = Math.pow(b, 0.476190476);
566 
567         l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2];
568         m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2];
569         s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2];
570 
571         ret = [l, m, s];
572         ret.l = l;
573         ret.m = m;
574         ret.s = s;
575 
576         return ret;
577     };
578 
579     /**
580      * Convert color information from LMS to RGB color space.
581      * @param {Number} l
582      * @param {Number} m
583      * @param {Number} s
584      * @returns {Array} Contains the r, g, and b value in this order.
585      */
586     JXG.LMS2rgb = function (l, m, s) {
587         var r, g, b, ret,
588             // constants
589             matrix = [[30.830854, -29.832659, 1.610474],
590                 [-6.481468, 17.715578, -2.532642],
591                 [-0.375690, -1.199062, 14.273846]],
592 
593             // re-gamma, inspired by GIMP modules/display-filter-color-blind.c:
594             // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>,
595             //                         Sven Neumann <sven@gimp.org>,
596             //                         Robert Dougherty <bob@vischeck.com> and
597             //                         Alex Wade <alex@vischeck.com>
598             // This code is an implementation of an algorithm described by Hans Brettel,
599             // Francoise Vienot and John Mollon in the Journal of the Optical Society of
600             // America V14(10), pg 2647. (See http://vischeck.com/ for more info.)
601             lut_lookup = function (value) {
602                 var offset = 127, step = 64;
603 
604                 while (step > 0) {
605                     if (Math.pow(offset, 0.476190476) > value) {
606                         offset -= step;
607                     } else {
608                         if (Math.pow(offset + 1, 0.476190476) > value) {
609                             return offset;
610                         }
611 
612                         offset += step;
613                     }
614 
615                     step /= 2;
616                 }
617 
618                 /*  the algorithm above can't reach 255  */
619                 if (offset === 254 && 13.994955247 < value) {
620                     return 255;
621                 }
622 
623                 return offset;
624             };
625 
626         // transform back to rgb
627         r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2];
628         g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2];
629         b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2];
630 
631         r = lut_lookup(r);
632         g = lut_lookup(g);
633         b = lut_lookup(b);
634 
635         ret = [r, g, b];
636         ret.r = r;
637         ret.g = g;
638         ret.b = b;
639 
640         return ret;
641     };
642 
643     /**
644      * Splits a RGBA color value like #112233AA into it's RGB and opacity parts.
645      * @param {String} rgba A RGBA color value
646      * @returns {Array} An array containing the rgb color value in the first and the opacity in the second field.
647      */
648     JXG.rgba2rgbo = function (rgba) {
649         var opacity;
650 
651         if (rgba.length === 9 && rgba.charAt(0) === '#') {
652             opacity = parseInt(rgba.substr(7, 2).toUpperCase(), 16) / 255;
653             rgba = rgba.substr(0, 7);
654         } else {
655             opacity = 1;
656         }
657 
658         return [rgba, opacity];
659     };
660 
661     /**
662      * Generates a RGBA color value like #112233AA from it's RGB and opacity parts.
663      * @param {String} rgb A RGB color value.
664      * @param {Number} o The desired opacity >=0, <=1.
665      * @returns {String} The RGBA color value.
666      */
667     JXG.rgbo2rgba = function (rgb, o) {
668         var rgba;
669 
670         if (rgb === 'none') {
671             return rgb;
672         }
673 
674         rgba = Math.round(o * 255).toString(16);
675         if (rgba.length === 1) {
676             rgba = "0" + rgba;
677         }
678 
679         return rgb + rgba;
680     };
681 
682     /**
683      * Decolorizes the given color.
684      * @param {String} color HTML string containing the HTML color code.
685      * @returns {String} Returns a HTML color string
686      */
687     JXG.rgb2bw = function (color) {
688         var x, tmp, arr,
689             HexChars = "0123456789ABCDEF";
690 
691         if (color === 'none') {
692             return color;
693         }
694 
695         arr = JXG.rgbParser(color);
696         x = Math.floor(0.3 * arr[0] + 0.59 * arr[1] + 0.11 * arr[2]);
697 
698         // rgbParser and Math.floor ensure that x is 0 <= x <= 255.
699         // Bitwise operators can be used.
700         /*jslint bitwise: true*/
701         tmp = HexChars.charAt((x >> 4) & 0xf) + HexChars.charAt(x & 0xf);
702 
703         color = "#" + tmp + tmp + tmp;
704 
705         return color;
706     };
707 
708     /**
709      * Converts a color into how a colorblind human approximately would see it.
710      * @param {String} color HTML string containing the HTML color code.
711      * @param {String} deficiency The type of color blindness. Possible
712      * options are <i>protanopia</i>, <i>deuteranopia</i>, and <i>tritanopia</i>.
713      * @returns {String} Returns a HTML color string
714      */
715     JXG.rgb2cb = function (color, deficiency) {
716         var rgb, l, m, s, lms, tmp,
717             a1, b1, c1, a2, b2, c2,
718             inflection,
719             HexChars = "0123456789ABCDEF";
720 
721         if (color === 'none') {
722             return color;
723         }
724 
725         lms = JXG.rgb2LMS(color);
726         l = lms[0];
727         m = lms[1];
728         s = lms[2];
729 
730         deficiency = deficiency.toLowerCase();
731 
732         switch (deficiency) {
733             case "protanopia":
734                 a1 = -0.06150039994295001;
735                 b1 = 0.08277001656812001;
736                 c1 = -0.013200141220000003;
737                 a2 = 0.05858939668799999;
738                 b2 = -0.07934519995360001;
739                 c2 = 0.013289415272000003;
740                 inflection = 0.6903216543277437;
741 
742                 tmp = s / m;
743 
744                 if (tmp < inflection) {
745                     l = -(b1 * m + c1 * s) / a1;
746                 } else {
747                     l = -(b2 * m + c2 * s) / a2;
748                 }
749                 break;
750             case "tritanopia":
751                 a1 = -0.00058973116217;
752                 b1 = 0.007690316482;
753                 c1 = -0.01011703519052;
754                 a2 = 0.025495080838999994;
755                 b2 = -0.0422740347;
756                 c2 = 0.017005316784;
757                 inflection = 0.8349489908460004;
758 
759                 tmp = m / l;
760 
761                 if (tmp < inflection) {
762                     s = -(a1 * l + b1 * m) / c1;
763                 } else {
764                     s = -(a2 * l + b2 * m) / c2;
765                 }
766                 break;
767             default:
768                 a1 = -0.06150039994295001;
769                 b1 = 0.08277001656812001;
770                 c1 = -0.013200141220000003;
771                 a2 = 0.05858939668799999;
772                 b2 = -0.07934519995360001;
773                 c2 = 0.013289415272000003;
774                 inflection = 0.5763833686400911;
775 
776                 tmp = s / l;
777 
778                 if (tmp < inflection) {
779                     m = -(a1 * l + c1 * s) / b1;
780                 } else {
781                     m = -(a2 * l + c2 * s) / b2;
782                 }
783                 break;
784         }
785 
786         rgb = JXG.LMS2rgb(l, m, s);
787 
788         // LMS2rgb returns an array of values ranging from 0 to 255 (both included)
789         // bitwise operators are safe to use.
790         /*jslint bitwise: true*/
791         tmp = HexChars.charAt((rgb[0] >> 4) & 0xf) + HexChars.charAt(rgb[0] & 0xf);
792         color = "#" + tmp;
793         tmp = HexChars.charAt((rgb[1] >> 4) & 0xf) + HexChars.charAt(rgb[1] & 0xf);
794         color += tmp;
795         tmp = HexChars.charAt((rgb[2] >> 4) & 0xf) + HexChars.charAt(rgb[2] & 0xf);
796         color += tmp;
797 
798         return color;
799     };
800 
801     /**
802      * Determines highlight color to a given color. Done by reducing (or increasing) the opacity.
803      * @param {String} color HTML RGBA string containing the HTML color code.
804      * @returns {String} Returns a HTML RGBA color string
805      */
806     JXG.autoHighlight = function (colstr) {
807         var col = JXG.rgba2rgbo(colstr),
808             c = col[0],
809             opa = col[1];
810 
811         if (colstr.charAt(0) === '#') {
812             if (opa < 0.3) {
813                 opa *= 1.8;
814             } else {
815                 opa *= 0.4;
816             }
817 
818             return JXG.rgbo2rgba(c, opa);
819         }
820 
821         return colstr;
822     };
823 
824     /**
825      * Calculate whether a light or a dark color is needed as a contrast.
826      * Especially useful to determine whether white or black font goes
827      * better with a given background color.
828      * @param {String} hexColor HEX value of color.
829      * @param {String} [darkColor="#000000"] HEX string for a dark color.
830      * @param {String} [lightColor="#ffffff"] HEX string for a light color.
831      * @param {Number} [threshold=8]
832      * @returns {String} Returns darkColor or lightColor.
833      */
834     JXG.contrast = function (hexColor, darkColor, lightColor, threshold) {
835         var rgb,
836             black = '#000000',
837             rgbBlack,
838             l1, l2,
839             contrastRatio;
840 
841         darkColor = darkColor || '#000000';
842         lightColor = lightColor || '#ffffff';
843         threshold = threshold || 7;
844 
845         // hexColor RGB
846         rgb = JXG.rgbParser(hexColor);
847 
848         // Black RGB
849         rgbBlack = JXG.rgbParser(black);
850 
851         // Calc contrast ratio
852         l1 = 0.2126 * Math.pow(rgb[0] / 255, 2.2) +
853             0.7152 * Math.pow(rgb[1] / 255, 2.2) +
854             0.0722 * Math.pow(rgb[2] / 255, 2.2);
855 
856         l2 = 0.2126 * Math.pow(rgbBlack[0] / 255, 2.2) +
857             0.7152 * Math.pow(rgbBlack[1] / 255, 2.2) +
858             0.0722 * Math.pow(rgbBlack[2] / 255, 2.2);
859 
860         if (l1 > l2) {
861             contrastRatio = Math.floor((l1 + 0.05) / (l2 + 0.05));
862         } else {
863             contrastRatio = Math.floor((l2 + 0.05) / (l1 + 0.05));
864         }
865         contrastRatio = contrastRatio - 1;
866 
867         // If contrast is more than threshold, return darkColor
868         if (contrastRatio > threshold) {
869             return darkColor;
870         }
871         // if not, return lightColor.
872         return lightColor;
873     };
874 
875     return JXG;
876 });
877