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