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