Skip to content

Commit 52820ac

Browse files
authored
Merge pull request #58 from segayuu/Add-Color
Add Color class from tagcloud hexo helper
2 parents ab30d63 + 6d9df36 commit 52820ac

File tree

3 files changed

+390
-0
lines changed

3 files changed

+390
-0
lines changed

lib/color.js

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
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;

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
describe('util', () => {
44
require('./scripts/cache_stream');
55
require('./scripts/camel_case_keys');
6+
require('./scripts/color');
67
require('./scripts/escape_diacritic');
78
require('./scripts/escape_html');
89
require('./scripts/escape_regexp');

test/scripts/color.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
describe('color', () => {
4+
const Color = require('../../lib/color');
5+
6+
it('name', () => {
7+
const red = new Color('red');
8+
const pink = new Color('pink');
9+
const mid1 = red.mix(pink, 1 / 3);
10+
const mid2 = red.mix(pink, 2 / 3);
11+
12+
`${red}`.should.eql('#f00');
13+
`${pink}`.should.eql('#ffc0cb');
14+
`${mid1}`.should.eql('#ff4044');
15+
`${mid2}`.should.eql('#ff8087');
16+
});
17+
18+
it('hex', () => {
19+
const red = new Color('#f00');
20+
const pink = new Color('#ffc0cb');
21+
const mid1 = red.mix(pink, 1 / 3);
22+
const mid2 = red.mix(pink, 2 / 3);
23+
24+
`${red}`.should.eql('#f00');
25+
`${pink}`.should.eql('#ffc0cb');
26+
`${mid1}`.should.eql('#ff4044');
27+
`${mid2}`.should.eql('#ff8087');
28+
});
29+
30+
it('RGBA', () => {
31+
const steelblueA = new Color('rgba(70, 130, 180, 0.3)');
32+
const steelblue = new Color('rgb(70, 130, 180)');
33+
const mid1 = steelblueA.mix(steelblue, 1 / 3);
34+
const mid2 = steelblueA.mix(steelblue, 2 / 3);
35+
36+
`${steelblueA}`.should.eql('rgba(70, 130, 180, 0.3)');
37+
`${steelblue}`.should.eql('#4682b4');
38+
`${mid1}`.should.eql('rgba(70, 130, 180, 0.53)');
39+
`${mid2}`.should.eql('rgba(70, 130, 180, 0.77)');
40+
});
41+
42+
it('HSLA', () => {
43+
const steelblueA = new Color('hsla(207, 44%, 49%, 0.3)');
44+
const steelblue = new Color('hsl(207, 44%, 49%)');
45+
const mid1 = steelblueA.mix(steelblue, 1 / 3);
46+
const mid2 = steelblueA.mix(steelblue, 2 / 3);
47+
48+
`${steelblueA}`.should.eql('rgba(70, 130, 180, 0.3)');
49+
`${steelblue}`.should.eql('#4682b4');
50+
`${mid1}`.should.eql('rgba(70, 130, 180, 0.53)');
51+
`${mid2}`.should.eql('rgba(70, 130, 180, 0.77)');
52+
});
53+
});

0 commit comments

Comments
 (0)