/ src / client / randomColor.js
randomColor.js
  1  // randomColor by David Merfield under the CC0 license
  2  // https://github.com/davidmerfield/randomColor/
  3  
  4  ;(function(root, factory) {
  5  
  6    // Support CommonJS
  7    if (typeof exports === 'object') {
  8      var randomColor = factory();
  9  
 10      // Support NodeJS & Component, which allow module.exports to be a function
 11      if (typeof module === 'object' && module && module.exports) {
 12        exports = module.exports = randomColor;
 13      }
 14  
 15      // Support CommonJS 1.1.1 spec
 16      exports.randomColor = randomColor;
 17  
 18    // Support AMD
 19    } else if (typeof define === 'function' && define.amd) {
 20      define([], factory);
 21  
 22    // Support vanilla script loading
 23    } else {
 24      root.randomColor = factory();
 25    }
 26  
 27  }(this, function() {
 28  
 29    // Seed to get repeatable colors
 30    var seed = null;
 31  
 32    // Shared color dictionary
 33    var colorDictionary = {};
 34  
 35    // Populate the color dictionary
 36    loadColorBounds();
 37  
 38    var randomColor = function (options) {
 39  
 40      options = options || {};
 41  
 42      // Check if there is a seed and ensure it's an
 43      // integer. Otherwise, reset the seed value.
 44      if (options.seed !== undefined && options.seed !== null && options.seed === parseInt(options.seed, 10)) {
 45        seed = options.seed;
 46  
 47      // A string was passed as a seed
 48      } else if (typeof options.seed === 'string') {
 49        seed = stringToInteger(options.seed);
 50  
 51      // Something was passed as a seed but it wasn't an integer or string
 52      } else if (options.seed !== undefined && options.seed !== null) {
 53        throw new TypeError('The seed value must be an integer or string');
 54  
 55      // No seed, reset the value outside.
 56      } else {
 57        seed = null;
 58      }
 59  
 60      var H,S,B;
 61  
 62      // Check if we need to generate multiple colors
 63      if (options.count !== null && options.count !== undefined) {
 64  
 65        var totalColors = options.count,
 66            colors = [];
 67  
 68        options.count = null;
 69  
 70        while (totalColors > colors.length) {
 71  
 72          // Since we're generating multiple colors,
 73          // incremement the seed. Otherwise we'd just
 74          // generate the same color each time...
 75          if (seed && options.seed) options.seed += 1;
 76  
 77          colors.push(randomColor(options));
 78        }
 79  
 80        options.count = totalColors;
 81  
 82        return colors;
 83      }
 84  
 85      // First we pick a hue (H)
 86      H = pickHue(options);
 87  
 88      // Then use H to determine saturation (S)
 89      S = pickSaturation(H, options);
 90  
 91      // Then use S and H to determine brightness (B).
 92      B = pickBrightness(H, S, options);
 93  
 94      // Then we return the HSB color in the desired format
 95      return setFormat([H,S,B], options);
 96    };
 97  
 98    function pickHue (options) {
 99  
100      var hueRange = getHueRange(options.hue),
101          hue = randomWithin(hueRange);
102  
103      // Instead of storing red as two seperate ranges,
104      // we group them, using negative numbers
105      if (hue < 0) {hue = 360 + hue;}
106  
107      return hue;
108  
109    }
110  
111    function pickSaturation (hue, options) {
112  
113      if (options.hue === 'monochrome') {
114        return 0;
115      }
116  
117      if (options.luminosity === 'random') {
118        return randomWithin([0,100]);
119      }
120  
121      var saturationRange = getSaturationRange(hue);
122  
123      var sMin = saturationRange[0],
124          sMax = saturationRange[1];
125  
126      switch (options.luminosity) {
127  
128        case 'bright':
129          sMin = 55;
130          break;
131  
132        case 'dark':
133          sMin = sMax - 10;
134          break;
135  
136        case 'light':
137          sMax = 55;
138          break;
139     }
140  
141      return randomWithin([sMin, sMax]);
142  
143    }
144  
145    function pickBrightness (H, S, options) {
146  
147      var bMin = getMinimumBrightness(H, S),
148          bMax = 100;
149  
150      switch (options.luminosity) {
151  
152        case 'dark':
153          bMax = bMin + 20;
154          break;
155  
156        case 'light':
157          bMin = (bMax + bMin)/2;
158          break;
159  
160        case 'random':
161          bMin = 0;
162          bMax = 100;
163          break;
164      }
165  
166      return randomWithin([bMin, bMax]);
167    }
168  
169    function setFormat (hsv, options) {
170  
171      switch (options.format) {
172  
173        case 'hsvArray':
174          return hsv;
175  
176        case 'hslArray':
177          return HSVtoHSL(hsv);
178  
179        case 'hsl':
180          var hsl = HSVtoHSL(hsv);
181          return 'hsl('+hsl[0]+', '+hsl[1]+'%, '+hsl[2]+'%)';
182  
183        case 'hsla':
184          var hslColor = HSVtoHSL(hsv);
185          var alpha = options.alpha || Math.random();
186          return 'hsla('+hslColor[0]+', '+hslColor[1]+'%, '+hslColor[2]+'%, ' + alpha + ')';
187  
188        case 'rgbArray':
189          return HSVtoRGB(hsv);
190  
191        case 'rgb':
192          var rgb = HSVtoRGB(hsv);
193          return 'rgb(' + rgb.join(', ') + ')';
194  
195        case 'rgba':
196          var rgbColor = HSVtoRGB(hsv);
197          var alpha = options.alpha || Math.random();
198          return 'rgba(' + rgbColor.join(', ') + ', ' + alpha + ')';
199  
200        default:
201          return HSVtoHex(hsv);
202      }
203  
204    }
205  
206    function getMinimumBrightness(H, S) {
207  
208      var lowerBounds = getColorInfo(H).lowerBounds;
209  
210      for (var i = 0; i < lowerBounds.length - 1; i++) {
211  
212        var s1 = lowerBounds[i][0],
213            v1 = lowerBounds[i][1];
214  
215        var s2 = lowerBounds[i+1][0],
216            v2 = lowerBounds[i+1][1];
217  
218        if (S >= s1 && S <= s2) {
219  
220           var m = (v2 - v1)/(s2 - s1),
221               b = v1 - m*s1;
222  
223           return m*S + b;
224        }
225  
226      }
227  
228      return 0;
229    }
230  
231    function getHueRange (colorInput) {
232  
233      if (typeof parseInt(colorInput) === 'number') {
234  
235        var number = parseInt(colorInput);
236  
237        if (number < 360 && number > 0) {
238          return [number, number];
239        }
240  
241      }
242  
243      if (typeof colorInput === 'string') {
244  
245        if (colorDictionary[colorInput]) {
246          var color = colorDictionary[colorInput];
247          if (color.hueRange) {return color.hueRange;}
248        } else if (colorInput.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) {
249          var hue = HexToHSB(colorInput)[0];
250          return [ hue, hue ];
251        }
252      }
253  
254      return [0,360];
255  
256    }
257  
258    function getSaturationRange (hue) {
259      return getColorInfo(hue).saturationRange;
260    }
261  
262    function getColorInfo (hue) {
263  
264      // Maps red colors to make picking hue easier
265      if (hue >= 334 && hue <= 360) {
266        hue-= 360;
267      }
268  
269      for (var colorName in colorDictionary) {
270         var color = colorDictionary[colorName];
271         if (color.hueRange &&
272             hue >= color.hueRange[0] &&
273             hue <= color.hueRange[1]) {
274            return colorDictionary[colorName];
275         }
276      } return 'Color not found';
277    }
278  
279    function randomWithin (range) {
280      if (seed === null) {
281        return Math.floor(range[0] + Math.random()*(range[1] + 1 - range[0]));
282      } else {
283        //Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
284        var max = range[1] || 1;
285        var min = range[0] || 0;
286        seed = (seed * 9301 + 49297) % 233280;
287        var rnd = seed / 233280.0;
288        return Math.floor(min + rnd * (max - min));
289      }
290    }
291  
292    function HSVtoHex (hsv){
293  
294      var rgb = HSVtoRGB(hsv);
295  
296      function componentToHex(c) {
297          var hex = c.toString(16);
298          return hex.length == 1 ? '0' + hex : hex;
299      }
300  
301      var hex = '#' + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
302  
303      return hex;
304  
305    }
306  
307    function defineColor (name, hueRange, lowerBounds) {
308  
309      var sMin = lowerBounds[0][0],
310          sMax = lowerBounds[lowerBounds.length - 1][0],
311  
312          bMin = lowerBounds[lowerBounds.length - 1][1],
313          bMax = lowerBounds[0][1];
314  
315      colorDictionary[name] = {
316        hueRange: hueRange,
317        lowerBounds: lowerBounds,
318        saturationRange: [sMin, sMax],
319        brightnessRange: [bMin, bMax]
320      };
321  
322    }
323  
324    function loadColorBounds () {
325  
326      defineColor(
327        'monochrome',
328        null,
329        [[0,0],[100,0]]
330      );
331  
332      defineColor(
333        'red',
334        [-26,18],
335        [[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]]
336      );
337  
338      defineColor(
339        'orange',
340        [19,46],
341        [[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]]
342      );
343  
344      defineColor(
345        'yellow',
346        [47,62],
347        [[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]]
348      );
349  
350      defineColor(
351        'green',
352        [63,178],
353        [[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]]
354      );
355  
356      defineColor(
357        'blue',
358        [179, 257],
359        [[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]]
360      );
361  
362      defineColor(
363        'purple',
364        [258, 282],
365        [[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]]
366      );
367  
368      defineColor(
369        'pink',
370        [283, 334],
371        [[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]]
372      );
373  
374    }
375  
376    function HSVtoRGB (hsv) {
377  
378      // this doesn't work for the values of 0 and 360
379      // here's the hacky fix
380      var h = hsv[0];
381      if (h === 0) {h = 1;}
382      if (h === 360) {h = 359;}
383  
384      // Rebase the h,s,v values
385      h = h/360;
386      var s = hsv[1]/100,
387          v = hsv[2]/100;
388  
389      var h_i = Math.floor(h*6),
390        f = h * 6 - h_i,
391        p = v * (1 - s),
392        q = v * (1 - f*s),
393        t = v * (1 - (1 - f)*s),
394        r = 256,
395        g = 256,
396        b = 256;
397  
398      switch(h_i) {
399        case 0: r = v; g = t; b = p;  break;
400        case 1: r = q; g = v; b = p;  break;
401        case 2: r = p; g = v; b = t;  break;
402        case 3: r = p; g = q; b = v;  break;
403        case 4: r = t; g = p; b = v;  break;
404        case 5: r = v; g = p; b = q;  break;
405      }
406  
407      var result = [Math.floor(r*255), Math.floor(g*255), Math.floor(b*255)];
408      return result;
409    }
410  
411    function HexToHSB (hex) {
412      hex = hex.replace(/^#/, '');
413      hex = hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex;
414  
415      var red = parseInt(hex.substr(0, 2), 16) / 255,
416            green = parseInt(hex.substr(2, 2), 16) / 255,
417            blue = parseInt(hex.substr(4, 2), 16) / 255;
418  
419      var cMax = Math.max(red, green, blue),
420            delta = cMax - Math.min(red, green, blue),
421            saturation = cMax ? (delta / cMax) : 0;
422  
423      switch (cMax) {
424        case red: return [ 60 * (((green - blue) / delta) % 6) || 0, saturation, cMax ];
425        case green: return [ 60 * (((blue - red) / delta) + 2) || 0, saturation, cMax ];
426        case blue: return [ 60 * (((red - green) / delta) + 4) || 0, saturation, cMax ];
427      }
428    }
429  
430    function HSVtoHSL (hsv) {
431      var h = hsv[0],
432        s = hsv[1]/100,
433        v = hsv[2]/100,
434        k = (2-s)*v;
435  
436      return [
437        h,
438        Math.round(s*v / (k<1 ? k : 2-k) * 10000) / 100,
439        k/2 * 100
440      ];
441    }
442  
443    function stringToInteger (string) {
444      var total = 0
445      for (var i = 0; i !== string.length; i++) {
446        if (total >= Number.MAX_SAFE_INTEGER) break;
447        total += string.charCodeAt(i)
448      }
449      return total
450    }
451  
452    return randomColor;
453  }));