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