      1 /*
      2     Copyright 2008-2025
      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