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 /** 298 * Converts HSV color to RGB color. 299 * Based on C Code in "Computer Graphics -- Principles and Practice," 300 * Foley et al, 1996, p. 593. 301 * See also http://www.efg2.com/Lab/Graphics/Colors/HSV.htm 302 * @param {float} H value between 0 and 360 303 * @param {float} S value between 0.0 (shade of gray) to 1.0 (pure color) 304 * @param {float} V value between 0.0 (black) to 1.0 (white) 305 * @return {string} RGB color string 306 */ 307 JXG.hsv2rgb = function(H,S,V) { 308 var R,G,B, f,i,hTemp, p,q,t; 309 H = ((H%360.0)+360.0)%360; 310 if (S==0) { 311 if (isNaN(H) || H < JXG.Math.eps) { 312 R = V; 313 G = V; 314 B = V; 315 } else { 316 return '#ffffff'; 317 } 318 } else { 319 if (H>=360) { 320 hTemp = 0.0; 321 } else { 322 hTemp = H; 323 } 324 hTemp = hTemp / 60; // h is now IN [0,6) 325 i = Math.floor(hTemp); // largest integer <= h 326 f = hTemp - i; // fractional part of h 327 p = V * (1.0 - S); 328 q = V * (1.0 - (S * f)); 329 t = V * (1.0 - (S * (1.0 - f))); 330 switch (i) { 331 case 0: R = V; G = t; B = p; break; 332 case 1: R = q; G = V; B = p; break; 333 case 2: R = p; G = V; B = t; break; 334 case 3: R = p; G = q; B = V; break; 335 case 4: R = t; G = p; B = V; break; 336 case 5: R = V; G = p; B = q; break; 337 } 338 } 339 R = Math.round(R*255).toString(16); R = (R.length==2)?R:((R.length==1)?'0'+R:'00'); 340 G = Math.round(G*255).toString(16); G = (G.length==2)?G:((G.length==1)?'0'+G:'00'); 341 B = Math.round(B*255).toString(16); B = (B.length==2)?B:((B.length==1)?'0'+B:'00'); 342 return ['#',R,G,B].join(''); 343 }; 344 345 /** 346 * Converts r, g, b color to h, s, v. 347 * See http://zach.in.tu-clausthal.de/teaching/cg1_0708/folien/13_color_3_4up.pdf for more information. 348 * @param {number} r Amount of red in color. Number between 0 and 255. 349 * @param {number} g Amount of green. Number between 0 and 255. 350 * @param {number} b Amount of blue. Number between 0 and 255. 351 * @type Object 352 * @return Hashmap containing h,s, and v field. 353 */ 354 JXG.rgb2hsv = function() { 355 var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min, stx; 356 r = JXG.rgbParser.apply(JXG.rgbParser, arguments); 357 g = r[1]; 358 b = r[2]; 359 r = r[0]; 360 stx = JXG.Math.Statistics; 361 fr = r/255.; 362 fg = g/255.; 363 fb = b/255.; 364 max = stx.max([r, g, b]); 365 min = stx.min([r, g, b]); 366 fmax = max/255.; 367 fmin = min/255.; 368 369 v = fmax; 370 371 s = 0.; 372 if(v>0) { 373 s = (v-fmin)/(v*1.); 374 } 375 376 h = 1./(fmax-fmin); 377 if(s > 0) { 378 if(max==r) 379 h = (fg-fb)*h; 380 else if(max==g) 381 h = 2 + (fb-fr)*h; 382 else 383 h = 4 + (fr-fg)*h; 384 } 385 386 h *= 60; 387 if(h < 0) 388 h += 360; 389 390 if(max==min) 391 h = 0.; 392 393 return [h, s, v]; 394 }; 395 396 397 /** 398 * Convert RGB color information to LMS color space. 399 * @param {number} r Amount of red in color. Number between 0 and 255. 400 * @param {number} g Amount of green. Number between 0 and 255. 401 * @param {number} b Amount of blue. Number between 0 and 255. 402 * @type Object 403 * @return Hashmap containing the L, M, S cone values. 404 */ 405 JXG.rgb2LMS = function() { 406 var r, g, b, l, m, s, ret, 407 // constants 408 matrix = [[0.05059983, 0.08585369, 0.00952420], [0.01893033, 0.08925308, 0.01370054], [0.00292202, 0.00975732, 0.07145979]]; 409 410 r = JXG.rgbParser.apply(JXG.rgbParser, arguments); 411 g = r[1]; 412 b = r[2]; 413 r = r[0]; 414 415 // de-gamma 416 // Maybe this can be made faster by using a cache 417 r = Math.pow(r, 0.476190476); 418 g = Math.pow(g, 0.476190476); 419 b = Math.pow(b, 0.476190476); 420 421 l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2]; 422 m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2]; 423 s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2]; 424 425 ret = [l, m, s]; 426 ret.l = l; 427 ret.m = m; 428 ret.s = s; 429 430 return ret; 431 }; 432 /** 433 * Convert color information from LMS to RGB color space. 434 * @param {number} l Amount of l value. 435 * @param {number} m Amount of m value. 436 * @param {number} s Amount of s value. 437 * @type Object 438 * @return Hashmap containing the r, g, b values. 439 */ 440 JXG.LMS2rgb = function(l, m, s) { 441 var r, g, b, ret, 442 // constants 443 matrix = [[30.830854, -29.832659, 1.610474], [-6.481468, 17.715578, -2.532642], [-0.375690, -1.199062, 14.273846]]; 444 445 // transform back to rgb 446 r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2]; 447 g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2]; 448 b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2]; 449 450 // re-gamma, inspired by GIMP modules/display-filter-color-blind.c: 451 // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>, 452 // Sven Neumann <sven@gimp.org>, 453 // Robert Dougherty <bob@vischeck.com> and 454 // Alex Wade <alex@vischeck.com> 455 // This code is an implementation of an algorithm described by Hans Brettel, 456 // Francoise Vienot and John Mollon in the Journal of the Optical Society of 457 // America V14(10), pg 2647. (See http://vischeck.com/ for more info.) 458 var lut_lookup = function (value) { 459 var offset = 127, step = 64; 460 461 while (step > 0) { 462 if (Math.pow(offset, 0.476190476) > value) { 463 offset -= step; 464 } else { 465 if (Math.pow(offset+1, 0.476190476) > value) 466 return offset; 467 468 offset += step; 469 } 470 471 step /= 2; 472 } 473 474 /* the algorithm above can't reach 255 */ 475 if (offset == 254 && 13.994955247 < value) 476 return 255; 477 478 return offset; 479 }; 480 481 482 r = lut_lookup(r); 483 g = lut_lookup(g); 484 b = lut_lookup(b); 485 486 ret = [r, g, b]; 487 ret.r = r; 488 ret.g = g; 489 ret.b = b; 490 491 return ret; 492 }; 493