1 /*
  2     Copyright 2008-2026
  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 HSL color.
403  * From {@link https://en.wikipedia.org/wiki/HSL_and_HSV}.
404  * @param {Number} H value between 0 and 360
405  * @param {Number} S value between 0.0 (shade of gray) to 1.0 (pure color)
406  * @param {Number} V value between 0.0 (black) to 1.0 (white)
407  * @returns {Array} Contains the numbers h, s, and l value in this order.
408  */
409 JXG.hsv2hsl = function(H, S, V) {
410     var SL,
411         L = V * (1 - S * 0.5);
412     SL = (L === 0 || L === 1) ? 0 : ((V - L ) / Math.min(L, 1 - L));
413     return [H, SL, L];
414 };
415 
416 /**
417  * Converts HSV color to RGB color.
418  * Based on C Code in "Computer Graphics -- Principles and Practice,"
419  * Foley et al, 1996, p. 593.
420  * See also https://www.had2know.org/technology/hsv-rgb-conversion-formula-calculator.html
421  * @param {Number} H value between 0 and 360
422  * @param {Number} S value between 0.0 (shade of gray) to 1.0 (pure color)
423  * @param {Number} V value between 0.0 (black) to 1.0 (white)
424  * @returns {String} RGB color string
425  */
426 JXG.hsv2rgb = function (H, S, V) {
427     var R, G, B, f, i, hTemp, p, q, t;
428 
429     H = ((H % 360.0) + 360.0) % 360;
430 
431     if (S === 0) {
432         if (isNaN(H) || H < Mat.eps) {
433             R = V;
434             G = V;
435             B = V;
436         } else {
437             return "#ffffff";
438         }
439     } else {
440         if (H >= 360) {
441             hTemp = 0.0;
442         } else {
443             hTemp = H;
444         }
445 
446         // h is now IN [0,6)
447         hTemp = hTemp / 60;
448         // largest integer <= h
449         i = Math.floor(hTemp);
450         // fractional part of h
451         f = hTemp - i;
452         p = V * (1.0 - S);
453         q = V * (1.0 - S * f);
454         t = V * (1.0 - S * (1.0 - f));
455 
456         switch (i) {
457             case 0:
458                 R = V;
459                 G = t;
460                 B = p;
461                 break;
462             case 1:
463                 R = q;
464                 G = V;
465                 B = p;
466                 break;
467             case 2:
468                 R = p;
469                 G = V;
470                 B = t;
471                 break;
472             case 3:
473                 R = p;
474                 G = q;
475                 B = V;
476                 break;
477             case 4:
478                 R = t;
479                 G = p;
480                 B = V;
481                 break;
482             case 5:
483                 R = V;
484                 G = p;
485                 B = q;
486                 break;
487         }
488     }
489 
490     R = Math.round(R * 255).toString(16);
491     R = R.length === 2 ? R : R.length === 1 ? "0" + R : '00';
492     G = Math.round(G * 255).toString(16);
493     G = G.length === 2 ? G : G.length === 1 ? "0" + G : '00';
494     B = Math.round(B * 255).toString(16);
495     B = B.length === 2 ? B : B.length === 1 ? "0" + B : '00';
496 
497     return ["#", R, G, B].join("");
498 };
499 
500 /**
501  * Converts a color from the RGB color space into the HSV space. Input can be any valid HTML/CSS color definition.
502  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
503  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
504  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
505  * 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>.
506  * @param {Number} ag
507  * @param {Number} ab
508  * @returns {Array} Contains the numbers h, s, and v value in this order.
509  *
510  */
511 JXG.rgb2hsv = function (color, ag, ab) {
512     var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min;
513 
514     r = JXG.rgbParser(color, ag, ab);
515 
516     g = r[1];
517     b = r[2];
518     r = r[0];
519     fr = r / 255.0;
520     fg = g / 255.0;
521     fb = b / 255.0;
522     max = Math.max(r, g, b);
523     min = Math.min(r, g, b);
524     fmax = max / 255.0;
525     fmin = min / 255.0;
526 
527     v = fmax;
528     s = 0.0;
529 
530     if (v > 0) {
531         s = (v - fmin) / v;
532     }
533 
534     h = 1.0 / (fmax - fmin);
535 
536     if (s > 0) {
537         if (max === r) {
538             h = (fg - fb) * h;
539         } else if (max === g) {
540             h = 2 + (fb - fr) * h;
541         } else {
542             h = 4 + (fr - fg) * h;
543         }
544     }
545 
546     h *= 60;
547 
548     if (h < 0) {
549         h += 360;
550     }
551 
552     if (max === min) {
553         h = 0.0;
554     }
555 
556     return [h, s, v];
557 };
558 
559 /**
560  * Converts a color from the RGB color space into the LMS space. Input can be any valid HTML/CSS color definition.
561  * @param {String|Array|Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
562  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
563  * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
564  * expects the parameters ag and ab.
565  * @param {Number} ag
566  * @param {Number} ab
567  * @returns {Array} Contains the l, m, and s value in this order.
568  */
569 JXG.rgb2LMS = function (color, ag, ab) {
570     var r,
571         g,
572         b,
573         l,
574         m,
575         s,
576         ret,
577         // constants
578         matrix = [
579             [0.05059983, 0.08585369, 0.0095242],
580             [0.01893033, 0.08925308, 0.01370054],
581             [0.00292202, 0.00975732, 0.07145979]
582         ];
583 
584     r = JXG.rgbParser(color, ag, ab);
585     g = r[1];
586     b = r[2];
587     r = r[0];
588 
589     // de-gamma
590     // Maybe this can be made faster by using a cache
591     r = Math.pow(r, 0.476190476);
592     g = Math.pow(g, 0.476190476);
593     b = Math.pow(b, 0.476190476);
594 
595     l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2];
596     m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2];
597     s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2];
598 
599     ret = [l, m, s];
600     ret.l = l;
601     ret.m = m;
602     ret.s = s;
603 
604     return ret;
605 };
606 
607 /**
608  * Convert color information from LMS to RGB color space.
609  * @param {Number} l
610  * @param {Number} m
611  * @param {Number} s
612  * @returns {Array} Contains the r, g, and b value in this order.
613  */
614 JXG.LMS2rgb = function (l, m, s) {
615     var r,
616         g,
617         b,
618         ret,
619         // constants
620         matrix = [
621             [30.830854, -29.832659, 1.610474],
622             [-6.481468, 17.715578, -2.532642],
623             [-0.37569, -1.199062, 14.273846]
624         ],
625         // re-gamma, inspired by GIMP modules/display-filter-color-blind.c:
626         // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>,
627         //                         Sven Neumann <sven@gimp.org>,
628         //                         Robert Dougherty <bob@vischeck.com> and
629         //                         Alex Wade <alex@vischeck.com>
630         // This code is an implementation of an algorithm described by Hans Brettel,
631         // Francoise Vienot and John Mollon in the Journal of the Optical Society of
632         // America V14(10), pg 2647. (See http://vischeck.com/ for more info.)
633         lut_lookup = function (value) {
634             var offset = 127,
635                 step = 64;
636 
637             while (step > 0) {
638                 if (Math.pow(offset, 0.476190476) > value) {
639                     offset -= step;
640                 } else {
641                     if (Math.pow(offset + 1, 0.476190476) > value) {
642                         return offset;
643                     }
644 
645                     offset += step;
646                 }
647 
648                 step /= 2;
649             }
650 
651             /*  the algorithm above can't reach 255  */
652             if (offset === 254 && 13.994955247 < value) {
653                 return 255;
654             }
655 
656             return offset;
657         };
658 
659     // transform back to rgb
660     r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2];
661     g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2];
662     b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2];
663 
664     r = lut_lookup(r);
665     g = lut_lookup(g);
666     b = lut_lookup(b);
667 
668     ret = [r, g, b];
669     ret.r = r;
670     ret.g = g;
671     ret.b = b;
672 
673     return ret;
674 };
675 
676 /**
677  * Splits a RGBA color value like #112233AA into it's RGB and opacity parts.
678  * @param {String} rgba A RGBA color value
679  * @returns {Array} An array containing the rgb color value in the first and the opacity in the second field.
680  */
681 JXG.rgba2rgbo = function (rgba) {
682     var opacity;
683 
684     if (rgba.length === 9 && rgba.charAt(0) === "#") {
685         opacity = parseInt(rgba.slice(7, 9).toUpperCase(), 16) / 255;
686         rgba = rgba.slice(0, 7);
687     } else {
688         opacity = 1;
689     }
690 
691     return [rgba, opacity];
692 };
693 
694 /**
695  * Generates a RGBA color value like #112233AA from it's RGB and opacity parts.
696  * @param {String|Array} rgb A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black'
697  * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
698  * from 0 to 255. They will be interpreted as red, green, and blue values.
699  * @param {Number} o The desired opacity >=0, <=1.
700  * @returns {String} The RGBA color value.
701  */
702 JXG.rgbo2rgba = function (rgb, o) {
703     var rgba;
704 
705     if (rgb === "none" || rgb === 'transparent') {
706         return rgb;
707     }
708 
709     rgba = Math.round(o * 255).toString(16);
710     if (rgba.length === 1) {
711         rgba = "0" + rgba;
712     }
713 
714     return JXG.rgb2hex(rgb) + rgba;
715 };
716 
717 /**
718  * Decolorizes the given color.
719  * @param {String} color HTML string containing the HTML color code.
720  * @returns {String} Returns a HTML color string
721  */
722 JXG.rgb2bw = function (color) {
723     var x,
724         tmp,
725         arr,
726         HexChars = '0123456789ABCDEF';
727 
728     if (color === 'none') {
729         return color;
730     }
731 
732     arr = JXG.rgbParser(color);
733     x = Math.floor(0.3 * arr[0] + 0.59 * arr[1] + 0.11 * arr[2]);
734 
735     // rgbParser and Math.floor ensure that x is 0 <= x <= 255.
736     // Bitwise operators can be used.
737     /*jslint bitwise: true*/
738     tmp = HexChars.charAt((x >> 4) & 0xf) + HexChars.charAt(x & 0xf);
739 
740     color = "#" + tmp + tmp + tmp;
741 
742     return color;
743 };
744 
745 /**
746  * Converts a color into how a colorblind human approximately would see it.
747  * @param {String} color HTML string containing the HTML color code.
748  * @param {String} deficiency The type of color blindness. Possible
749  * options are <i>protanopia</i>, <i>deuteranopia</i>, and <i>tritanopia</i>.
750  * @returns {String} Returns a HTML color string
751  */
752 JXG.rgb2cb = function (color, deficiency) {
753     var rgb,
754         l,
755         m,
756         s,
757         lms,
758         tmp,
759         a1,
760         b1,
761         c1,
762         a2,
763         b2,
764         c2,
765         inflection,
766         HexChars = '0123456789ABCDEF';
767 
768     if (color === 'none') {
769         return color;
770     }
771 
772     lms = JXG.rgb2LMS(color);
773     l = lms[0];
774     m = lms[1];
775     s = lms[2];
776 
777     deficiency = deficiency.toLowerCase();
778 
779     switch (deficiency) {
780         case "protanopia":
781             a1 = -0.06150039994295001;
782             b1 = 0.08277001656812001;
783             c1 = -0.013200141220000003;
784             a2 = 0.05858939668799999;
785             b2 = -0.07934519995360001;
786             c2 = 0.013289415272000003;
787             inflection = 0.6903216543277437;
788 
789             tmp = s / m;
790 
791             if (tmp < inflection) {
792                 l = -(b1 * m + c1 * s) / a1;
793             } else {
794                 l = -(b2 * m + c2 * s) / a2;
795             }
796             break;
797         case "tritanopia":
798             a1 = -0.00058973116217;
799             b1 = 0.007690316482;
800             c1 = -0.01011703519052;
801             a2 = 0.025495080838999994;
802             b2 = -0.0422740347;
803             c2 = 0.017005316784;
804             inflection = 0.8349489908460004;
805 
806             tmp = m / l;
807 
808             if (tmp < inflection) {
809                 s = -(a1 * l + b1 * m) / c1;
810             } else {
811                 s = -(a2 * l + b2 * m) / c2;
812             }
813             break;
814         default:
815             a1 = -0.06150039994295001;
816             b1 = 0.08277001656812001;
817             c1 = -0.013200141220000003;
818             a2 = 0.05858939668799999;
819             b2 = -0.07934519995360001;
820             c2 = 0.013289415272000003;
821             inflection = 0.5763833686400911;
822 
823             tmp = s / l;
824 
825             if (tmp < inflection) {
826                 m = -(a1 * l + c1 * s) / b1;
827             } else {
828                 m = -(a2 * l + c2 * s) / b2;
829             }
830             break;
831     }
832 
833     rgb = JXG.LMS2rgb(l, m, s);
834 
835     // LMS2rgb returns an array of values ranging from 0 to 255 (both included)
836     // bitwise operators are safe to use.
837     /*jslint bitwise: true*/
838     tmp = HexChars.charAt((rgb[0] >> 4) & 0xf) + HexChars.charAt(rgb[0] & 0xf);
839     color = "#" + tmp;
840     tmp = HexChars.charAt((rgb[1] >> 4) & 0xf) + HexChars.charAt(rgb[1] & 0xf);
841     color += tmp;
842     tmp = HexChars.charAt((rgb[2] >> 4) & 0xf) + HexChars.charAt(rgb[2] & 0xf);
843     color += tmp;
844 
845     return color;
846 };
847 
848 /**
849  * Lightens (percent > 0) or darkens (percent < 0) the color by the specified factor.
850  * @param {String} color
851  * @param {Number} percent
852  * @returns {String}
853  */
854 JXG.shadeColor = function (color, percent) {
855     var arr = JXG.rgbParser(color),
856         r = arr[0],
857         g = arr[1],
858         b = arr[2];
859 
860     r = parseInt(r + 255 * percent);
861     g = parseInt(g + 255 * percent);
862     b = parseInt(b + 255 * percent);
863 
864     r = (r > 0) ? r : 0;
865     g = (g > 0) ? g : 0;
866     b = (b > 0) ? b : 0;
867 
868     r = (r < 255) ? r : 255;
869     g = (g < 255) ? g : 255;
870     b = (b < 255) ? b : 255;
871 
872     r = Math.round(r);
873     g = Math.round(g);
874     b = Math.round(b);
875 
876     return JXG.rgb2hex([r, g, b]);
877 };
878 
879 /**
880  * Lightens the color by the specified factor.
881  * @param {String} color
882  * @param {Number} percent
883  * @returns {String}
884  *
885  * @see JXG.shadeColor
886  */
887 JXG.lightenColor = function (color, percent) {
888     return JXG.shadeColor(color, percent);
889 };
890 
891 /**
892  * Darkens the color by the specified factor.
893  * @param {String} color
894  * @param {Number} percent
895  * @returns {String}
896  *
897  * @see JXG.shadeColor
898  */
899 JXG.darkenColor = function (color, percent) {
900     return JXG.shadeColor(color, -1 * percent);
901 };
902 
903 /**
904  * Mix two colors together in variable proportion. Opacity is NOT included in the calculations.
905  * @param {String} color1
906  * @param {String} color2
907  * @param {Number} [percent=0.5] Balance point in percent.
908  * @returns {String}
909  */
910 JXG.mixColor = function (color1, color2, percent) {
911     var rgb1 = JXG.rgbParser(color1),
912         rgb2 = JXG.rgbParser(color2),
913         rgb = [],
914         i;
915 
916     // percent = percent ?? 0.5;
917     percent = (percent !== undefined && percent !== null) ? percent : 0.5;
918 
919     for (i = 0; i < 3; i++) {
920         rgb[i] = parseInt(rgb1[i] * percent + rgb2[i] * (1 - percent));
921     }
922 
923     return JXG.rgb2hex(rgb);
924 };
925 
926 /**
927  * Determines highlight color to a given color. Done by reducing (or increasing) the opacity.
928  * @param {String} color HTML RGBA string containing the HTML color code.
929  * @returns {String} Returns a HTML RGBA color string
930  */
931 JXG.autoHighlight = function (colstr) {
932     var col = JXG.rgba2rgbo(colstr),
933         c = col[0],
934         opa = col[1];
935 
936     if (colstr.charAt(0) === "#") {
937         if (opa < 0.3) {
938             opa *= 1.8;
939         } else {
940             opa *= 0.4;
941         }
942 
943         return JXG.rgbo2rgba(c, opa);
944     }
945 
946     return colstr;
947 };
948 
949 /**
950  * Calculate whether a light or a dark color is needed as a contrast.
951  * Especially useful to determine whether white or black font goes
952  * better with a given background color.
953  * @param {String} hexColor HEX value of color.
954  * @param {String} [darkColor="#000000"] HEX string for a dark color.
955  * @param {String} [lightColor="#ffffff"] HEX string for a light color.
956  * @param {Number} [threshold=8]
957  * @returns {String} Returns darkColor or lightColor.
958  */
959 JXG.contrast = function (hexColor, darkColor, lightColor, threshold) {
960     var rgb,
961         black = "#000000",
962         rgbBlack,
963         l1,
964         l2,
965         contrastRatio;
966 
967     darkColor = darkColor || "#000000";
968     lightColor = lightColor || "#ffffff";
969     threshold = threshold || 7;
970 
971     // hexColor RGB
972     rgb = JXG.rgbParser(hexColor);
973 
974     // Black RGB
975     rgbBlack = JXG.rgbParser(black);
976 
977     // Calc contrast ratio
978     l1 =
979         0.2126 * Math.pow(rgb[0] / 255, 2.2) +
980         0.7152 * Math.pow(rgb[1] / 255, 2.2) +
981         0.0722 * Math.pow(rgb[2] / 255, 2.2);
982 
983     l2 =
984         0.2126 * Math.pow(rgbBlack[0] / 255, 2.2) +
985         0.7152 * Math.pow(rgbBlack[1] / 255, 2.2) +
986         0.0722 * Math.pow(rgbBlack[2] / 255, 2.2);
987 
988     if (l1 > l2) {
989         contrastRatio = Math.floor((l1 + 0.05) / (l2 + 0.05));
990     } else {
991         contrastRatio = Math.floor((l2 + 0.05) / (l1 + 0.05));
992     }
993     contrastRatio = contrastRatio - 1;
994 
995     // If contrast is more than threshold, return darkColor
996     if (contrastRatio > threshold) {
997         return darkColor;
998     }
999     // if not, return lightColor.
1000     return lightColor;
1001 };
1002 
1003 /**
1004  * Use the color scheme of JSXGraph up to version 1.3.2.
1005  * This method has to be called before JXG.JSXGraph.initBoard();
1006  *
1007  * @see JXG.palette
1008  * @see JXG.paletteWong
1009  *
1010  * @example
1011  *
1012  * JXG.setClassicColors();
1013  * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-5, 5, 5,-5]});
1014  *
1015  */
1016 JXG.setClassicColors = function () {
1017     JXG.Options.elements.strokeColor = 'blue';
1018     JXG.Options.elements.fillColor = 'red';
1019     JXG.Options.hatch.strokeColor = 'blue';
1020     JXG.Options.angle.fillColor = "#ff7f00";
1021     JXG.Options.angle.highlightFillColor = "#ff7f00";
1022     JXG.Options.angle.strokeColor = "#ff7f00";
1023     JXG.Options.angle.label.strokeColor = 'blue';
1024     JXG.Options.arc.strokeColor = 'blue';
1025     JXG.Options.circle.center.fillColor = 'red';
1026     JXG.Options.circle.center.strokeColor = 'blue';
1027     JXG.Options.circumcircle.strokeColor = 'blue';
1028     JXG.Options.circumcircle.center.fillColor = 'red';
1029     JXG.Options.circumcircle.center.strokeColor = 'blue';
1030     JXG.Options.circumcirclearc.strokeColor = 'blue';
1031     JXG.Options.circumcirclesector.strokeColor = 'blue';
1032     JXG.Options.circumcirclesector.fillColor = 'green';
1033     JXG.Options.circumcirclesector.highlightFillColor = 'green';
1034     JXG.Options.conic.strokeColor = 'blue';
1035     JXG.Options.curve.strokeColor = 'blue';
1036     JXG.Options.incircle.strokeColor = 'blue';
1037     JXG.Options.incircle.center.fillColor = 'red';
1038     JXG.Options.incircle.center.strokeColor = 'blue';
1039     JXG.Options.inequality.fillColor = 'red';
1040     JXG.Options.integral.fillColor = 'red';
1041     JXG.Options.integral.curveLeft.color = 'red';
1042     JXG.Options.integral.curveRight.color = 'red';
1043     JXG.Options.line.strokeColor = 'blue';
1044     JXG.Options.point.fillColor = 'red';
1045     JXG.Options.point.strokeColor = 'red';
1046     JXG.Options.polygon.fillColor = 'green';
1047     JXG.Options.polygon.highlightFillColor = 'green';
1048     JXG.Options.polygon.vertices.strokeColor = 'red';
1049     JXG.Options.polygon.vertices.fillColor = 'red';
1050     JXG.Options.regularpolygon.fillColor = 'green';
1051     JXG.Options.regularpolygon.highlightFillColor = 'green';
1052     JXG.Options.regularpolygon.vertices.strokeColor = 'red';
1053     JXG.Options.regularpolygon.vertices.fillColor = 'red';
1054     JXG.Options.riemannsum.fillColor = 'yellow';
1055     JXG.Options.sector.fillColor = 'green';
1056     JXG.Options.sector.highlightFillColor = 'green';
1057     JXG.Options.semicircle.center.fillColor = 'red';
1058     JXG.Options.semicircle.center.strokeColor = 'blue';
1059     JXG.Options.slopetriangle.fillColor = 'red';
1060     JXG.Options.slopetriangle.highlightFillColor = 'red';
1061     JXG.Options.turtle.arrow.strokeColor = 'blue';
1062 };
1063 
1064 JXG.extend(
1065     JXG,
1066     /** @lends JXG */ {
1067         /**
1068          * Bang Wong color palette,
1069          * optimized for various type
1070          * of color blindness.
1071          * It contains values for
1072          * <ul>
1073          * <li> 'black'
1074          * <li> 'orange'
1075          * <li> 'skyblue'
1076          * <li> 'bluishgreen'
1077          * <li> 'yellow'
1078          * <li> 'darkblue'
1079          * <li> 'vermillion'
1080          * <li> 'reddishpurple'
1081          * </ul>
1082          *
1083          * As substitutes for standard colors, it contains the following aliases:
1084          *
1085          * <ul>
1086          * <li> black (= #000000)
1087          * <li> blue (= darkblue)
1088          * <li> green (= bluishgreen)
1089          * <li> purple (= reddishpurple)
1090          * <li> red (= vermillion)
1091          * <li> white (= #ffffff)
1092          * </ul>
1093          *
1094          * See <a href="https://www.nature.com/articles/nmeth.1618">Bang Wong: "Points of view: Color blindness"</a>
1095          * and
1096          * <a href="https://davidmathlogic.com/colorblind/">https://davidmathlogic.com/colorblind/</a>.
1097          *
1098          * @name JXG.paletteWong
1099          * @type Object
1100          * @see JXG.palette
1101          * @example
1102          * var p = board.create('line', [[-1, 1], [2, -3]], {strokeColor: JXG.paletteWong.yellow});
1103          */
1104         paletteWong: {
1105             black: "#000000",
1106             orange: "#E69F00",
1107             skyblue: "#56B4E9",
1108             bluishgreen: "#009E73",
1109             yellow: "#F0E442",
1110             darkblue: "#0072B2",
1111             vermillion: "#D55E00",
1112             reddishpurple: "#CC79A7",
1113 
1114             blue: "#0072B2",
1115             red: "#D55E00", // vermillion
1116             green: "#009E73", // bluishgreen
1117             purple: "#CC79A7", // reddishpurple
1118             white: "#ffffff"
1119         }
1120     }
1121 );
1122 
1123 /**
1124  * Default color palette.
1125  * Contains at least color values for
1126  * <ul>
1127  * <li> black
1128  * <li> blue
1129  * <li> green
1130  * <li> purple
1131  * <li> red
1132  * <li> white
1133  * <li> yellow
1134  * </ul>
1135  *
1136  * @name JXG.palette
1137  * @type Object
1138  * @default JXG.paletteWong
1139  * @see JXG.paletteWong
1140  *
1141  * @example
1142  *
1143  * var p = board.create('line', [[-1, 1], [2, -3]], {strokeColor: JXG.palette.yellow});
1144  *
1145  */
1146 JXG.palette = JXG.paletteWong;
1147 
1148 export default JXG;
1149