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