418 lines
11 KiB
JavaScript
418 lines
11 KiB
JavaScript
// randomColor by David Merfield under the CC0 license
|
|
// https://github.com/davidmerfield/randomColor/
|
|
|
|
(function(root, factory) {
|
|
// Support CommonJS
|
|
if (typeof exports === "object") {
|
|
var randomColor = factory();
|
|
|
|
// Support NodeJS & Component, which allow module.exports to be a function
|
|
if (typeof module === "object" && module && module.exports) {
|
|
exports = module.exports = randomColor;
|
|
}
|
|
|
|
// Support CommonJS 1.1.1 spec
|
|
exports.randomColor = randomColor;
|
|
|
|
// Support AMD
|
|
} else if (typeof define === "function" && define.amd) {
|
|
define([], factory);
|
|
|
|
// Support vanilla script loading
|
|
} else {
|
|
root.randomColor = factory();
|
|
}
|
|
})(this, function() {
|
|
// Seed to get repeatable colors
|
|
var seed = null;
|
|
|
|
// Shared color dictionary
|
|
var colorDictionary = {};
|
|
|
|
// Populate the color dictionary
|
|
loadColorBounds();
|
|
|
|
var randomColor = function(options) {
|
|
options = options || {};
|
|
|
|
// Check if there is a seed and ensure it's an
|
|
// integer. Otherwise, reset the seed value.
|
|
if (options.seed !== undefined && options.seed !== null && options.seed === parseInt(options.seed, 10)) {
|
|
seed = options.seed;
|
|
|
|
// A string was passed as a seed
|
|
} else if (typeof options.seed === "string") {
|
|
seed = stringToInteger(options.seed);
|
|
|
|
// Something was passed as a seed but it wasn't an integer or string
|
|
} else if (options.seed !== undefined && options.seed !== null) {
|
|
throw new TypeError("The seed value must be an integer or string");
|
|
|
|
// No seed, reset the value outside.
|
|
} else {
|
|
seed = null;
|
|
}
|
|
|
|
var H, S, B;
|
|
|
|
// Check if we need to generate multiple colors
|
|
if (options.count !== null && options.count !== undefined) {
|
|
var totalColors = options.count,
|
|
colors = [];
|
|
|
|
options.count = null;
|
|
|
|
while (totalColors > colors.length) {
|
|
// Since we're generating multiple colors,
|
|
// incremement the seed. Otherwise we'd just
|
|
// generate the same color each time...
|
|
if (seed && options.seed) options.seed += 1;
|
|
|
|
colors.push(randomColor(options));
|
|
}
|
|
|
|
options.count = totalColors;
|
|
|
|
return colors;
|
|
}
|
|
|
|
// First we pick a hue (H)
|
|
H = pickHue(options);
|
|
|
|
// Then use H to determine saturation (S)
|
|
S = pickSaturation(H, options);
|
|
|
|
// Then use S and H to determine brightness (B).
|
|
B = pickBrightness(H, S, options);
|
|
|
|
// Then we return the HSB color in the desired format
|
|
return setFormat([H, S, B], options);
|
|
};
|
|
|
|
function pickHue(options) {
|
|
var hueRange = getHueRange(options.hue),
|
|
hue = randomWithin(hueRange);
|
|
|
|
// Instead of storing red as two seperate ranges,
|
|
// we group them, using negative numbers
|
|
if (hue < 0) {
|
|
hue = 360 + hue;
|
|
}
|
|
|
|
return hue;
|
|
}
|
|
|
|
function pickSaturation(hue, options) {
|
|
if (options.hue === "monochrome") {
|
|
return 0;
|
|
}
|
|
|
|
if (options.luminosity === "random") {
|
|
return randomWithin([0, 100]);
|
|
}
|
|
|
|
var saturationRange = getSaturationRange(hue);
|
|
|
|
var sMin = saturationRange[0],
|
|
sMax = saturationRange[1];
|
|
|
|
switch (options.luminosity) {
|
|
case "bright":
|
|
sMin = 55;
|
|
break;
|
|
|
|
case "dark":
|
|
sMin = sMax - 10;
|
|
break;
|
|
|
|
case "light":
|
|
sMax = 55;
|
|
break;
|
|
}
|
|
|
|
return randomWithin([sMin, sMax]);
|
|
}
|
|
|
|
function pickBrightness(H, S, options) {
|
|
var bMin = getMinimumBrightness(H, S),
|
|
bMax = 100;
|
|
|
|
switch (options.luminosity) {
|
|
case "dark":
|
|
bMax = bMin + 20;
|
|
break;
|
|
|
|
case "light":
|
|
bMin = (bMax + bMin) / 2;
|
|
break;
|
|
|
|
case "random":
|
|
bMin = 0;
|
|
bMax = 100;
|
|
break;
|
|
}
|
|
|
|
return randomWithin([bMin, bMax]);
|
|
}
|
|
|
|
function setFormat(hsv, options) {
|
|
switch (options.format) {
|
|
case "hsvArray":
|
|
return hsv;
|
|
|
|
case "hslArray":
|
|
return HSVtoHSL(hsv);
|
|
|
|
case "hsl":
|
|
var hsl = HSVtoHSL(hsv);
|
|
return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)";
|
|
|
|
case "hsla":
|
|
var hslColor = HSVtoHSL(hsv);
|
|
var alpha = options.alpha || Math.random();
|
|
return "hsla(" + hslColor[0] + ", " + hslColor[1] + "%, " + hslColor[2] + "%, " + alpha + ")";
|
|
|
|
case "rgbArray":
|
|
return HSVtoRGB(hsv);
|
|
|
|
case "rgb":
|
|
var rgb = HSVtoRGB(hsv);
|
|
return "rgb(" + rgb.join(", ") + ")";
|
|
|
|
case "rgba":
|
|
var rgbColor = HSVtoRGB(hsv);
|
|
var alpha = options.alpha || Math.random();
|
|
return "rgba(" + rgbColor.join(", ") + ", " + alpha + ")";
|
|
|
|
default:
|
|
return HSVtoHex(hsv);
|
|
}
|
|
}
|
|
|
|
function getMinimumBrightness(H, S) {
|
|
var lowerBounds = getColorInfo(H).lowerBounds;
|
|
|
|
for (var i = 0; i < lowerBounds.length - 1; i++) {
|
|
var s1 = lowerBounds[i][0],
|
|
v1 = lowerBounds[i][1];
|
|
|
|
var s2 = lowerBounds[i + 1][0],
|
|
v2 = lowerBounds[i + 1][1];
|
|
|
|
if (S >= s1 && S <= s2) {
|
|
var m = (v2 - v1) / (s2 - s1),
|
|
b = v1 - m * s1;
|
|
|
|
return m * S + b;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function getHueRange(colorInput) {
|
|
if (typeof parseInt(colorInput) === "number") {
|
|
var number = parseInt(colorInput);
|
|
|
|
if (number < 360 && number > 0) {
|
|
return [number, number];
|
|
}
|
|
}
|
|
|
|
if (typeof colorInput === "string") {
|
|
if (colorDictionary[colorInput]) {
|
|
var color = colorDictionary[colorInput];
|
|
if (color.hueRange) {
|
|
return color.hueRange;
|
|
}
|
|
} else if (colorInput.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)) {
|
|
var hue = HexToHSB(colorInput)[0];
|
|
return [hue, hue];
|
|
}
|
|
}
|
|
|
|
return [0, 360];
|
|
}
|
|
|
|
function getSaturationRange(hue) {
|
|
return getColorInfo(hue).saturationRange;
|
|
}
|
|
|
|
function getColorInfo(hue) {
|
|
// Maps red colors to make picking hue easier
|
|
if (hue >= 334 && hue <= 360) {
|
|
hue -= 360;
|
|
}
|
|
|
|
for (var colorName in colorDictionary) {
|
|
var color = colorDictionary[colorName];
|
|
if (color.hueRange && hue >= color.hueRange[0] && hue <= color.hueRange[1]) {
|
|
return colorDictionary[colorName];
|
|
}
|
|
}
|
|
return "Color not found";
|
|
}
|
|
|
|
function randomWithin(range) {
|
|
if (seed === null) {
|
|
return Math.floor(range[0] + Math.random() * (range[1] + 1 - range[0]));
|
|
} else {
|
|
//Seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
|
|
var max = range[1] || 1;
|
|
var min = range[0] || 0;
|
|
seed = (seed * 9301 + 49297) % 233280;
|
|
var rnd = seed / 233280.0;
|
|
return Math.floor(min + rnd * (max - min));
|
|
}
|
|
}
|
|
|
|
function HSVtoHex(hsv) {
|
|
var rgb = HSVtoRGB(hsv);
|
|
|
|
function componentToHex(c) {
|
|
var hex = c.toString(16);
|
|
return hex.length == 1 ? "0" + hex : hex;
|
|
}
|
|
|
|
var hex = "#" + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
|
|
|
|
return hex;
|
|
}
|
|
|
|
function defineColor(name, hueRange, lowerBounds) {
|
|
var sMin = lowerBounds[0][0],
|
|
sMax = lowerBounds[lowerBounds.length - 1][0],
|
|
bMin = lowerBounds[lowerBounds.length - 1][1],
|
|
bMax = lowerBounds[0][1];
|
|
|
|
colorDictionary[name] = {
|
|
hueRange: hueRange,
|
|
lowerBounds: lowerBounds,
|
|
saturationRange: [sMin, sMax],
|
|
brightnessRange: [bMin, bMax]
|
|
};
|
|
}
|
|
|
|
function loadColorBounds() {
|
|
defineColor("monochrome", null, [[0, 0], [100, 0]]);
|
|
|
|
defineColor("red", [-26, 18], [[20, 100], [30, 92], [40, 89], [50, 85], [60, 78], [70, 70], [80, 60], [90, 55], [100, 50]]);
|
|
|
|
defineColor("orange", [19, 46], [[20, 100], [30, 93], [40, 88], [50, 86], [60, 85], [70, 70], [100, 70]]);
|
|
|
|
defineColor("yellow", [47, 62], [[25, 100], [40, 94], [50, 89], [60, 86], [70, 84], [80, 82], [90, 80], [100, 75]]);
|
|
|
|
defineColor("green", [63, 178], [[30, 100], [40, 90], [50, 85], [60, 81], [70, 74], [80, 64], [90, 50], [100, 40]]);
|
|
|
|
defineColor("blue", [179, 257], [[20, 100], [30, 86], [40, 80], [50, 74], [60, 60], [70, 52], [80, 44], [90, 39], [100, 35]]);
|
|
|
|
defineColor("purple", [258, 282], [[20, 100], [30, 87], [40, 79], [50, 70], [60, 65], [70, 59], [80, 52], [90, 45], [100, 42]]);
|
|
|
|
defineColor("pink", [283, 334], [[20, 100], [30, 90], [40, 86], [60, 84], [80, 80], [90, 75], [100, 73]]);
|
|
}
|
|
|
|
function HSVtoRGB(hsv) {
|
|
// this doesn't work for the values of 0 and 360
|
|
// here's the hacky fix
|
|
var h = hsv[0];
|
|
if (h === 0) {
|
|
h = 1;
|
|
}
|
|
if (h === 360) {
|
|
h = 359;
|
|
}
|
|
|
|
// Rebase the h,s,v values
|
|
h = h / 360;
|
|
var s = hsv[1] / 100,
|
|
v = hsv[2] / 100;
|
|
|
|
var h_i = Math.floor(h * 6),
|
|
f = h * 6 - h_i,
|
|
p = v * (1 - s),
|
|
q = v * (1 - f * s),
|
|
t = v * (1 - (1 - f) * s),
|
|
r = 256,
|
|
g = 256,
|
|
b = 256;
|
|
|
|
switch (h_i) {
|
|
case 0:
|
|
r = v;
|
|
g = t;
|
|
b = p;
|
|
break;
|
|
case 1:
|
|
r = q;
|
|
g = v;
|
|
b = p;
|
|
break;
|
|
case 2:
|
|
r = p;
|
|
g = v;
|
|
b = t;
|
|
break;
|
|
case 3:
|
|
r = p;
|
|
g = q;
|
|
b = v;
|
|
break;
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
b = v;
|
|
break;
|
|
case 5:
|
|
r = v;
|
|
g = p;
|
|
b = q;
|
|
break;
|
|
}
|
|
|
|
var result = [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)];
|
|
return result;
|
|
}
|
|
|
|
function HexToHSB(hex) {
|
|
hex = hex.replace(/^#/, "");
|
|
hex = hex.length === 3 ? hex.replace(/(.)/g, "$1$1") : hex;
|
|
|
|
var red = parseInt(hex.substr(0, 2), 16) / 255,
|
|
green = parseInt(hex.substr(2, 2), 16) / 255,
|
|
blue = parseInt(hex.substr(4, 2), 16) / 255;
|
|
|
|
var cMax = Math.max(red, green, blue),
|
|
delta = cMax - Math.min(red, green, blue),
|
|
saturation = cMax ? delta / cMax : 0;
|
|
|
|
switch (cMax) {
|
|
case red:
|
|
return [60 * (((green - blue) / delta) % 6) || 0, saturation, cMax];
|
|
case green:
|
|
return [60 * ((blue - red) / delta + 2) || 0, saturation, cMax];
|
|
case blue:
|
|
return [60 * ((red - green) / delta + 4) || 0, saturation, cMax];
|
|
}
|
|
}
|
|
|
|
function HSVtoHSL(hsv) {
|
|
var h = hsv[0],
|
|
s = hsv[1] / 100,
|
|
v = hsv[2] / 100,
|
|
k = (2 - s) * v;
|
|
|
|
return [h, Math.round(s * v / (k < 1 ? k : 2 - k) * 10000) / 100, k / 2 * 100];
|
|
}
|
|
|
|
function stringToInteger(string) {
|
|
var total = 0;
|
|
for (var i = 0; i !== string.length; i++) {
|
|
if (total >= Number.MAX_SAFE_INTEGER) break;
|
|
total += string.charCodeAt(i);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
return randomColor;
|
|
});
|