UNPKG

4.71 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5// these algorithms are sourced from https://drafts.csswg.org/css-color/#color-conversion-code
6
7function lin_sRGB(RGB) {
8 // convert an array of sRGB values in the range 0.0 - 1.0
9 // to linear light (un-companded) form.
10 // https://en.wikipedia.org/wiki/SRGB
11 return RGB.map((val) => {
12 if (val < 0.04045) {
13 return val / 12.92;
14 }
15
16 return Math.pow((val + 0.055) / 1.055, 2.4);
17 });
18}
19
20function matrixMultiple3d(matrix, vector) {
21 return [
22 matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
23 matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
24 matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2],
25 ];
26}
27
28function srgb2xyz(srgb) {
29 return matrixMultiple3d(
30 [
31 [0.4124564, 0.3575761, 0.1804375],
32 [0.2126729, 0.7151522, 0.072175],
33 [0.0193339, 0.119192, 0.9503041],
34 ],
35 srgb,
36 );
37}
38
39function chromaticAdaptationD65_D50(xyz) {
40 return matrixMultiple3d(
41 [
42 [1.0478112, 0.0228866, -0.050127],
43 [0.0295424, 0.9904844, -0.0170491],
44 [-0.0092345, 0.0150436, 0.7521316],
45 ],
46 xyz,
47 );
48}
49
50function xyz2lab(xyzIn) {
51 // Assuming XYZ is relative to D50, convert to CIE Lab
52 // from CIE standard, which now defines these as a rational fraction
53 const ε = 216 / 24389; // 6^3/29^3
54 const κ = 24389 / 27; // 29^3/3^3
55 const white = [0.9642, 1.0, 0.8249]; // D50 reference white
56
57 // compute xyz, which is XYZ scaled relative to reference white
58 const xyz = xyzIn.map((value, i) => value / white[i]);
59
60 // now compute f
61 const f = xyz.map((value) => {
62 if (value > ε) {
63 return Math.cbrt(value);
64 }
65
66 return (κ * value + 16) / 116;
67 });
68
69 return [
70 116 * f[1] - 16, // L
71 500 * (f[0] - f[1]), // a
72 200 * (f[1] - f[2]), // b
73 ];
74}
75
76function rgb2hsl(r, g, b) {
77 r /= 255;
78 g /= 255;
79 b /= 255;
80 let h;
81 let s;
82 let l;
83 const M = Math.max(r, g, b);
84 const m = Math.min(r, g, b);
85 const d = M - m;
86
87 if (d === 0) {
88 h = 0;
89 } else if (M === r) {
90 h = ((g - b) / d) % 6;
91 } else if (M === g) {
92 h = (b - r) / d + 2;
93 } else {
94 h = (r - g) / d + 4;
95 }
96
97 h *= 60;
98
99 if (h < 0) {
100 h += 360;
101 }
102
103 l = (M + m) / 2;
104
105 if (d === 0) {
106 s = 0;
107 } else {
108 s = d / (1 - Math.abs(2 * l - 1));
109 }
110
111 s *= 100;
112 l *= 100;
113
114 return [Math.round(h), Math.round(s), Math.round(l)];
115}
116
117function rgb2hwb(rgb_r, rgb_g, rgb_b) {
118 rgb_r /= 255;
119 rgb_g /= 255;
120 rgb_b /= 255;
121
122 const w = Math.min(rgb_r, rgb_g, rgb_b);
123 const v = Math.max(rgb_r, rgb_g, rgb_b);
124
125 const b = 1 - v;
126
127 if (v === w) {
128 return [0, Math.round(w * 100), Math.round(b * 100)];
129 }
130
131 const f = rgb_r === w ? rgb_g - rgb_b : rgb_g === w ? rgb_b - rgb_r : rgb_r - rgb_g;
132 const i = rgb_r === w ? 3 : rgb_g === w ? 5 : 1;
133
134 return [
135 Math.round(((i - f / (v - w)) / 6) * 360) % 360,
136 Math.round(w * 100),
137 Math.round(b * 100),
138 ];
139}
140
141function perc255(value) {
142 return `${Math.round((value * 100) / 255)}%`;
143}
144
145function generateColorFuncs(hexString) {
146 if (hexString.length !== 7) {
147 throw new Error(
148 `Invalid hex string color definition (${hexString}) - expected 6 character hex string`,
149 );
150 }
151
152 const rgb = [0, 0, 0];
153
154 for (let i = 0; i < 3; i += 1) {
155 rgb[i] = parseInt(hexString.substr(2 * i + 1, 2), 16);
156 }
157
158 const hsl = rgb2hsl(rgb[0], rgb[1], rgb[2]);
159 const hwb = rgb2hwb(rgb[0], rgb[1], rgb[2]);
160 const func = [];
161 const rgbStr = `${rgb[0]},${rgb[1]},${rgb[2]}`;
162 const rgbPercStr = `${perc255(rgb[0])},${perc255(rgb[1])},${perc255(rgb[2])}`;
163 const hslStr = `${hsl[0]},${hsl[1]}%,${hsl[2]}%`;
164 const hwbStr = `${hwb[0]},${hwb[1]}%,${hwb[2]}%`;
165
166 // *very* convoluted process, just to be able to establish if the color
167 // is gray -- or not.
168 const linRgb = lin_sRGB([rgb[0] / 255, rgb[1] / 255, rgb[2] / 255]);
169 const xyz_d65 = srgb2xyz(linRgb);
170 const xyz_d50 = chromaticAdaptationD65_D50(xyz_d65);
171 const lab = xyz2lab(xyz_d50);
172
173 func.push(`rgb(${rgbStr})`);
174 func.push(`rgba(${rgbStr},1)`);
175 func.push(`rgba(${rgbStr},100%)`);
176 func.push(`rgb(${rgbPercStr})`);
177 func.push(`rgba(${rgbPercStr},1)`);
178 func.push(`rgba(${rgbPercStr},100%)`);
179 func.push(`hsl(${hslStr})`);
180 func.push(`hsla(${hslStr},1)`);
181 func.push(`hsla(${hslStr},100%)`);
182 func.push(`hwb(${hwbStr})`);
183 func.push(`hwb(${hwbStr},1)`);
184 func.push(`hwb(${hwbStr},100%)`);
185
186 // technically, this should be 0 - but then #808080 wouldn't even be gray
187 if (lab[1] * lab[1] < 0.01 && lab[2] * lab[2] < 0.01) {
188 // yay! gray!
189 const grayStr = Math.round(lab[0]);
190
191 func.push(`gray(${grayStr})`);
192 func.push(`gray(${grayStr},1)`);
193 func.push(`gray(${grayStr},100%)`);
194 func.push(`gray(${grayStr}%)`);
195 func.push(`gray(${grayStr}%,1)`);
196 func.push(`gray(${grayStr}%,100%)`);
197 }
198
199 return func;
200}
201
202module.exports = generateColorFuncs;