      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