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