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