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.substr(1, 6); 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.substr(7, 2).toUpperCase(), 16) / 255; 664 rgba = rgba.substr(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