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         venetianred: "ae181e",
188         violet: "ee82ee",
189         violetred: "d02090",
190         wheat: "f5deb3",
191         white: "ffffff",
192         whitesmoke: "f5f5f5",
193         yellow: "ffff00",
194         yellowgreen: "9acd32"
195     },
196     // array of color definition objects
197     colorDefs = [
198         {
199             re: /^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d.]{1,3})\s*\)\s*$/,
200             example: ["rgba(123, 234, 45, 0.5)", "rgba(255,234,245,1.0)"],
201             process: function (bits) {
202                 return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10)];
203             }
204         },
205         {
206             re: /^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$/,
207             example: ["rgb(123, 234, 45)", "rgb(255,234,245)"],
208             process: function (bits) {
209                 return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10)];
210             }
211         },
212         {
213             re: /^(\w{2})(\w{2})(\w{2})$/,
214             example: ["#00ff00", "336699"],
215             process: function (bits) {
216                 return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16)];
217             }
218         },
219         {
220             re: /^(\w{1})(\w{1})(\w{1})$/,
221             example: ["#fb0", "f0f"],
222             process: function (bits) {
223                 return [
224                     parseInt(bits[1] + bits[1], 16),
225                     parseInt(bits[2] + bits[2], 16),
226                     parseInt(bits[3] + bits[3], 16)
227                 ];
228             }
229         }
230     ];
231 
232 /**
233  * Converts a valid HTML/CSS color string into a rgb value array. This is the base
234  * function for the following wrapper functions which only adjust the output to
235  * different flavors like an object, string or hex values.
236  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
237  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
238  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
239  * expects the parameters ag and ab.
240  * @param {Number} ag
241  * @param {Number} ab
242  * @returns {Array} RGB color values as an array [r, g, b] with values ranging from 0 to 255.
243  */
244 JXG.rgbParser = function (color, ag, ab) {
245     var color_string,
246         channels,
247         re,
248         processor,
249         bits,
250         i,
251         r,
252         g,
253         b,
254         values = color,
255         testFloat;
256 
257     if (!Type.exists(color)) {
258         return [];
259     }
260 
261     if (Type.exists(ag) && Type.exists(ab)) {
262         values = [color, ag, ab];
263     }
264 
265     color_string = values;
266 
267     testFloat = false;
268     if (Type.isArray(color_string)) {
269         for (i = 0; i < 3; i++) {
270             testFloat = testFloat || /\./.test(values[i].toString());
271         }
272 
273         for (i = 0; i < 3; i++) {
274             testFloat = testFloat && values[i] >= 0.0 && values[i] <= 1.0;
275         }
276 
277         if (testFloat) {
278             return [
279                 Math.ceil(values[0] * 255),
280                 Math.ceil(values[1] * 255),
281                 Math.ceil(values[2] * 255)
282             ];
283         }
284 
285         return values;
286     }
287 
288     if (typeof values === "string") {
289         color_string = values;
290     }
291 
292     // strip any leading #
293     if (color_string.charAt(0) === "#") {
294         // remove # if any
295         color_string = color_string.slice(1, 7);
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     if (isNaN(r) || isNaN(g) || isNaN(b)) {
319         return [];
320     }
321 
322     // validate/cleanup values
323     r = r < 0 || isNaN(r) ? 0 : r > 255 ? 255 : r;
324     g = g < 0 || isNaN(g) ? 0 : g > 255 ? 255 : g;
325     b = b < 0 || isNaN(b) ? 0 : b > 255 ? 255 : b;
326 
327     return [r, g, b];
328 };
329 
330 JXG.isColor = function (strColor) {
331     var s = new Option().style;
332     s.color = strColor;
333     return s.color !== '';
334 };
335 
336 /**
337  * Converts a valid HTML/CSS color string into a string of the 'rgb(r, g, b)' format.
338  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
339  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
340  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
341  * expects the parameters ag and ab.
342  * @param {Number} ag
343  * @param {Number} ab
344  * @returns {String} A 'rgb(r, g, b)' formatted string
345  */
346 JXG.rgb2css = function (color, ag, ab) {
347     var r;
348 
349     r = JXG.rgbParser(color, ag, ab);
350 
351     return "rgb(" + r[0] + ", " + r[1] + ", " + r[2] + ")";
352 };
353 
354 /**
355  * Converts a valid HTML/CSS color string into a HTML rgb string.
356  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
357  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
358  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
359  * expects the parameters ag and ab.
360  * @param {Number} ag
361  * @param {Number} ab
362  * @returns {String} A '#rrggbb' formatted string
363  */
364 JXG.rgb2hex = function (color, ag, ab) {
365     var r, g, b;
366 
367     r = JXG.rgbParser(color, ag, ab);
368     g = r[1];
369     b = r[2];
370     r = r[0];
371     r = r.toString(16);
372     g = g.toString(16);
373     b = b.toString(16);
374 
375     if (r.length === 1) {
376         r = "0" + r;
377     }
378 
379     if (g.length === 1) {
380         g = "0" + g;
381     }
382 
383     if (b.length === 1) {
384         b = "0" + b;
385     }
386 
387     return "#" + r + g + b;
388 };
389 
390 /**
391  * Converts a valid HTML/CSS color string from the '#rrggbb' format into the 'rgb(r, g, b)' format.
392  * @param {String} hex A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', or 'black'
393  * @deprecated Use {@link JXG#rgb2css} instead.
394  * @returns {String} A 'rgb(r, g, b)' formatted string
395  */
396 JXG.hex2rgb = function (hex) {
397     JXG.deprecated("JXG.hex2rgb()", "JXG.rgb2css()");
398     return JXG.rgb2css(hex);
399 };
400 
401 /**
402  * Converts HSV color to RGB color.
403  * Based on C Code in "Computer Graphics -- Principles and Practice,"
404  * Foley et al, 1996, p. 593.
405  * See also https://www.had2know.org/technology/hsv-rgb-conversion-formula-calculator.html
406  * @param {Number} H value between 0 and 360
407  * @param {Number} S value between 0.0 (shade of gray) to 1.0 (pure color)
408  * @param {Number} V value between 0.0 (black) to 1.0 (white)
409  * @returns {String} RGB color string
410  */
411 JXG.hsv2rgb = function (H, S, V) {
412     var R, G, B, f, i, hTemp, p, q, t;
413 
414     H = ((H % 360.0) + 360.0) % 360;
415 
416     if (S === 0) {
417         if (isNaN(H) || H < Mat.eps) {
418             R = V;
419             G = V;
420             B = V;
421         } else {
422             return "#ffffff";
423         }
424     } else {
425         if (H >= 360) {
426             hTemp = 0.0;
427         } else {
428             hTemp = H;
429         }
430 
431         // h is now IN [0,6)
432         hTemp = hTemp / 60;
433         // largest integer <= h
434         i = Math.floor(hTemp);
435         // fractional part of h
436         f = hTemp - i;
437         p = V * (1.0 - S);
438         q = V * (1.0 - S * f);
439         t = V * (1.0 - S * (1.0 - f));
440 
441         switch (i) {
442             case 0:
443                 R = V;
444                 G = t;
445                 B = p;
446                 break;
447             case 1:
448                 R = q;
449                 G = V;
450                 B = p;
451                 break;
452             case 2:
453                 R = p;
454                 G = V;
455                 B = t;
456                 break;
457             case 3:
458                 R = p;
459                 G = q;
460                 B = V;
461                 break;
462             case 4:
463                 R = t;
464                 G = p;
465                 B = V;
466                 break;
467             case 5:
468                 R = V;
469                 G = p;
470                 B = q;
471                 break;
472         }
473     }
474 
475     R = Math.round(R * 255).toString(16);
476     R = R.length === 2 ? R : R.length === 1 ? "0" + R : "00";
477     G = Math.round(G * 255).toString(16);
478     G = G.length === 2 ? G : G.length === 1 ? "0" + G : "00";
479     B = Math.round(B * 255).toString(16);
480     B = B.length === 2 ? B : B.length === 1 ? "0" + B : "00";
481 
482     return ["#", R, G, B].join("");
483 };
484 
485 /**
486  * Converts a color from the RGB color space into the HSV space. Input can be any valid HTML/CSS color definition.
487  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
488  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
489  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
490  * expects the parameters ag and ab. See <a href="https://www.had2know.org/technology/hsv-rgb-conversion-formula-calculator.html">https://www.had2know.org/technology/hsv-rgb-conversion-formula-calculator.html</a>.
491  * @param {Number} ag
492  * @param {Number} ab
493  * @returns {Array} Contains the h, s, and v value in this order.
494  *
495  */
496 JXG.rgb2hsv = function (color, ag, ab) {
497     var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min;
498 
499     r = JXG.rgbParser(color, ag, ab);
500 
501     g = r[1];
502     b = r[2];
503     r = r[0];
504     fr = r / 255.0;
505     fg = g / 255.0;
506     fb = b / 255.0;
507     max = Math.max(r, g, b);
508     min = Math.min(r, g, b);
509     fmax = max / 255.0;
510     fmin = min / 255.0;
511 
512     v = fmax;
513     s = 0.0;
514 
515     if (v > 0) {
516         s = (v - fmin) / v;
517     }
518 
519     h = 1.0 / (fmax - fmin);
520 
521     if (s > 0) {
522         if (max === r) {
523             h = (fg - fb) * h;
524         } else if (max === g) {
525             h = 2 + (fb - fr) * h;
526         } else {
527             h = 4 + (fr - fg) * h;
528         }
529     }
530 
531     h *= 60;
532 
533     if (h < 0) {
534         h += 360;
535     }
536 
537     if (max === min) {
538         h = 0.0;
539     }
540 
541     return [h, s, v];
542 };
543 
544 /**
545  * Converts a color from the RGB color space into the LMS space. Input can be any valid HTML/CSS color definition.
546  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
547  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
548  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
549  * expects the parameters ag and ab.
550  * @param {Number} ag
551  * @param {Number} ab
552  * @returns {Array} Contains the l, m, and s value in this order.
553  */
554 JXG.rgb2LMS = function (color, ag, ab) {
555     var r,
556         g,
557         b,
558         l,
559         m,
560         s,
561         ret,
562         // constants
563         matrix = [
564             [0.05059983, 0.08585369, 0.0095242],
565             [0.01893033, 0.08925308, 0.01370054],
566             [0.00292202, 0.00975732, 0.07145979]
567         ];
568 
569     r = JXG.rgbParser(color, ag, ab);
570     g = r[1];
571     b = r[2];
572     r = r[0];
573 
574     // de-gamma
575     // Maybe this can be made faster by using a cache
576     r = Math.pow(r, 0.476190476);
577     g = Math.pow(g, 0.476190476);
578     b = Math.pow(b, 0.476190476);
579 
580     l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2];
581     m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2];
582     s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2];
583 
584     ret = [l, m, s];
585     ret.l = l;
586     ret.m = m;
587     ret.s = s;
588 
589     return ret;
590 };
591 
592 /**
593  * Convert color information from LMS to RGB color space.
594  * @param {Number} l
595  * @param {Number} m
596  * @param {Number} s
597  * @returns {Array} Contains the r, g, and b value in this order.
598  */
599 JXG.LMS2rgb = function (l, m, s) {
600     var r,
601         g,
602         b,
603         ret,
604         // constants
605         matrix = [
606             [30.830854, -29.832659, 1.610474],
607             [-6.481468, 17.715578, -2.532642],
608             [-0.37569, -1.199062, 14.273846]
609         ],
610         // re-gamma, inspired by GIMP modules/display-filter-color-blind.c:
611         // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>,
612         //                         Sven Neumann <sven@gimp.org>,
613         //                         Robert Dougherty <bob@vischeck.com> and
614         //                         Alex Wade <alex@vischeck.com>
615         // This code is an implementation of an algorithm described by Hans Brettel,
616         // Francoise Vienot and John Mollon in the Journal of the Optical Society of
617         // America V14(10), pg 2647. (See http://vischeck.com/ for more info.)
618         lut_lookup = function (value) {
619             var offset = 127,
620                 step = 64;
621 
622             while (step > 0) {
623                 if (Math.pow(offset, 0.476190476) > value) {
624                     offset -= step;
625                 } else {
626                     if (Math.pow(offset + 1, 0.476190476) > value) {
627                         return offset;
628                     }
629 
630                     offset += step;
631                 }
632 
633                 step /= 2;
634             }
635 
636             /*  the algorithm above can't reach 255  */
637             if (offset === 254 && 13.994955247 < value) {
638                 return 255;
639             }
640 
641             return offset;
642         };
643 
644     // transform back to rgb
645     r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2];
646     g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2];
647     b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2];
648 
649     r = lut_lookup(r);
650     g = lut_lookup(g);
651     b = lut_lookup(b);
652 
653     ret = [r, g, b];
654     ret.r = r;
655     ret.g = g;
656     ret.b = b;
657 
658     return ret;
659 };
660 
661 /**
662  * Splits a RGBA color value like #112233AA into it's RGB and opacity parts.
663  * @param {String} rgba A RGBA color value
664  * @returns {Array} An array containing the rgb color value in the first and the opacity in the second field.
665  */
666 JXG.rgba2rgbo = function (rgba) {
667     var opacity;
668 
669     if (rgba.length === 9 && rgba.charAt(0) === "#") {
670         opacity = parseInt(rgba.slice(7, 9).toUpperCase(), 16) / 255;
671         rgba = rgba.slice(0, 7);
672     } else {
673         opacity = 1;
674     }
675 
676     return [rgba, opacity];
677 };
678 
679 /**
680  * Generates a RGBA color value like #112233AA from it's RGB and opacity parts.
681  * @param {String|Array} rgb A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
682  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
683  * from 0 to 255. They will be interpreted as red, green, and blue values.
684  * @param {Number} o The desired opacity >=0, <=1.
685  * @returns {String} The RGBA color value.
686  */
687 JXG.rgbo2rgba = function (rgb, o) {
688     var rgba;
689 
690     if (rgb === "none" || rgb === "transparent") {
691         return rgb;
692     }
693 
694     rgba = Math.round(o * 255).toString(16);
695     if (rgba.length === 1) {
696         rgba = "0" + rgba;
697     }
698 
699     return JXG.rgb2hex(rgb) + rgba;
700 };
701 
702 /**
703  * Decolorizes the given color.
704  * @param {String} color HTML string containing the HTML color code.
705  * @returns {String} Returns a HTML color string
706  */
707 JXG.rgb2bw = function (color) {
708     var x,
709         tmp,
710         arr,
711         HexChars = "0123456789ABCDEF";
712 
713     if (color === "none") {
714         return color;
715     }
716 
717     arr = JXG.rgbParser(color);
718     x = Math.floor(0.3 * arr[0] + 0.59 * arr[1] + 0.11 * arr[2]);
719 
720     // rgbParser and Math.floor ensure that x is 0 <= x <= 255.
721     // Bitwise operators can be used.
722     /*jslint bitwise: true*/
723     tmp = HexChars.charAt((x >> 4) & 0xf) + HexChars.charAt(x & 0xf);
724 
725     color = "#" + tmp + tmp + tmp;
726 
727     return color;
728 };
729 
730 /**
731  * Converts a color into how a colorblind human approximately would see it.
732  * @param {String} color HTML string containing the HTML color code.
733  * @param {String} deficiency The type of color blindness. Possible
734  * options are <i>protanopia</i>, <i>deuteranopia</i>, and <i>tritanopia</i>.
735  * @returns {String} Returns a HTML color string
736  */
737 JXG.rgb2cb = function (color, deficiency) {
738     var rgb,
739         l,
740         m,
741         s,
742         lms,
743         tmp,
744         a1,
745         b1,
746         c1,
747         a2,
748         b2,
749         c2,
750         inflection,
751         HexChars = "0123456789ABCDEF";
752 
753     if (color === "none") {
754         return color;
755     }
756 
757     lms = JXG.rgb2LMS(color);
758     l = lms[0];
759     m = lms[1];
760     s = lms[2];
761 
762     deficiency = deficiency.toLowerCase();
763 
764     switch (deficiency) {
765         case "protanopia":
766             a1 = -0.06150039994295001;
767             b1 = 0.08277001656812001;
768             c1 = -0.013200141220000003;
769             a2 = 0.05858939668799999;
770             b2 = -0.07934519995360001;
771             c2 = 0.013289415272000003;
772             inflection = 0.6903216543277437;
773 
774             tmp = s / m;
775 
776             if (tmp < inflection) {
777                 l = -(b1 * m + c1 * s) / a1;
778             } else {
779                 l = -(b2 * m + c2 * s) / a2;
780             }
781             break;
782         case "tritanopia":
783             a1 = -0.00058973116217;
784             b1 = 0.007690316482;
785             c1 = -0.01011703519052;
786             a2 = 0.025495080838999994;
787             b2 = -0.0422740347;
788             c2 = 0.017005316784;
789             inflection = 0.8349489908460004;
790 
791             tmp = m / l;
792 
793             if (tmp < inflection) {
794                 s = -(a1 * l + b1 * m) / c1;
795             } else {
796                 s = -(a2 * l + b2 * m) / c2;
797             }
798             break;
799         default:
800             a1 = -0.06150039994295001;
801             b1 = 0.08277001656812001;
802             c1 = -0.013200141220000003;
803             a2 = 0.05858939668799999;
804             b2 = -0.07934519995360001;
805             c2 = 0.013289415272000003;
806             inflection = 0.5763833686400911;
807 
808             tmp = s / l;
809 
810             if (tmp < inflection) {
811                 m = -(a1 * l + c1 * s) / b1;
812             } else {
813                 m = -(a2 * l + c2 * s) / b2;
814             }
815             break;
816     }
817 
818     rgb = JXG.LMS2rgb(l, m, s);
819 
820     // LMS2rgb returns an array of values ranging from 0 to 255 (both included)
821     // bitwise operators are safe to use.
822     /*jslint bitwise: true*/
823     tmp = HexChars.charAt((rgb[0] >> 4) & 0xf) + HexChars.charAt(rgb[0] & 0xf);
824     color = "#" + tmp;
825     tmp = HexChars.charAt((rgb[1] >> 4) & 0xf) + HexChars.charAt(rgb[1] & 0xf);
826     color += tmp;
827     tmp = HexChars.charAt((rgb[2] >> 4) & 0xf) + HexChars.charAt(rgb[2] & 0xf);
828     color += tmp;
829 
830     return color;
831 };
832 
833 /**
834  * Lightens (percent > 0) or darkens (percent < 0) the color by the specified factor.
835  * @param {String} color
836  * @param {Number} percent
837  * @returns {String}
838  */
839 JXG.shadeColor = function (color, percent) {
840     var arr = JXG.rgbParser(color),
841         r = arr[0],
842         g = arr[1],
843         b = arr[2];
844 
845     r = parseInt(r + 255 * percent);
846     g = parseInt(g + 255 * percent);
847     b = parseInt(b + 255 * percent);
848 
849     r = (r > 0) ? r : 0;
850     g = (g > 0) ? g : 0;
851     b = (b > 0) ? b : 0;
852 
853     r = (r < 255) ? r : 255;
854     g = (g < 255) ? g : 255;
855     b = (b < 255) ? b : 255;
856 
857     r = Math.round(r);
858     g = Math.round(g);
859     b = Math.round(b);
860 
861     return JXG.rgb2hex([r, g, b]);
862 };
863 
864 /**
865  * Lightens the color by the specified factor.
866  * @param {String} color
867  * @param {Number} percent
868  * @returns {String}
869  *
870  * @see JXG.shadeColor
871  */
872 JXG.lightenColor = function (color, percent) {
873     return JXG.shadeColor(color, percent);
874 };
875 
876 /**
877  * Darkens the color by the specified factor.
878  * @param {String} color
879  * @param {Number} percent
880  * @returns {String}
881  *
882  * @see JXG.shadeColor
883  */
884 JXG.darkenColor = function (color, percent) {
885     return JXG.shadeColor(color, -1 * percent);
886 };
887 
888 /**
889  * Determines highlight color to a given color. Done by reducing (or increasing) the opacity.
890  * @param {String} color HTML RGBA string containing the HTML color code.
891  * @returns {String} Returns a HTML RGBA color string
892  */
893 JXG.autoHighlight = function (colstr) {
894     var col = JXG.rgba2rgbo(colstr),
895         c = col[0],
896         opa = col[1];
897 
898     if (colstr.charAt(0) === "#") {
899         if (opa < 0.3) {
900             opa *= 1.8;
901         } else {
902             opa *= 0.4;
903         }
904 
905         return JXG.rgbo2rgba(c, opa);
906     }
907 
908     return colstr;
909 };
910 
911 /**
912  * Calculate whether a light or a dark color is needed as a contrast.
913  * Especially useful to determine whether white or black font goes
914  * better with a given background color.
915  * @param {String} hexColor HEX value of color.
916  * @param {String} [darkColor="#000000"] HEX string for a dark color.
917  * @param {String} [lightColor="#ffffff"] HEX string for a light color.
918  * @param {Number} [threshold=8]
919  * @returns {String} Returns darkColor or lightColor.
920  */
921 JXG.contrast = function (hexColor, darkColor, lightColor, threshold) {
922     var rgb,
923         black = "#000000",
924         rgbBlack,
925         l1,
926         l2,
927         contrastRatio;
928 
929     darkColor = darkColor || "#000000";
930     lightColor = lightColor || "#ffffff";
931     threshold = threshold || 7;
932 
933     // hexColor RGB
934     rgb = JXG.rgbParser(hexColor);
935 
936     // Black RGB
937     rgbBlack = JXG.rgbParser(black);
938 
939     // Calc contrast ratio
940     l1 =
941         0.2126 * Math.pow(rgb[0] / 255, 2.2) +
942         0.7152 * Math.pow(rgb[1] / 255, 2.2) +
943         0.0722 * Math.pow(rgb[2] / 255, 2.2);
944 
945     l2 =
946         0.2126 * Math.pow(rgbBlack[0] / 255, 2.2) +
947         0.7152 * Math.pow(rgbBlack[1] / 255, 2.2) +
948         0.0722 * Math.pow(rgbBlack[2] / 255, 2.2);
949 
950     if (l1 > l2) {
951         contrastRatio = Math.floor((l1 + 0.05) / (l2 + 0.05));
952     } else {
953         contrastRatio = Math.floor((l2 + 0.05) / (l1 + 0.05));
954     }
955     contrastRatio = contrastRatio - 1;
956 
957     // If contrast is more than threshold, return darkColor
958     if (contrastRatio > threshold) {
959         return darkColor;
960     }
961     // if not, return lightColor.
962     return lightColor;
963 };
964 
965 /**
966  * Use the color scheme of JSXGraph up to version 1.3.2.
967  * This method has to be called before JXG.JSXGraph.initBoard();
968  *
969  * @see JXG.palette
970  * @see JXG.paletteWong
971  *
972  * @example
973  *
974  * JXG.setClassicColors();
975  * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-5, 5, 5,-5]});
976  *
977  */
978 JXG.setClassicColors = function () {
979     JXG.Options.elements.strokeColor = "blue";
980     JXG.Options.elements.fillColor = "red";
981     JXG.Options.hatch.strokeColor = "blue";
982     JXG.Options.angle.fillColor = "#ff7f00";
983     JXG.Options.angle.highlightFillColor = "#ff7f00";
984     JXG.Options.angle.strokeColor = "#ff7f00";
985     JXG.Options.angle.label.strokeColor = "blue";
986     JXG.Options.arc.strokeColor = "blue";
987     JXG.Options.circle.center.fillColor = "red";
988     JXG.Options.circle.center.strokeColor = "blue";
989     JXG.Options.circumcircle.strokeColor = "blue";
990     JXG.Options.circumcircle.center.fillColor = "red";
991     JXG.Options.circumcircle.center.strokeColor = "blue";
992     JXG.Options.circumcirclearc.strokeColor = "blue";
993     JXG.Options.circumcirclesector.strokeColor = "blue";
994     JXG.Options.circumcirclesector.fillColor = "green";
995     JXG.Options.circumcirclesector.highlightFillColor = "green";
996     JXG.Options.conic.strokeColor = "blue";
997     JXG.Options.curve.strokeColor = "blue";
998     JXG.Options.incircle.strokeColor = "blue";
999     JXG.Options.incircle.center.fillColor = "red";
1000     JXG.Options.incircle.center.strokeColor = "blue";
1001     JXG.Options.inequality.fillColor = "red";
1002     JXG.Options.integral.fillColor = "red";
1003     JXG.Options.integral.curveLeft.color = "red";
1004     JXG.Options.integral.curveRight.color = "red";
1005     JXG.Options.line.strokeColor = "blue";
1006     JXG.Options.point.fillColor = "red";
1007     JXG.Options.point.strokeColor = "red";
1008     JXG.Options.polygon.fillColor = "green";
1009     JXG.Options.polygon.highlightFillColor = "green";
1010     JXG.Options.polygon.vertices.strokeColor = "red";
1011     JXG.Options.polygon.vertices.fillColor = "red";
1012     JXG.Options.regularpolygon.fillColor = "green";
1013     JXG.Options.regularpolygon.highlightFillColor = "green";
1014     JXG.Options.regularpolygon.vertices.strokeColor = "red";
1015     JXG.Options.regularpolygon.vertices.fillColor = "red";
1016     JXG.Options.riemannsum.fillColor = "yellow";
1017     JXG.Options.sector.fillColor = "green";
1018     JXG.Options.sector.highlightFillColor = "green";
1019     JXG.Options.semicircle.center.fillColor = "red";
1020     JXG.Options.semicircle.center.strokeColor = "blue";
1021     JXG.Options.slopetriangle.fillColor = "red";
1022     JXG.Options.slopetriangle.highlightFillColor = "red";
1023     JXG.Options.turtle.arrow.strokeColor = "blue";
1024 };
1025 
1026 JXG.extend(
1027     JXG,
1028     /** @lends JXG */ {
1029         /**
1030          * Bang Wong color palette,
1031          * optimized for various type
1032          * of color blindness.
1033          * It contains values for
1034          * <ul>
1035          * <li> 'black'
1036          * <li> 'orange'
1037          * <li> 'skyblue'
1038          * <li> 'bluishgreen'
1039          * <li> 'yellow'
1040          * <li> 'darkblue'
1041          * <li> 'vermillion'
1042          * <li> 'reddishpurple'
1043          * </ul>
1044          *
1045          * As substitutes for standard colors, it contains the following aliases:
1046          *
1047          * <ul>
1048          * <li> black (= #000000)
1049          * <li> blue (= darkblue)
1050          * <li> green (= bluishgreen)
1051          * <li> purple (= reddishpurple)
1052          * <li> red (= vermillion)
1053          * <li> white (= #ffffff)
1054          * </ul>
1055          *
1056          * See <a href="https://www.nature.com/articles/nmeth.1618">Bang Wong: "Points of view: Color blindness"</a>
1057          * and
1058          * <a href="https://davidmathlogic.com/colorblind/">https://davidmathlogic.com/colorblind/</a>.
1059          *
1060          * @name JXG.paletteWong
1061          * @type Object
1062          * @see JXG.palette
1063          * @example
1064          * var p = board.create('line', [[-1, 1], [2, -3]], {strokeColor: JXG.paletteWong.yellow});
1065          */
1066         paletteWong: {
1067             black: "#000000",
1068             orange: "#E69F00",
1069             skyblue: "#56B4E9",
1070             bluishgreen: "#009E73",
1071             yellow: "#F0E442",
1072             darkblue: "#0072B2",
1073             vermillion: "#D55E00",
1074             reddishpurple: "#CC79A7",
1075 
1076             blue: "#0072B2",
1077             red: "#D55E00", // vermillion
1078             green: "#009E73", // bluishgreen
1079             purple: "#CC79A7", // reddishpurple
1080             white: "#ffffff"
1081         }
1082     }
1083 );
1084 
1085 /**
1086  * Default color palette.
1087  * Contains at least color values for
1088  * <ul>
1089  * <li> black
1090  * <li> blue
1091  * <li> green
1092  * <li> purple
1093  * <li> red
1094  * <li> white
1095  * <li> yellow
1096  * </ul>
1097  *
1098  * @name JXG.palette
1099  * @type Object
1100  * @default JXG.paletteWong
1101  * @see JXG.paletteWong
1102  *
1103  * @example
1104  *
1105  * var p = board.create('line', [[-1, 1], [2, -3]], {strokeColor: JXG.palette.yellow});
1106  *
1107  */
1108 JXG.palette = JXG.paletteWong;
1109 
1110 export default JXG;
1111