1 /** 2 * Functions for color conversions. Based on a class to parse color values by Stoyan Stefanov <sstoo@gmail.com> 3 * @see http://www.phpied.com/rgb-color-parser-in-javascript/ 4 */ 5 6 /** 7 * Converts a valid HTML/CSS color string into a rgb value array. This is the base 8 * function for the following wrapper functions which only adjust the output to 9 * different flavors like an object, string or hex values. 10 * @parameter {string} color_string A valid HTML or CSS styled color value, e.g. #12ab21, #abc, black, or rgb(12, 132, 233) <strong>or</string> 11 * @parameter {array} color_array Array containing three color values either from 0.0 to 1.0 or from 0 to 255. They will be interpreted as red, green, and blue values <strong>OR</strong> 12 * @parameter {number} r,g,b Three color values r, g, and b like those in the array variant. 13 * @returns {Array} RGB color values as an array [r, g, b] which component's are between 0 and 255. 14 */ 15 JXG.rgbParser = function() { 16 var color_string, 17 testFloat = false, 18 i, 19 r, g, b; 20 21 if(arguments.length === 0) { 22 return []; 23 } 24 25 if(arguments.length >= 3) { 26 arguments[0] = [arguments[0], arguments[1], arguments[2]]; 27 arguments.length = 1; 28 } 29 30 color_string = arguments[0]; 31 if(JXG.isArray(color_string)) { 32 for(i = 0; i < 3; i++) { 33 testFloat |= /\./.test(arguments[0][i].toString()); 34 } 35 36 for(i = 0; i < 3; i++) { 37 testFloat &= (arguments[0][i] >= 0.0) & (arguments[0][i] <= 1.0); 38 } 39 40 if(testFloat) 41 return [Math.ceil(arguments[0][0] * 255), Math.ceil(arguments[0][1] * 255), Math.ceil(arguments[0][2] * 255)]; 42 else { 43 arguments[0].length = 3; 44 return arguments[0]; 45 } 46 } else if(typeof arguments[0] === 'string') { 47 color_string = arguments[0]; 48 } 49 50 // strip any leading # 51 if (color_string.charAt(0) == '#') { // remove # if any 52 color_string = color_string.substr(1,6); 53 } 54 55 color_string = color_string.replace(/ /g,'').toLowerCase(); 56 57 // before getting into regexps, try simple matches 58 // and overwrite the input 59 var simple_colors = { 60 aliceblue: 'f0f8ff', 61 antiquewhite: 'faebd7', 62 aqua: '00ffff', 63 aquamarine: '7fffd4', 64 azure: 'f0ffff', 65 beige: 'f5f5dc', 66 bisque: 'ffe4c4', 67 black: '000000', 68 blanchedalmond: 'ffebcd', 69 blue: '0000ff', 70 blueviolet: '8a2be2', 71 brown: 'a52a2a', 72 burlywood: 'deb887', 73 cadetblue: '5f9ea0', 74 chartreuse: '7fff00', 75 chocolate: 'd2691e', 76 coral: 'ff7f50', 77 cornflowerblue: '6495ed', 78 cornsilk: 'fff8dc', 79 crimson: 'dc143c', 80 cyan: '00ffff', 81 darkblue: '00008b', 82 darkcyan: '008b8b', 83 darkgoldenrod: 'b8860b', 84 darkgray: 'a9a9a9', 85 darkgreen: '006400', 86 darkkhaki: 'bdb76b', 87 darkmagenta: '8b008b', 88 darkolivegreen: '556b2f', 89 darkorange: 'ff8c00', 90 darkorchid: '9932cc', 91 darkred: '8b0000', 92 darksalmon: 'e9967a', 93 darkseagreen: '8fbc8f', 94 darkslateblue: '483d8b', 95 darkslategray: '2f4f4f', 96 darkturquoise: '00ced1', 97 darkviolet: '9400d3', 98 deeppink: 'ff1493', 99 deepskyblue: '00bfff', 100 dimgray: '696969', 101 dodgerblue: '1e90ff', 102 feldspar: 'd19275', 103 firebrick: 'b22222', 104 floralwhite: 'fffaf0', 105 forestgreen: '228b22', 106 fuchsia: 'ff00ff', 107 gainsboro: 'dcdcdc', 108 ghostwhite: 'f8f8ff', 109 gold: 'ffd700', 110 goldenrod: 'daa520', 111 gray: '808080', 112 green: '008000', 113 greenyellow: 'adff2f', 114 honeydew: 'f0fff0', 115 hotpink: 'ff69b4', 116 indianred : 'cd5c5c', 117 indigo : '4b0082', 118 ivory: 'fffff0', 119 khaki: 'f0e68c', 120 lavender: 'e6e6fa', 121 lavenderblush: 'fff0f5', 122 lawngreen: '7cfc00', 123 lemonchiffon: 'fffacd', 124 lightblue: 'add8e6', 125 lightcoral: 'f08080', 126 lightcyan: 'e0ffff', 127 lightgoldenrodyellow: 'fafad2', 128 lightgrey: 'd3d3d3', 129 lightgreen: '90ee90', 130 lightpink: 'ffb6c1', 131 lightsalmon: 'ffa07a', 132 lightseagreen: '20b2aa', 133 lightskyblue: '87cefa', 134 lightslateblue: '8470ff', 135 lightslategray: '778899', 136 lightsteelblue: 'b0c4de', 137 lightyellow: 'ffffe0', 138 lime: '00ff00', 139 limegreen: '32cd32', 140 linen: 'faf0e6', 141 magenta: 'ff00ff', 142 maroon: '800000', 143 mediumaquamarine: '66cdaa', 144 mediumblue: '0000cd', 145 mediumorchid: 'ba55d3', 146 mediumpurple: '9370d8', 147 mediumseagreen: '3cb371', 148 mediumslateblue: '7b68ee', 149 mediumspringgreen: '00fa9a', 150 mediumturquoise: '48d1cc', 151 mediumvioletred: 'c71585', 152 midnightblue: '191970', 153 mintcream: 'f5fffa', 154 mistyrose: 'ffe4e1', 155 moccasin: 'ffe4b5', 156 navajowhite: 'ffdead', 157 navy: '000080', 158 oldlace: 'fdf5e6', 159 olive: '808000', 160 olivedrab: '6b8e23', 161 orange: 'ffa500', 162 orangered: 'ff4500', 163 orchid: 'da70d6', 164 palegoldenrod: 'eee8aa', 165 palegreen: '98fb98', 166 paleturquoise: 'afeeee', 167 palevioletred: 'd87093', 168 papayawhip: 'ffefd5', 169 peachpuff: 'ffdab9', 170 peru: 'cd853f', 171 pink: 'ffc0cb', 172 plum: 'dda0dd', 173 powderblue: 'b0e0e6', 174 purple: '800080', 175 red: 'ff0000', 176 rosybrown: 'bc8f8f', 177 royalblue: '4169e1', 178 saddlebrown: '8b4513', 179 salmon: 'fa8072', 180 sandybrown: 'f4a460', 181 seagreen: '2e8b57', 182 seashell: 'fff5ee', 183 sienna: 'a0522d', 184 silver: 'c0c0c0', 185 skyblue: '87ceeb', 186 slateblue: '6a5acd', 187 slategray: '708090', 188 snow: 'fffafa', 189 springgreen: '00ff7f', 190 steelblue: '4682b4', 191 tan: 'd2b48c', 192 teal: '008080', 193 thistle: 'd8bfd8', 194 tomato: 'ff6347', 195 turquoise: '40e0d0', 196 violet: 'ee82ee', 197 violetred: 'd02090', 198 wheat: 'f5deb3', 199 white: 'ffffff', 200 whitesmoke: 'f5f5f5', 201 yellow: 'ffff00', 202 yellowgreen: '9acd32' 203 }; 204 color_string = simple_colors[color_string] || color_string; 205 // end of simple type-in colors 206 207 // array of color definition objects 208 var color_defs = [ 209 { 210 re: /^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]{1,3})\s*\)\s*$/, 211 example: ['rgba(123, 234, 45, 0.5)', 'rgba(255,234,245,1.0)'], 212 process: function (bits){ 213 return [ 214 parseInt(bits[1]), 215 parseInt(bits[2]), 216 parseInt(bits[3]) 217 ]; 218 } 219 }, 220 { 221 re: /^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$/, 222 example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], 223 process: function (bits){ 224 return [ 225 parseInt(bits[1]), 226 parseInt(bits[2]), 227 parseInt(bits[3]) 228 ]; 229 } 230 }, 231 { 232 re: /^(\w{2})(\w{2})(\w{2})$/, 233 example: ['#00ff00', '336699'], 234 process: function (bits){ 235 return [ 236 parseInt(bits[1], 16), 237 parseInt(bits[2], 16), 238 parseInt(bits[3], 16) 239 ]; 240 } 241 }, 242 { 243 re: /^(\w{1})(\w{1})(\w{1})$/, 244 example: ['#fb0', 'f0f'], 245 process: function (bits){ 246 return [ 247 parseInt(bits[1] + bits[1], 16), 248 parseInt(bits[2] + bits[2], 16), 249 parseInt(bits[3] + bits[3], 16) 250 ]; 251 } 252 } 253 ]; 254 255 // search through the definitions to find a match 256 for (i = 0; i < color_defs.length; i++) { 257 var re = color_defs[i].re, 258 processor = color_defs[i].process, 259 bits = re.exec(color_string), 260 channels; 261 if (bits) { 262 channels = processor(bits); 263 r = channels[0]; 264 g = channels[1]; 265 b = channels[2]; 266 } 267 268 } 269 270 if (isNaN(r) || isNaN(g) || isNaN(b)) { 271 return []; 272 } 273 274 // validate/cleanup values 275 r = (r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r); 276 g = (g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g); 277 b = (b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b); 278 279 return [r, g, b]; 280 }; 281 282 /** 283 * Returns output of JXG.rgbParser as a CSS styled rgb() string. 284 */ 285 JXG.rgb2css = function () { 286 var r, g, b; 287 r = JXG.rgbParser.apply(JXG.rgbParser, arguments); 288 g = r[1]; 289 b = r[2]; 290 r = r[0]; 291 return 'rgb(' + r + ', ' + g + ', ' + b + ')'; 292 }; 293 294 /** 295 * Returns array returned by JXG.rgbParser as a HTML rgb string. 296 */ 297 JXG.rgb2hex = function () { 298 var r, g, b; 299 r = JXG.rgbParser.apply(JXG.rgbParser, arguments); 300 g = r[1]; 301 b = r[2]; 302 r = r[0]; 303 r = r.toString(16); 304 g = g.toString(16); 305 b = b.toString(16); 306 if (r.length == 1) r = '0' + r; 307 if (g.length == 1) g = '0' + g; 308 if (b.length == 1) b = '0' + b; 309 return '#' + r + g + b; 310 }; 311 312 JXG.hex2rgb = function (hex) { 313 var r, g, b; 314 if (hex.charAt(0) == '#') 315 hex = hex.slice(1); 316 317 r = parseInt(hex.substr(0, 2), 16); 318 g = parseInt(hex.substr(2, 2), 16); 319 b = parseInt(hex.substr(4, 2), 16); 320 321 return 'rgb(' + r + ', ' + g + ', ' + b + ')'; 322 }; 323 324 /** 325 * Converts HSV color to RGB color. 326 * Based on C Code in "Computer Graphics -- Principles and Practice," 327 * Foley et al, 1996, p. 593. 328 * See also http://www.efg2.com/Lab/Graphics/Colors/HSV.htm 329 * @param {float} H value between 0 and 360 330 * @param {float} S value between 0.0 (shade of gray) to 1.0 (pure color) 331 * @param {float} V value between 0.0 (black) to 1.0 (white) 332 * @return {string} RGB color string 333 */ 334 JXG.hsv2rgb = function(H,S,V) { 335 var R,G,B, f,i,hTemp, p,q,t; 336 H = ((H%360.0)+360.0)%360; 337 if (S==0) { 338 if (isNaN(H) || H < JXG.Math.eps) { 339 R = V; 340 G = V; 341 B = V; 342 } else { 343 return '#ffffff'; 344 } 345 } else { 346 if (H>=360) { 347 hTemp = 0.0; 348 } else { 349 hTemp = H; 350 } 351 hTemp = hTemp / 60; // h is now IN [0,6) 352 i = Math.floor(hTemp); // largest integer <= h 353 f = hTemp - i; // fractional part of h 354 p = V * (1.0 - S); 355 q = V * (1.0 - (S * f)); 356 t = V * (1.0 - (S * (1.0 - f))); 357 switch (i) { 358 case 0: R = V; G = t; B = p; break; 359 case 1: R = q; G = V; B = p; break; 360 case 2: R = p; G = V; B = t; break; 361 case 3: R = p; G = q; B = V; break; 362 case 4: R = t; G = p; B = V; break; 363 case 5: R = V; G = p; B = q; break; 364 } 365 } 366 R = Math.round(R*255).toString(16); R = (R.length==2)?R:((R.length==1)?'0'+R:'00'); 367 G = Math.round(G*255).toString(16); G = (G.length==2)?G:((G.length==1)?'0'+G:'00'); 368 B = Math.round(B*255).toString(16); B = (B.length==2)?B:((B.length==1)?'0'+B:'00'); 369 return ['#',R,G,B].join(''); 370 }; 371 372 /** 373 * Converts r, g, b color to h, s, v. 374 * See http://zach.in.tu-clausthal.de/teaching/cg1_0708/folien/13_color_3_4up.pdf for more information. 375 * @param {number} r Amount of red in color. Number between 0 and 255. 376 * @param {number} g Amount of green. Number between 0 and 255. 377 * @param {number} b Amount of blue. Number between 0 and 255. 378 * @type Object 379 * @return Hashmap containing h,s, and v field. 380 */ 381 JXG.rgb2hsv = function() { 382 var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min, stx; 383 r = JXG.rgbParser.apply(JXG.rgbParser, arguments); 384 g = r[1]; 385 b = r[2]; 386 r = r[0]; 387 stx = JXG.Math.Statistics; 388 fr = r/255.; 389 fg = g/255.; 390 fb = b/255.; 391 max = stx.max([r, g, b]); 392 min = stx.min([r, g, b]); 393 fmax = max/255.; 394 fmin = min/255.; 395 396 v = fmax; 397 398 s = 0.; 399 if(v>0) { 400 s = (v-fmin)/(v*1.); 401 } 402 403 h = 1./(fmax-fmin); 404 if(s > 0) { 405 if(max==r) 406 h = (fg-fb)*h; 407 else if(max==g) 408 h = 2 + (fb-fr)*h; 409 else 410 h = 4 + (fr-fg)*h; 411 } 412 413 h *= 60; 414 if(h < 0) 415 h += 360; 416 417 if(max==min) 418 h = 0.; 419 420 return [h, s, v]; 421 }; 422 423 424 /** 425 * Convert RGB color information to LMS color space. 426 * @param {number} r Amount of red in color. Number between 0 and 255. 427 * @param {number} g Amount of green. Number between 0 and 255. 428 * @param {number} b Amount of blue. Number between 0 and 255. 429 * @type Object 430 * @return Hashmap containing the L, M, S cone values. 431 */ 432 JXG.rgb2LMS = function() { 433 var r, g, b, l, m, s, ret, 434 // constants 435 matrix = [[0.05059983, 0.08585369, 0.00952420], [0.01893033, 0.08925308, 0.01370054], [0.00292202, 0.00975732, 0.07145979]]; 436 437 r = JXG.rgbParser.apply(JXG.rgbParser, arguments); 438 g = r[1]; 439 b = r[2]; 440 r = r[0]; 441 442 // de-gamma 443 // Maybe this can be made faster by using a cache 444 r = Math.pow(r, 0.476190476); 445 g = Math.pow(g, 0.476190476); 446 b = Math.pow(b, 0.476190476); 447 448 l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2]; 449 m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2]; 450 s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2]; 451 452 ret = [l, m, s]; 453 ret.l = l; 454 ret.m = m; 455 ret.s = s; 456 457 return ret; 458 }; 459 /** 460 * Convert color information from LMS to RGB color space. 461 * @param {number} l Amount of l value. 462 * @param {number} m Amount of m value. 463 * @param {number} s Amount of s value. 464 * @type Object 465 * @return Hashmap containing the r, g, b values. 466 */ 467 JXG.LMS2rgb = function(l, m, s) { 468 var r, g, b, ret, 469 // constants 470 matrix = [[30.830854, -29.832659, 1.610474], [-6.481468, 17.715578, -2.532642], [-0.375690, -1.199062, 14.273846]]; 471 472 // transform back to rgb 473 r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2]; 474 g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2]; 475 b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2]; 476 477 // re-gamma, inspired by GIMP modules/display-filter-color-blind.c: 478 // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>, 479 // Sven Neumann <sven@gimp.org>, 480 // Robert Dougherty <bob@vischeck.com> and 481 // Alex Wade <alex@vischeck.com> 482 // This code is an implementation of an algorithm described by Hans Brettel, 483 // Francoise Vienot and John Mollon in the Journal of the Optical Society of 484 // America V14(10), pg 2647. (See http://vischeck.com/ for more info.) 485 var lut_lookup = function (value) { 486 var offset = 127, step = 64; 487 488 while (step > 0) { 489 if (Math.pow(offset, 0.476190476) > value) { 490 offset -= step; 491 } else { 492 if (Math.pow(offset+1, 0.476190476) > value) 493 return offset; 494 495 offset += step; 496 } 497 498 step /= 2; 499 } 500 501 /* the algorithm above can't reach 255 */ 502 if (offset == 254 && 13.994955247 < value) 503 return 255; 504 505 return offset; 506 }; 507 508 509 r = lut_lookup(r); 510 g = lut_lookup(g); 511 b = lut_lookup(b); 512 513 ret = [r, g, b]; 514 ret.r = r; 515 ret.g = g; 516 ret.b = b; 517 518 return ret; 519 }; 520 521 /** 522 * Splits a RGBA color value like #112233AA into it's RGB and opacity parts. 523 * @param {String} rgba A RGBA color value 524 * @returns {Array} An array containing the rgb color value in the first and the opacity in the second field. 525 */ 526 JXG.rgba2rgbo = function (rgba) { 527 var opacity; 528 529 if (rgba.length == 9 && rgba.charAt(0) == '#') { 530 opacity = parseInt(rgba.substr(7, 2).toUpperCase(), 16) / 255; 531 rgba = rgba.substr(0, 7); 532 } else { 533 opacity = 1; 534 } 535 536 return [rgba, opacity]; 537 }; 538 539 /** 540 * Generates a RGBA color value like #112233AA from it's RGB and opacity parts. 541 * @param {String} rgb A RGB color value. 542 * @param {Float} o The desired opacity >=0, <=1. 543 * @returns {String} The RGBA color value. 544 */ 545 JXG.rgbo2rgba = function (rgb, o) { 546 var rgba; 547 548 if (rgb == 'none') 549 return rgb; 550 551 rgba = Math.round(o*255).toString(16); 552 if (rgba.length == 1) 553 rgba = "0" + rgba; 554 555 return rgb + rgba; 556 }; 557 558 /** 559 * Decolorizes the given color. 560 * @param {String} color HTML string containing the HTML color code. 561 * @type String 562 * @return Returns a HTML color string 563 */ 564 JXG.rgb2bw = function(color) { 565 if(color == 'none') { 566 return color; 567 } 568 var x, HexChars="0123456789ABCDEF", tmp, arr; 569 arr = JXG.rgbParser(color); 570 x = 0.3*arr[0] + 0.59*arr[1] + 0.11*arr[2]; 571 tmp = HexChars.charAt((x>>4)&0xf)+HexChars.charAt(x&0xf); 572 color = "#" + tmp + "" + tmp + "" + tmp; 573 return color; 574 }; 575 576 /** 577 * Converts a color into how a colorblind human approximately would see it. 578 * @param {String} color HTML string containing the HTML color code. 579 * @param {String} deficiency The type of color blindness. Possible 580 * options are <i>protanopia</i>, <i>deuteranopia</i>, and <i>tritanopia</i>. 581 * @type String 582 * @return Returns a HTML color string 583 */ 584 JXG.rgb2cb = function(color, deficiency) { 585 if(color == 'none') { 586 return color; 587 } 588 589 var rgb, l, m, s, lms, tmp, 590 a1, b1, c1, a2, b2, c2, 591 inflection; 592 593 lms = JXG.rgb2LMS(color); 594 l = lms.l; m = lms.m; s = lms.s; 595 596 deficiency = deficiency.toLowerCase(); 597 598 switch(deficiency) { 599 case "protanopia": 600 a1 = -0.06150039994295001; 601 b1 = 0.08277001656812001; 602 c1 = -0.013200141220000003; 603 a2 = 0.05858939668799999; 604 b2 = -0.07934519995360001; 605 c2 = 0.013289415272000003; 606 inflection = 0.6903216543277437; 607 608 tmp = s/m; 609 if (tmp < inflection) 610 l = -(b1 * m + c1 * s) / a1; 611 else 612 l = -(b2 * m + c2 * s) / a2; 613 break; 614 case "tritanopia": 615 a1 = -0.00058973116217; 616 b1 = 0.007690316482; 617 c1 = -0.01011703519052; 618 a2 = 0.025495080838999994; 619 b2 = -0.0422740347; 620 c2 = 0.017005316784; 621 inflection = 0.8349489908460004; 622 623 tmp = m / l; 624 if (tmp < inflection) 625 s = -(a1 * l + b1 * m) / c1; 626 else 627 s = -(a2 * l + b2 * m) / c2; 628 break; 629 default: 630 a1 = -0.06150039994295001; 631 b1 = 0.08277001656812001; 632 c1 = -0.013200141220000003; 633 a2 = 0.05858939668799999; 634 b2 = -0.07934519995360001; 635 c2 = 0.013289415272000003; 636 inflection = 0.5763833686400911; 637 638 tmp = s/l; 639 if(tmp < inflection) 640 m = -(a1 * l + c1 * s) / b1; 641 else 642 m = -(a2 * l + c2 * s) / b2; 643 break; 644 } 645 646 rgb = JXG.LMS2rgb(l, m, s); 647 648 var HexChars="0123456789ABCDEF"; 649 tmp = HexChars.charAt((rgb.r>>4)&0xf)+HexChars.charAt(rgb.r&0xf); 650 color = "#" + tmp; 651 tmp = HexChars.charAt((rgb.g>>4)&0xf)+HexChars.charAt(rgb.g&0xf); 652 color += tmp; 653 tmp = HexChars.charAt((rgb.b>>4)&0xf)+HexChars.charAt(rgb.b&0xf); 654 color += tmp; 655 656 return color; 657 }; 658