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