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