|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +// https://github.com/imathis/hsl-picker/blob/master/assets/javascripts/modules/color.coffee |
| 4 | +const rHex3 = /^#[0-9a-f]{3}$/; |
| 5 | +const rHex6 = /^#[0-9a-f]{6}$/; |
| 6 | +const rRGB = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,?\s*(0?\.?\d+)?\s*\)$/; |
| 7 | +const rHSL = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,?\s*(0?\.?\d+)?\s*\)$/; |
| 8 | + |
| 9 | +// https://www.w3.org/TR/css3-color/#svg-color |
| 10 | +const colorNames = { |
| 11 | + aliceblue: {r: 240, g: 248, b: 255, a: 1}, |
| 12 | + antiquewhite: {r: 250, g: 235, b: 215, a: 1}, |
| 13 | + aqua: {r: 0, g: 255, b: 255, a: 1}, |
| 14 | + aquamarine: {r: 127, g: 255, b: 212, a: 1}, |
| 15 | + azure: {r: 240, g: 255, b: 255, a: 1}, |
| 16 | + beige: {r: 245, g: 245, b: 220, a: 1}, |
| 17 | + bisque: {r: 255, g: 228, b: 196, a: 1}, |
| 18 | + black: {r: 0, g: 0, b: 0, a: 1}, |
| 19 | + blanchedalmond: {r: 255, g: 235, b: 205, a: 1}, |
| 20 | + blue: {r: 0, g: 0, b: 255, a: 1}, |
| 21 | + blueviolet: {r: 138, g: 43, b: 226, a: 1}, |
| 22 | + brown: {r: 165, g: 42, b: 42, a: 1}, |
| 23 | + burlywood: {r: 222, g: 184, b: 135, a: 1}, |
| 24 | + cadetblue: {r: 95, g: 158, b: 160, a: 1}, |
| 25 | + chartreuse: {r: 127, g: 255, b: 0, a: 1}, |
| 26 | + chocolate: {r: 210, g: 105, b: 30, a: 1}, |
| 27 | + coral: {r: 255, g: 127, b: 80, a: 1}, |
| 28 | + cornflowerblue: {r: 100, g: 149, b: 237, a: 1}, |
| 29 | + cornsilk: {r: 255, g: 248, b: 220, a: 1}, |
| 30 | + crimson: {r: 220, g: 20, b: 60, a: 1}, |
| 31 | + cyan: {r: 0, g: 255, b: 255, a: 1}, |
| 32 | + darkblue: {r: 0, g: 0, b: 139, a: 1}, |
| 33 | + darkcyan: {r: 0, g: 139, b: 139, a: 1}, |
| 34 | + darkgoldenrod: {r: 184, g: 134, b: 11, a: 1}, |
| 35 | + darkgray: {r: 169, g: 169, b: 169, a: 1}, |
| 36 | + darkgreen: {r: 0, g: 100, b: 0, a: 1}, |
| 37 | + darkgrey: {r: 169, g: 169, b: 169, a: 1}, |
| 38 | + darkkhaki: {r: 189, g: 183, b: 107, a: 1}, |
| 39 | + darkmagenta: {r: 139, g: 0, b: 139, a: 1}, |
| 40 | + darkolivegreen: {r: 85, g: 107, b: 47, a: 1}, |
| 41 | + darkorange: {r: 255, g: 140, b: 0, a: 1}, |
| 42 | + darkorchid: {r: 153, g: 50, b: 204, a: 1}, |
| 43 | + darkred: {r: 139, g: 0, b: 0, a: 1}, |
| 44 | + darksalmon: {r: 233, g: 150, b: 122, a: 1}, |
| 45 | + darkseagreen: {r: 143, g: 188, b: 143, a: 1}, |
| 46 | + darkslateblue: {r: 72, g: 61, b: 139, a: 1}, |
| 47 | + darkslategray: {r: 47, g: 79, b: 79, a: 1}, |
| 48 | + darkslategrey: {r: 47, g: 79, b: 79, a: 1}, |
| 49 | + darkturquoise: {r: 0, g: 206, b: 209, a: 1}, |
| 50 | + darkviolet: {r: 148, g: 0, b: 211, a: 1}, |
| 51 | + deeppink: {r: 255, g: 20, b: 147, a: 1}, |
| 52 | + deepskyblue: {r: 0, g: 191, b: 255, a: 1}, |
| 53 | + dimgray: {r: 105, g: 105, b: 105, a: 1}, |
| 54 | + dimgrey: {r: 105, g: 105, b: 105, a: 1}, |
| 55 | + dodgerblue: {r: 30, g: 144, b: 255, a: 1}, |
| 56 | + firebrick: {r: 178, g: 34, b: 34, a: 1}, |
| 57 | + floralwhite: {r: 255, g: 250, b: 240, a: 1}, |
| 58 | + forestgreen: {r: 34, g: 139, b: 34, a: 1}, |
| 59 | + fuchsia: {r: 255, g: 0, b: 255, a: 1}, |
| 60 | + gainsboro: {r: 220, g: 220, b: 220, a: 1}, |
| 61 | + ghostwhite: {r: 248, g: 248, b: 255, a: 1}, |
| 62 | + gold: {r: 255, g: 215, b: 0, a: 1}, |
| 63 | + goldenrod: {r: 218, g: 165, b: 32, a: 1}, |
| 64 | + gray: {r: 128, g: 128, b: 128, a: 1}, |
| 65 | + green: {r: 0, g: 128, b: 0, a: 1}, |
| 66 | + greenyellow: {r: 173, g: 255, b: 47, a: 1}, |
| 67 | + grey: {r: 128, g: 128, b: 128, a: 1}, |
| 68 | + honeydew: {r: 240, g: 255, b: 240, a: 1}, |
| 69 | + hotpink: {r: 255, g: 105, b: 180, a: 1}, |
| 70 | + indianred: {r: 205, g: 92, b: 92, a: 1}, |
| 71 | + indigo: {r: 75, g: 0, b: 130, a: 1}, |
| 72 | + ivory: {r: 255, g: 255, b: 240, a: 1}, |
| 73 | + khaki: {r: 240, g: 230, b: 140, a: 1}, |
| 74 | + lavender: {r: 230, g: 230, b: 250, a: 1}, |
| 75 | + lavenderblush: {r: 255, g: 240, b: 245, a: 1}, |
| 76 | + lawngreen: {r: 124, g: 252, b: 0, a: 1}, |
| 77 | + lemonchiffon: {r: 255, g: 250, b: 205, a: 1}, |
| 78 | + lightblue: {r: 173, g: 216, b: 230, a: 1}, |
| 79 | + lightcoral: {r: 240, g: 128, b: 128, a: 1}, |
| 80 | + lightcyan: {r: 224, g: 255, b: 255, a: 1}, |
| 81 | + lightgoldenrodyellow: {r: 250, g: 250, b: 210, a: 1}, |
| 82 | + lightgray: {r: 211, g: 211, b: 211, a: 1}, |
| 83 | + lightgreen: {r: 144, g: 238, b: 144, a: 1}, |
| 84 | + lightgrey: {r: 211, g: 211, b: 211, a: 1}, |
| 85 | + lightpink: {r: 255, g: 182, b: 193, a: 1}, |
| 86 | + lightsalmon: {r: 255, g: 160, b: 122, a: 1}, |
| 87 | + lightseagreen: {r: 32, g: 178, b: 170, a: 1}, |
| 88 | + lightskyblue: {r: 135, g: 206, b: 250, a: 1}, |
| 89 | + lightslategray: {r: 119, g: 136, b: 153, a: 1}, |
| 90 | + lightslategrey: {r: 119, g: 136, b: 153, a: 1}, |
| 91 | + lightsteelblue: {r: 176, g: 196, b: 222, a: 1}, |
| 92 | + lightyellow: {r: 255, g: 255, b: 224, a: 1}, |
| 93 | + lime: {r: 0, g: 255, b: 0, a: 1}, |
| 94 | + limegreen: {r: 50, g: 205, b: 50, a: 1}, |
| 95 | + linen: {r: 250, g: 240, b: 230, a: 1}, |
| 96 | + magenta: {r: 255, g: 0, b: 255, a: 1}, |
| 97 | + maroon: {r: 128, g: 0, b: 0, a: 1}, |
| 98 | + mediumaquamarine: {r: 102, g: 205, b: 170, a: 1}, |
| 99 | + mediumblue: {r: 0, g: 0, b: 205, a: 1}, |
| 100 | + mediumorchid: {r: 186, g: 85, b: 211, a: 1}, |
| 101 | + mediumpurple: {r: 147, g: 112, b: 219, a: 1}, |
| 102 | + mediumseagreen: {r: 60, g: 179, b: 113, a: 1}, |
| 103 | + mediumslateblue: {r: 123, g: 104, b: 238, a: 1}, |
| 104 | + mediumspringgreen: {r: 0, g: 250, b: 154, a: 1}, |
| 105 | + mediumturquoise: {r: 72, g: 209, b: 204, a: 1}, |
| 106 | + mediumvioletred: {r: 199, g: 21, b: 133, a: 1}, |
| 107 | + midnightblue: {r: 25, g: 25, b: 112, a: 1}, |
| 108 | + mintcream: {r: 245, g: 255, b: 250, a: 1}, |
| 109 | + mistyrose: {r: 255, g: 228, b: 225, a: 1}, |
| 110 | + moccasin: {r: 255, g: 228, b: 181, a: 1}, |
| 111 | + navajowhite: {r: 255, g: 222, b: 173, a: 1}, |
| 112 | + navy: {r: 0, g: 0, b: 128, a: 1}, |
| 113 | + oldlace: {r: 253, g: 245, b: 230, a: 1}, |
| 114 | + olive: {r: 128, g: 128, b: 0, a: 1}, |
| 115 | + olivedrab: {r: 107, g: 142, b: 35, a: 1}, |
| 116 | + orange: {r: 255, g: 165, b: 0, a: 1}, |
| 117 | + orangered: {r: 255, g: 69, b: 0, a: 1}, |
| 118 | + orchid: {r: 218, g: 112, b: 214, a: 1}, |
| 119 | + palegoldenrod: {r: 238, g: 232, b: 170, a: 1}, |
| 120 | + palegreen: {r: 152, g: 251, b: 152, a: 1}, |
| 121 | + paleturquoise: {r: 175, g: 238, b: 238, a: 1}, |
| 122 | + palevioletred: {r: 219, g: 112, b: 147, a: 1}, |
| 123 | + papayawhip: {r: 255, g: 239, b: 213, a: 1}, |
| 124 | + peachpuff: {r: 255, g: 218, b: 185, a: 1}, |
| 125 | + peru: {r: 205, g: 133, b: 63, a: 1}, |
| 126 | + pink: {r: 255, g: 192, b: 203, a: 1}, |
| 127 | + plum: {r: 221, g: 160, b: 221, a: 1}, |
| 128 | + powderblue: {r: 176, g: 224, b: 230, a: 1}, |
| 129 | + purple: {r: 128, g: 0, b: 128, a: 1}, |
| 130 | + red: {r: 255, g: 0, b: 0, a: 1}, |
| 131 | + rosybrown: {r: 188, g: 143, b: 143, a: 1}, |
| 132 | + royalblue: {r: 65, g: 105, b: 225, a: 1}, |
| 133 | + saddlebrown: {r: 139, g: 69, b: 19, a: 1}, |
| 134 | + salmon: {r: 250, g: 128, b: 114, a: 1}, |
| 135 | + sandybrown: {r: 244, g: 164, b: 96, a: 1}, |
| 136 | + seagreen: {r: 46, g: 139, b: 87, a: 1}, |
| 137 | + seashell: {r: 255, g: 245, b: 238, a: 1}, |
| 138 | + sienna: {r: 160, g: 82, b: 45, a: 1}, |
| 139 | + silver: {r: 192, g: 192, b: 192, a: 1}, |
| 140 | + skyblue: {r: 135, g: 206, b: 235, a: 1}, |
| 141 | + slateblue: {r: 106, g: 90, b: 205, a: 1}, |
| 142 | + slategray: {r: 112, g: 128, b: 144, a: 1}, |
| 143 | + slategrey: {r: 112, g: 128, b: 144, a: 1}, |
| 144 | + snow: {r: 255, g: 250, b: 250, a: 1}, |
| 145 | + springgreen: {r: 0, g: 255, b: 127, a: 1}, |
| 146 | + steelblue: {r: 70, g: 130, b: 180, a: 1}, |
| 147 | + tan: {r: 210, g: 180, b: 140, a: 1}, |
| 148 | + teal: {r: 0, g: 128, b: 128, a: 1}, |
| 149 | + thistle: {r: 216, g: 191, b: 216, a: 1}, |
| 150 | + tomato: {r: 255, g: 99, b: 71, a: 1}, |
| 151 | + turquoise: {r: 64, g: 224, b: 208, a: 1}, |
| 152 | + violet: {r: 238, g: 130, b: 238, a: 1}, |
| 153 | + wheat: {r: 245, g: 222, b: 179, a: 1}, |
| 154 | + white: {r: 255, g: 255, b: 255, a: 1}, |
| 155 | + whitesmoke: {r: 245, g: 245, b: 245, a: 1}, |
| 156 | + yellow: {r: 255, g: 255, b: 0, a: 1}, |
| 157 | + yellowgreen: {r: 154, g: 205, b: 50, a: 1} |
| 158 | +}; |
| 159 | + |
| 160 | +const convertHue = (p, q, h) => { |
| 161 | + if (h < 0) h++; |
| 162 | + if (h > 1) h--; |
| 163 | + |
| 164 | + let color; |
| 165 | + |
| 166 | + if (h * 6 < 1) { |
| 167 | + color = p + ((q - p) * h * 6); |
| 168 | + } else if (h * 2 < 1) { |
| 169 | + color = q; |
| 170 | + } else if (h * 3 < 2) { |
| 171 | + color = p + ((q - p) * ((2 / 3) - h) * 6); |
| 172 | + } else { |
| 173 | + color = p; |
| 174 | + } |
| 175 | + |
| 176 | + return Math.round(color * 255); |
| 177 | +}; |
| 178 | + |
| 179 | +const convertRGB = value => { |
| 180 | + const str = value.toString(16); |
| 181 | + if (value < 16) return `0${str}`; |
| 182 | + |
| 183 | + return str; |
| 184 | +}; |
| 185 | + |
| 186 | +const mixValue = (a, b, ratio) => a + ((b - a) * ratio); |
| 187 | + |
| 188 | +class Color { |
| 189 | + |
| 190 | + /** |
| 191 | + * @param {string|{ r: number; g: number; b: number; a: number;}} color |
| 192 | + */ |
| 193 | + constructor(color) { |
| 194 | + if (typeof color === 'string') { |
| 195 | + this._parse(color); |
| 196 | + } else if (color != null && typeof color === 'object') { |
| 197 | + this.r = color.r | 0; |
| 198 | + this.g = color.g | 0; |
| 199 | + this.b = color.b | 0; |
| 200 | + this.a = +color.a; |
| 201 | + } else { |
| 202 | + throw new TypeError('color is required!'); |
| 203 | + } |
| 204 | + |
| 205 | + if (this.r < 0 || this.r > 255 |
| 206 | + || this.g < 0 || this.g > 255 |
| 207 | + || this.b < 0 || this.b > 255 |
| 208 | + || this.a < 0 || this.a > 1) { |
| 209 | + throw new RangeError(`{r: ${this.r}, g: ${this.g}, b: ${this.b}, a: ${this.a}} is invalid.`); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + /** |
| 214 | + * @param {string} color |
| 215 | + */ |
| 216 | + _parse(color) { |
| 217 | + color = color.toLowerCase(); |
| 218 | + |
| 219 | + if (Object.prototype.hasOwnProperty.call(colorNames, color)) { |
| 220 | + const obj = colorNames[color]; |
| 221 | + |
| 222 | + this.r = obj.r; |
| 223 | + this.g = obj.g; |
| 224 | + this.b = obj.b; |
| 225 | + this.a = obj.a; |
| 226 | + |
| 227 | + return; |
| 228 | + } |
| 229 | + |
| 230 | + if (rHex3.test(color)) { |
| 231 | + const txt = color.substring(1); |
| 232 | + const code = parseInt(txt, 16); |
| 233 | + |
| 234 | + this.r = ((code & 0xF00) >> 8) * 17; |
| 235 | + this.g = ((code & 0xF0) >> 4) * 17; |
| 236 | + this.b = (code & 0xF) * 17; |
| 237 | + this.a = 1; |
| 238 | + |
| 239 | + return; |
| 240 | + } |
| 241 | + |
| 242 | + if (rHex6.test(color)) { |
| 243 | + const txt = color.substring(1); |
| 244 | + const code = parseInt(txt, 16); |
| 245 | + |
| 246 | + this.r = (code & 0xFF0000) >> 16; |
| 247 | + this.g = (code & 0xFF00) >> 8; |
| 248 | + this.b = code & 0xFF; |
| 249 | + this.a = 1; |
| 250 | + |
| 251 | + return; |
| 252 | + } |
| 253 | + |
| 254 | + let match = color.match(rRGB); |
| 255 | + |
| 256 | + if (match) { |
| 257 | + this.r = match[1] | 0; |
| 258 | + this.g = match[2] | 0; |
| 259 | + this.b = match[3] | 0; |
| 260 | + this.a = match[4] ? +match[4] : 1; |
| 261 | + |
| 262 | + return; |
| 263 | + } |
| 264 | + |
| 265 | + match = color.match(rHSL); |
| 266 | + |
| 267 | + if (match) { |
| 268 | + const h = +match[1] / 360; |
| 269 | + const s = +match[2] / 100; |
| 270 | + const l = +match[3] / 100; |
| 271 | + |
| 272 | + this.a = match[4] ? +match[4] : 1; |
| 273 | + |
| 274 | + if (!s) { |
| 275 | + this.r = this.g = this.b = l * 255; |
| 276 | + } |
| 277 | + |
| 278 | + const q = l < 0.5 ? l * (1 + s) : l + s - (l * s); |
| 279 | + const p = (2 * l) - q; |
| 280 | + |
| 281 | + const rt = h + (1 / 3); |
| 282 | + const gt = h; |
| 283 | + const bt = h - (1 / 3); |
| 284 | + |
| 285 | + this.r = convertHue(p, q, rt); |
| 286 | + this.g = convertHue(p, q, gt); |
| 287 | + this.b = convertHue(p, q, bt); |
| 288 | + |
| 289 | + return; |
| 290 | + } |
| 291 | + |
| 292 | + throw new Error(`${color} is not a supported color format.`); |
| 293 | + } |
| 294 | + |
| 295 | + toString() { |
| 296 | + if (this.a === 1) { |
| 297 | + const r = convertRGB(this.r); |
| 298 | + const g = convertRGB(this.g); |
| 299 | + const b = convertRGB(this.b); |
| 300 | + |
| 301 | + if (this.r % 17 || this.g % 17 || this.b % 17) { |
| 302 | + return `#${r}${g}${b}`; |
| 303 | + } |
| 304 | + |
| 305 | + return `#${r[0]}${g[0]}${b[0]}`; |
| 306 | + } |
| 307 | + |
| 308 | + return `rgba(${this.r}, ${this.g}, ${this.b}, ${parseFloat(this.a.toFixed(2))})`; |
| 309 | + } |
| 310 | + |
| 311 | + /** |
| 312 | + * @param {string|{ r: number; g: number; b: number; a: number;}} color |
| 313 | + * @param {number} ratio |
| 314 | + */ |
| 315 | + mix(color, ratio) { |
| 316 | + if (ratio > 1 || ratio < 0) { |
| 317 | + throw new RangeError('Valid numbers is only between 0 and 1.'); |
| 318 | + } |
| 319 | + switch (ratio) { |
| 320 | + case 0: |
| 321 | + return new Color(this); |
| 322 | + |
| 323 | + case 1: |
| 324 | + return new Color(color); |
| 325 | + } |
| 326 | + |
| 327 | + return new Color({ |
| 328 | + r: Math.round(mixValue(this.r, color.r, ratio)), |
| 329 | + g: Math.round(mixValue(this.g, color.g, ratio)), |
| 330 | + b: Math.round(mixValue(this.b, color.b, ratio)), |
| 331 | + a: mixValue(this.a, color.a, ratio) |
| 332 | + }); |
| 333 | + } |
| 334 | +} |
| 335 | + |
| 336 | +module.exports = Color; |
0 commit comments