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