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 }));