Spaces:
Paused
Paused
| const SATURATION_BOUND = [0, 100]; | |
| const LIGHTNESS_BOUND = [0, 100]; | |
| const pad2 = str => `${str.length === 1 ? '0' : ''}${str}`; | |
| const clamp = (num, min, max) => Math.max(Math.min(num, max), min); | |
| const random = (min, max) => Math.floor(Math.random() * ((max - min) + 1)) + min; | |
| const randomExclude = (min, max, exclude) => { | |
| const r = random(min, max); | |
| for (let i = 0; i < exclude?.length; i++) { | |
| const value = exclude[i]; | |
| if (value?.length === 2 && r >= value[0] && r <= value[1]) { | |
| return randomExclude(min, max, exclude); | |
| } | |
| } | |
| return r; | |
| }; | |
| /** | |
| * Generate hashCode | |
| * @param {string} str | |
| * @return {number} | |
| */ | |
| const hashCode = str => { | |
| const len = str.length; | |
| let hash = 0; | |
| for (let i = 0; i < len; i++) { | |
| hash = ((hash << 5) - hash) + str.charCodeAt(i); | |
| hash &= hash; // Convert to 32bit integer | |
| } | |
| return hash; | |
| }; | |
| /** | |
| * Clamps `num` within the inclusive `range` bounds | |
| * @param {number} num | |
| * @param {number|Array} range | |
| * @return {number} | |
| */ | |
| const boundHashCode = (num, range) => { | |
| if (typeof range === 'number') { | |
| return range; | |
| } | |
| return (num % Math.abs(range[1] - range[0])) + range[0]; | |
| }; | |
| /** | |
| * Sanitizing the `range` | |
| * @param {number|Array} range | |
| * @param {Array} bound | |
| * @return {number|Array} | |
| */ | |
| const sanitizeRange = (range, bound) => { | |
| if (typeof range === 'number') { | |
| return clamp(Math.abs(range), ...bound); | |
| } | |
| if (range.length === 1 || range[0] === range[1]) { | |
| return clamp(Math.abs(range[0]), ...bound); | |
| } | |
| return [ | |
| Math.abs(clamp(range[0], ...bound)), | |
| clamp(Math.abs(range[1]), ...bound), | |
| ]; | |
| }; | |
| /** | |
| * @param {number} p | |
| * @param {number} q | |
| * @param {number} t | |
| * @return {number} | |
| */ | |
| const hueToRgb = (p, q, t) => { | |
| if (t < 0) { | |
| t += 1; | |
| } else if (t > 1) { | |
| t -= 1; | |
| } | |
| if (t < 1 / 6) { | |
| return p + ((q - p) * 6 * t); | |
| } | |
| if (t < 1 / 2) { | |
| return q; | |
| } | |
| if (t < 2 / 3) { | |
| return p + ((q - p) * ((2 / 3) - t) * 6); | |
| } | |
| return p; | |
| }; | |
| /** | |
| * Converts an HSL color to RGB | |
| * @param {number} h Hue | |
| * @param {number} s Saturation | |
| * @param {number} l Lightness | |
| * @return {Array} | |
| */ | |
| const hslToRgb = (h, s, l) => { | |
| let r; | |
| let g; | |
| let b; | |
| h /= 360; | |
| s /= 100; | |
| l /= 100; | |
| if (s === 0) { | |
| // achromatic | |
| r = g = b = l; | |
| } else { | |
| const q = l < 0.5 | |
| ? l * (1 + s) | |
| : (l + s) - (l * s); | |
| const p = (2 * l) - q; | |
| r = hueToRgb(p, q, h + (1 / 3)); | |
| g = hueToRgb(p, q, h); | |
| b = hueToRgb(p, q, h - (1 / 3)); | |
| } | |
| return [ | |
| Math.round(r * 255), | |
| Math.round(g * 255), | |
| Math.round(b * 255), | |
| ]; | |
| }; | |
| /** | |
| * Determines whether the RGB color is light or not | |
| * http://www.w3.org/TR/AERT#color-contrast | |
| * @param {number} r Red | |
| * @param {number} g Green | |
| * @param {number} b Blue | |
| * @param {number} differencePoint | |
| * @return {boolean} | |
| */ | |
| const rgbIsLight = (r, g, b, differencePoint) => ((r * 299) + (g * 587) + (b * 114)) / 1000 >= differencePoint; // eslint-disable-line max-len | |
| /** | |
| * Converts an HSL color to string format | |
| * @param {number} h Hue | |
| * @param {number} s Saturation | |
| * @param {number} l Lightness | |
| * @return {string} | |
| */ | |
| const hslToString = (h, s, l) => `hsl(${h}, ${s}%, ${l}%)`; | |
| /** | |
| * Converts RGB color to string format | |
| * @param {number} r Red | |
| * @param {number} g Green | |
| * @param {number} b Blue | |
| * @param {string} format Color format | |
| * @return {string} | |
| */ | |
| const rgbFormat = (r, g, b, format) => { | |
| switch (format) { | |
| case 'rgb': | |
| return `rgb(${r}, ${g}, ${b})`; | |
| case 'hex': | |
| default: | |
| return `#${pad2(r.toString(16))}${pad2(g.toString(16))}${pad2(b.toString(16))}`; | |
| } | |
| }; | |
| /** | |
| * Generate unique color from `value` | |
| * @param {string|number} value | |
| * @param {Object} [options={}] | |
| * @param {string} [options.format='hex'] | |
| * The color format, it can be one of `hex`, `rgb` or `hsl` | |
| * @param {number|Array} [options.saturation=[50, 55]] | |
| * Determines the color saturation, it can be a number or a range between 0 and 100 | |
| * @param {number|Array} [options.lightness=[50, 60]] | |
| * Determines the color lightness, it can be a number or a range between 0 and 100 | |
| * @param {number} [options.differencePoint=130] | |
| * Determines the color brightness difference point. We use it to obtain the `isLight` value | |
| * in the output, it can be a number between 0 and 255 | |
| * @return {Object} | |
| * @example | |
| * | |
| * ```js | |
| * uniqolor('Hello world!') | |
| * // { color: "#5cc653", isLight: true } | |
| * | |
| * uniqolor('Hello world!', { format: 'rgb' }) | |
| * // { color: "rgb(92, 198, 83)", isLight: true } | |
| * | |
| * uniqolor('Hello world!', { | |
| * saturation: 30, | |
| * lightness: [70, 80], | |
| * }) | |
| * // { color: "#afd2ac", isLight: true } | |
| * | |
| * uniqolor('Hello world!', { | |
| * saturation: 30, | |
| * lightness: [70, 80], | |
| * differencePoint: 200, | |
| * }) | |
| * // { color: "#afd2ac", isLight: false } | |
| * ``` | |
| */ | |
| const uniqolor = (value, { | |
| format = 'hex', | |
| saturation = [50, 55], | |
| lightness = [50, 60], | |
| differencePoint = 130, | |
| } = {}) => { | |
| const hash = Math.abs(hashCode(String(value))); | |
| const h = boundHashCode(hash, [0, 360]); | |
| const s = boundHashCode(hash, sanitizeRange(saturation, SATURATION_BOUND)); | |
| const l = boundHashCode(hash, sanitizeRange(lightness, LIGHTNESS_BOUND)); | |
| const [r, g, b] = hslToRgb(h, s, l); | |
| return { | |
| color: format === 'hsl' | |
| ? hslToString(h, s, l) | |
| : rgbFormat(r, g, b, format), | |
| isLight: rgbIsLight(r, g, b, differencePoint), | |
| }; | |
| }; | |
| /** | |
| * Generate random color | |
| * @param {Object} [options={}] | |
| * @param {string} [options.format='hex'] | |
| * The color format, it can be one of `hex`, `rgb` or `hsl` | |
| * @param {number|Array} [options.saturation=[50, 55]] | |
| * Determines the color saturation, it can be a number or a range between 0 and 100 | |
| * @param {number|Array} [options.lightness=[50, 60]] | |
| * Determines the color lightness, it can be a number or a range between 0 and 100 | |
| * @param {number} [options.differencePoint=130] | |
| * Determines the color brightness difference point. We use it to obtain the `isLight` value | |
| * in the output, it can be a number between 0 and 255 | |
| * @param {Array} [options.excludeHue] | |
| * Exclude certain hue ranges. For example to exclude red color range: `[[0, 20], [325, 359]]` | |
| * @return {Object} | |
| * @example | |
| * | |
| * ```js | |
| * // Generate random color | |
| * uniqolor.random() | |
| * // { color: "#644cc8", isLight: false } | |
| * | |
| * // Generate a random color with HSL format | |
| * uniqolor.random({ format: 'hsl' }) | |
| * // { color: "hsl(89, 55%, 60%)", isLight: true } | |
| * | |
| * // Generate a random color in specific saturation and lightness | |
| * uniqolor.random({ | |
| * saturation: 80, | |
| * lightness: [70, 80], | |
| * }) | |
| * // { color: "#c7b9da", isLight: true } | |
| * | |
| * // Generate a random color but exclude red color range | |
| * uniqolor.random({ | |
| * excludeHue: [[0, 20], [325, 359]], | |
| * }) | |
| * // {color: '#53caab', isLight: true} | |
| * ``` | |
| */ | |
| uniqolor.random = ({ | |
| format = 'hex', | |
| saturation = [50, 55], | |
| lightness = [50, 60], | |
| differencePoint = 130, | |
| excludeHue, | |
| } = {}) => { | |
| saturation = sanitizeRange(saturation, SATURATION_BOUND); | |
| lightness = sanitizeRange(lightness, LIGHTNESS_BOUND); | |
| const h = excludeHue ? randomExclude(0, 359, excludeHue) : random(0, 359); | |
| const s = typeof saturation === 'number' | |
| ? saturation | |
| : random(...saturation); | |
| const l = typeof lightness === 'number' | |
| ? lightness | |
| : random(...lightness); | |
| const [r, g, b] = hslToRgb(h, s, l); | |
| return { | |
| color: format === 'hsl' | |
| ? hslToString(h, s, l) | |
| : rgbFormat(r, g, b, format), | |
| isLight: rgbIsLight(r, g, b, differencePoint), | |
| }; | |
| }; | |
| export default uniqolor; | |