@etsoo/shared 1.1.15 → 1.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,12 @@ Etsoo implmented Color
26
26
  |static getColors|Get HEX or RGB colors|
27
27
  |static getEColors|Get EColors|
28
28
  |static parse|Parse HTML color to EColor|
29
+ |clone|Clone color with adjustments|
30
+ |getContrastRatio|Get contrast ratio, a value between 0 and 1|
31
+ |getDeltaValue|Get Delta value (perceptible by human eyes)|
32
+ |getLuminance|Get luminance|
29
33
  |toHEXColor|To HEX color string|
34
+ |toLabValue|To Lab value|
30
35
  |toRGBColor|To RGB color string|
31
36
 
32
37
  ## Keyboard
@@ -13,5 +13,6 @@ test('Tests for parse', () => {
13
13
  });
14
14
 
15
15
  test('Tests for getColors', () => {
16
- expect(EColor.getColors(undefined, 128).length).toBe(8);
16
+ const colors = EColor.getColors(undefined, 128);
17
+ expect(colors.length).toBe(8);
17
18
  });
@@ -17,17 +17,19 @@ export declare class EColor {
17
17
  * Get HEX or RGB colors
18
18
  * @param init Initial color
19
19
  * @param factor Increase factor
20
+ * @param adjustOrder Adjust order to increase difference
20
21
  * @param hex to HEX or not
21
22
  * @returns Result
22
23
  */
23
- static getColors(init?: string, factor?: number, hex?: boolean): string[];
24
+ static getColors(init?: string, factor?: number, adjustOrder?: boolean, hex?: boolean): string[];
24
25
  /**
25
26
  * Get EColors
26
27
  * @param init Initial color
27
28
  * @param factor Increase factor
29
+ * @param adjustOrder Adjust order to increase difference
28
30
  * @returns Result
29
31
  */
30
- static getEColors(init?: string, factor?: number): EColor[];
32
+ static getEColors(init?: string, factor?: number, adjustOrder?: boolean): EColor[];
31
33
  /**
32
34
  * HEX string to integer value
33
35
  * @param hex HEX string
@@ -55,7 +57,7 @@ export declare class EColor {
55
57
  */
56
58
  constructor(r: number, g: number, b: number, alpha?: number | undefined);
57
59
  /**
58
- * Clone color with adjusts
60
+ * Clone color with adjustments
59
61
  * @param adjustR Adjust R value
60
62
  * @param adjustG Adjust G value
61
63
  * @param adjustB Adjust B value
@@ -63,14 +65,41 @@ export declare class EColor {
63
65
  */
64
66
  clone(adjustR?: number, adjustG?: number, adjustB?: number, alpha?: number): EColor | undefined;
65
67
  /**
66
- * To RGB color string
67
- * @param includeAlpha Include alpha or not
68
- * @returns RGB color string
68
+ * Get contrast ratio, a value between 0 and 1
69
+ * @param color Contrast color
69
70
  */
70
- toRGBColor(includeAlpha?: boolean): string;
71
+ getContrastRatio(color: EColor): number;
72
+ /**
73
+ * Get Delta value (perceptible by human eyes)
74
+ * <= 1, Not perceptible by human eyes
75
+ * 1 - 2, Perceptible through close observation
76
+ * 2 - 10, Perceptible at a glance
77
+ * 11 - 49, Colors are more similar than opposite
78
+ * 100+, Colors are exact opposite
79
+ * @param color Contrast color
80
+ * @returns Value
81
+ */
82
+ getDeltaValue(color: EColor): number;
83
+ /**
84
+ * Get luminance
85
+ * Darker one has higher luminance
86
+ * https://dev.to/alvaromontoro/building-your-own-color-contrast-checker-4j7o
87
+ */
88
+ getLuminance(): number;
71
89
  /**
72
90
  * To HEX color string
73
91
  * @returns HEX color string
74
92
  */
75
93
  toHEXColor(): string;
94
+ /**
95
+ * To Lab value
96
+ * @returns Lab value
97
+ */
98
+ toLabValue(): [number, number, number];
99
+ /**
100
+ * To RGB color string
101
+ * @param includeAlpha Include alpha or not
102
+ * @returns RGB color string
103
+ */
104
+ toRGBColor(includeAlpha?: boolean): string;
76
105
  }
@@ -36,19 +36,21 @@ class EColor {
36
36
  * Get HEX or RGB colors
37
37
  * @param init Initial color
38
38
  * @param factor Increase factor
39
+ * @param adjustOrder Adjust order to increase difference
39
40
  * @param hex to HEX or not
40
41
  * @returns Result
41
42
  */
42
- static getColors(init = '#000', factor = 51, hex = true) {
43
- return EColor.getEColors(init, factor).map((c) => hex ? c.toHEXColor() : c.toRGBColor());
43
+ static getColors(init = '#000', factor = 51, adjustOrder = true, hex = true) {
44
+ return EColor.getEColors(init, factor, adjustOrder).map((c) => hex ? c.toHEXColor() : c.toRGBColor());
44
45
  }
45
46
  /**
46
47
  * Get EColors
47
48
  * @param init Initial color
48
49
  * @param factor Increase factor
50
+ * @param adjustOrder Adjust order to increase difference
49
51
  * @returns Result
50
52
  */
51
- static getEColors(init = '#000', factor = 51) {
53
+ static getEColors(init = '#000', factor = 51, adjustOrder = true) {
52
54
  var _a;
53
55
  // Init color
54
56
  const initColor = (_a = EColor.parse(init)) !== null && _a !== void 0 ? _a : new EColor(0, 0, 0);
@@ -65,13 +67,38 @@ class EColor {
65
67
  for (const r of factors) {
66
68
  for (const g of factors) {
67
69
  for (const b of factors) {
68
- const newColor = initColor.clone(r, g, b);
69
- if (newColor)
70
- colors.push(newColor);
70
+ colors.push(initColor.clone(r, g, b));
71
71
  }
72
72
  }
73
73
  }
74
- return colors;
74
+ // Non-nullable colors
75
+ const nColors = colors.filter((color) => color != null);
76
+ // Adjust order
77
+ if (adjustOrder) {
78
+ const firstColor = nColors.shift();
79
+ if (firstColor) {
80
+ let color = firstColor;
81
+ const newColors = [color];
82
+ while (nColors.length > 0) {
83
+ const result = nColors.reduce((p, c, index) => {
84
+ const delta = color.getDeltaValue(c);
85
+ if (delta != null && delta > p.delta) {
86
+ p.delta = delta;
87
+ p.color = c;
88
+ p.index = index;
89
+ }
90
+ return p;
91
+ }, { delta: 0, color, index: -1 });
92
+ if (result.delta > 0) {
93
+ color = result.color;
94
+ newColors.push(color);
95
+ nColors.splice(result.index, 1);
96
+ }
97
+ }
98
+ return newColors;
99
+ }
100
+ }
101
+ return nColors;
75
102
  }
76
103
  /**
77
104
  * HEX string to integer value
@@ -124,7 +151,7 @@ class EColor {
124
151
  return undefined;
125
152
  }
126
153
  /**
127
- * Clone color with adjusts
154
+ * Clone color with adjustments
128
155
  * @param adjustR Adjust R value
129
156
  * @param adjustG Adjust G value
130
157
  * @param adjustB Adjust B value
@@ -141,6 +168,86 @@ class EColor {
141
168
  return undefined;
142
169
  return new EColor(r, g, b, alpha);
143
170
  }
171
+ /**
172
+ * Get contrast ratio, a value between 0 and 1
173
+ * @param color Contrast color
174
+ */
175
+ getContrastRatio(color) {
176
+ const lum1 = this.getLuminance();
177
+ const lum2 = color.getLuminance();
178
+ const brightest = Math.max(lum1, lum2);
179
+ const darkest = Math.min(lum1, lum2);
180
+ return (brightest + 0.05) / (darkest + 0.05);
181
+ }
182
+ /**
183
+ * Get Delta value (perceptible by human eyes)
184
+ * <= 1, Not perceptible by human eyes
185
+ * 1 - 2, Perceptible through close observation
186
+ * 2 - 10, Perceptible at a glance
187
+ * 11 - 49, Colors are more similar than opposite
188
+ * 100+, Colors are exact opposite
189
+ * @param color Contrast color
190
+ * @returns Value
191
+ */
192
+ getDeltaValue(color) {
193
+ const labA = this.toLabValue();
194
+ const labB = color.toLabValue();
195
+ const deltaL = labA[0] - labB[0];
196
+ const deltaA = labA[1] - labB[1];
197
+ const deltaB = labA[2] - labB[2];
198
+ const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
199
+ const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
200
+ const deltaC = c1 - c2;
201
+ let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
202
+ deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
203
+ const sc = 1.0 + 0.045 * c1;
204
+ const sh = 1.0 + 0.015 * c1;
205
+ const deltaLKlsl = deltaL / 1.0;
206
+ const deltaCkcsc = deltaC / sc;
207
+ const deltaHkhsh = deltaH / sh;
208
+ const i = deltaLKlsl * deltaLKlsl +
209
+ deltaCkcsc * deltaCkcsc +
210
+ deltaHkhsh * deltaHkhsh;
211
+ return i < 0 ? 0 : Math.sqrt(i);
212
+ }
213
+ /**
214
+ * Get luminance
215
+ * Darker one has higher luminance
216
+ * https://dev.to/alvaromontoro/building-your-own-color-contrast-checker-4j7o
217
+ */
218
+ getLuminance() {
219
+ const a = [this.r, this.g, this.b].map((v) => {
220
+ v /= 255;
221
+ return v <= 0.03928
222
+ ? v / 12.92
223
+ : Math.pow((v + 0.055) / 1.055, 2.4);
224
+ });
225
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
226
+ }
227
+ /**
228
+ * To HEX color string
229
+ * @returns HEX color string
230
+ */
231
+ toHEXColor() {
232
+ return `#${EColor.toHex(this.r)}${EColor.toHex(this.g)}${EColor.toHex(this.b)}`;
233
+ }
234
+ /**
235
+ * To Lab value
236
+ * @returns Lab value
237
+ */
238
+ toLabValue() {
239
+ let r = this.r / 255, g = this.g / 255, b = this.b / 255, x, y, z;
240
+ r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
241
+ g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
242
+ b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
243
+ x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
244
+ y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.0;
245
+ z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
246
+ x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;
247
+ y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;
248
+ z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;
249
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
250
+ }
144
251
  /**
145
252
  * To RGB color string
146
253
  * @param includeAlpha Include alpha or not
@@ -154,12 +261,5 @@ class EColor {
154
261
  return `RGBA(${this.r}, ${this.g}, ${this.b}, ${(_a = this.alpha) !== null && _a !== void 0 ? _a : 1})`;
155
262
  return `RGB(${this.r}, ${this.g}, ${this.b})`;
156
263
  }
157
- /**
158
- * To HEX color string
159
- * @returns HEX color string
160
- */
161
- toHEXColor() {
162
- return `#${EColor.toHex(this.r)}${EColor.toHex(this.g)}${EColor.toHex(this.b)}`;
163
- }
164
264
  }
165
265
  exports.EColor = EColor;
@@ -17,17 +17,19 @@ export declare class EColor {
17
17
  * Get HEX or RGB colors
18
18
  * @param init Initial color
19
19
  * @param factor Increase factor
20
+ * @param adjustOrder Adjust order to increase difference
20
21
  * @param hex to HEX or not
21
22
  * @returns Result
22
23
  */
23
- static getColors(init?: string, factor?: number, hex?: boolean): string[];
24
+ static getColors(init?: string, factor?: number, adjustOrder?: boolean, hex?: boolean): string[];
24
25
  /**
25
26
  * Get EColors
26
27
  * @param init Initial color
27
28
  * @param factor Increase factor
29
+ * @param adjustOrder Adjust order to increase difference
28
30
  * @returns Result
29
31
  */
30
- static getEColors(init?: string, factor?: number): EColor[];
32
+ static getEColors(init?: string, factor?: number, adjustOrder?: boolean): EColor[];
31
33
  /**
32
34
  * HEX string to integer value
33
35
  * @param hex HEX string
@@ -55,7 +57,7 @@ export declare class EColor {
55
57
  */
56
58
  constructor(r: number, g: number, b: number, alpha?: number | undefined);
57
59
  /**
58
- * Clone color with adjusts
60
+ * Clone color with adjustments
59
61
  * @param adjustR Adjust R value
60
62
  * @param adjustG Adjust G value
61
63
  * @param adjustB Adjust B value
@@ -63,14 +65,41 @@ export declare class EColor {
63
65
  */
64
66
  clone(adjustR?: number, adjustG?: number, adjustB?: number, alpha?: number): EColor | undefined;
65
67
  /**
66
- * To RGB color string
67
- * @param includeAlpha Include alpha or not
68
- * @returns RGB color string
68
+ * Get contrast ratio, a value between 0 and 1
69
+ * @param color Contrast color
69
70
  */
70
- toRGBColor(includeAlpha?: boolean): string;
71
+ getContrastRatio(color: EColor): number;
72
+ /**
73
+ * Get Delta value (perceptible by human eyes)
74
+ * <= 1, Not perceptible by human eyes
75
+ * 1 - 2, Perceptible through close observation
76
+ * 2 - 10, Perceptible at a glance
77
+ * 11 - 49, Colors are more similar than opposite
78
+ * 100+, Colors are exact opposite
79
+ * @param color Contrast color
80
+ * @returns Value
81
+ */
82
+ getDeltaValue(color: EColor): number;
83
+ /**
84
+ * Get luminance
85
+ * Darker one has higher luminance
86
+ * https://dev.to/alvaromontoro/building-your-own-color-contrast-checker-4j7o
87
+ */
88
+ getLuminance(): number;
71
89
  /**
72
90
  * To HEX color string
73
91
  * @returns HEX color string
74
92
  */
75
93
  toHEXColor(): string;
94
+ /**
95
+ * To Lab value
96
+ * @returns Lab value
97
+ */
98
+ toLabValue(): [number, number, number];
99
+ /**
100
+ * To RGB color string
101
+ * @param includeAlpha Include alpha or not
102
+ * @returns RGB color string
103
+ */
104
+ toRGBColor(includeAlpha?: boolean): string;
76
105
  }
@@ -33,19 +33,21 @@ export class EColor {
33
33
  * Get HEX or RGB colors
34
34
  * @param init Initial color
35
35
  * @param factor Increase factor
36
+ * @param adjustOrder Adjust order to increase difference
36
37
  * @param hex to HEX or not
37
38
  * @returns Result
38
39
  */
39
- static getColors(init = '#000', factor = 51, hex = true) {
40
- return EColor.getEColors(init, factor).map((c) => hex ? c.toHEXColor() : c.toRGBColor());
40
+ static getColors(init = '#000', factor = 51, adjustOrder = true, hex = true) {
41
+ return EColor.getEColors(init, factor, adjustOrder).map((c) => hex ? c.toHEXColor() : c.toRGBColor());
41
42
  }
42
43
  /**
43
44
  * Get EColors
44
45
  * @param init Initial color
45
46
  * @param factor Increase factor
47
+ * @param adjustOrder Adjust order to increase difference
46
48
  * @returns Result
47
49
  */
48
- static getEColors(init = '#000', factor = 51) {
50
+ static getEColors(init = '#000', factor = 51, adjustOrder = true) {
49
51
  var _a;
50
52
  // Init color
51
53
  const initColor = (_a = EColor.parse(init)) !== null && _a !== void 0 ? _a : new EColor(0, 0, 0);
@@ -62,13 +64,38 @@ export class EColor {
62
64
  for (const r of factors) {
63
65
  for (const g of factors) {
64
66
  for (const b of factors) {
65
- const newColor = initColor.clone(r, g, b);
66
- if (newColor)
67
- colors.push(newColor);
67
+ colors.push(initColor.clone(r, g, b));
68
68
  }
69
69
  }
70
70
  }
71
- return colors;
71
+ // Non-nullable colors
72
+ const nColors = colors.filter((color) => color != null);
73
+ // Adjust order
74
+ if (adjustOrder) {
75
+ const firstColor = nColors.shift();
76
+ if (firstColor) {
77
+ let color = firstColor;
78
+ const newColors = [color];
79
+ while (nColors.length > 0) {
80
+ const result = nColors.reduce((p, c, index) => {
81
+ const delta = color.getDeltaValue(c);
82
+ if (delta != null && delta > p.delta) {
83
+ p.delta = delta;
84
+ p.color = c;
85
+ p.index = index;
86
+ }
87
+ return p;
88
+ }, { delta: 0, color, index: -1 });
89
+ if (result.delta > 0) {
90
+ color = result.color;
91
+ newColors.push(color);
92
+ nColors.splice(result.index, 1);
93
+ }
94
+ }
95
+ return newColors;
96
+ }
97
+ }
98
+ return nColors;
72
99
  }
73
100
  /**
74
101
  * HEX string to integer value
@@ -121,7 +148,7 @@ export class EColor {
121
148
  return undefined;
122
149
  }
123
150
  /**
124
- * Clone color with adjusts
151
+ * Clone color with adjustments
125
152
  * @param adjustR Adjust R value
126
153
  * @param adjustG Adjust G value
127
154
  * @param adjustB Adjust B value
@@ -138,6 +165,86 @@ export class EColor {
138
165
  return undefined;
139
166
  return new EColor(r, g, b, alpha);
140
167
  }
168
+ /**
169
+ * Get contrast ratio, a value between 0 and 1
170
+ * @param color Contrast color
171
+ */
172
+ getContrastRatio(color) {
173
+ const lum1 = this.getLuminance();
174
+ const lum2 = color.getLuminance();
175
+ const brightest = Math.max(lum1, lum2);
176
+ const darkest = Math.min(lum1, lum2);
177
+ return (brightest + 0.05) / (darkest + 0.05);
178
+ }
179
+ /**
180
+ * Get Delta value (perceptible by human eyes)
181
+ * <= 1, Not perceptible by human eyes
182
+ * 1 - 2, Perceptible through close observation
183
+ * 2 - 10, Perceptible at a glance
184
+ * 11 - 49, Colors are more similar than opposite
185
+ * 100+, Colors are exact opposite
186
+ * @param color Contrast color
187
+ * @returns Value
188
+ */
189
+ getDeltaValue(color) {
190
+ const labA = this.toLabValue();
191
+ const labB = color.toLabValue();
192
+ const deltaL = labA[0] - labB[0];
193
+ const deltaA = labA[1] - labB[1];
194
+ const deltaB = labA[2] - labB[2];
195
+ const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
196
+ const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
197
+ const deltaC = c1 - c2;
198
+ let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
199
+ deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
200
+ const sc = 1.0 + 0.045 * c1;
201
+ const sh = 1.0 + 0.015 * c1;
202
+ const deltaLKlsl = deltaL / 1.0;
203
+ const deltaCkcsc = deltaC / sc;
204
+ const deltaHkhsh = deltaH / sh;
205
+ const i = deltaLKlsl * deltaLKlsl +
206
+ deltaCkcsc * deltaCkcsc +
207
+ deltaHkhsh * deltaHkhsh;
208
+ return i < 0 ? 0 : Math.sqrt(i);
209
+ }
210
+ /**
211
+ * Get luminance
212
+ * Darker one has higher luminance
213
+ * https://dev.to/alvaromontoro/building-your-own-color-contrast-checker-4j7o
214
+ */
215
+ getLuminance() {
216
+ const a = [this.r, this.g, this.b].map((v) => {
217
+ v /= 255;
218
+ return v <= 0.03928
219
+ ? v / 12.92
220
+ : Math.pow((v + 0.055) / 1.055, 2.4);
221
+ });
222
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
223
+ }
224
+ /**
225
+ * To HEX color string
226
+ * @returns HEX color string
227
+ */
228
+ toHEXColor() {
229
+ return `#${EColor.toHex(this.r)}${EColor.toHex(this.g)}${EColor.toHex(this.b)}`;
230
+ }
231
+ /**
232
+ * To Lab value
233
+ * @returns Lab value
234
+ */
235
+ toLabValue() {
236
+ let r = this.r / 255, g = this.g / 255, b = this.b / 255, x, y, z;
237
+ r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
238
+ g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
239
+ b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
240
+ x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
241
+ y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.0;
242
+ z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
243
+ x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;
244
+ y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;
245
+ z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;
246
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
247
+ }
141
248
  /**
142
249
  * To RGB color string
143
250
  * @param includeAlpha Include alpha or not
@@ -151,11 +258,4 @@ export class EColor {
151
258
  return `RGBA(${this.r}, ${this.g}, ${this.b}, ${(_a = this.alpha) !== null && _a !== void 0 ? _a : 1})`;
152
259
  return `RGB(${this.r}, ${this.g}, ${this.b})`;
153
260
  }
154
- /**
155
- * To HEX color string
156
- * @returns HEX color string
157
- */
158
- toHEXColor() {
159
- return `#${EColor.toHex(this.r)}${EColor.toHex(this.g)}${EColor.toHex(this.b)}`;
160
- }
161
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/shared",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "TypeScript shared utilities and functions",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -19,11 +19,17 @@ export class EColor {
19
19
  * Get HEX or RGB colors
20
20
  * @param init Initial color
21
21
  * @param factor Increase factor
22
+ * @param adjustOrder Adjust order to increase difference
22
23
  * @param hex to HEX or not
23
24
  * @returns Result
24
25
  */
25
- static getColors(init = '#000', factor: number = 51, hex: boolean = true) {
26
- return EColor.getEColors(init, factor).map((c) =>
26
+ static getColors(
27
+ init: string = '#000',
28
+ factor: number = 51,
29
+ adjustOrder: boolean = true,
30
+ hex: boolean = true
31
+ ) {
32
+ return EColor.getEColors(init, factor, adjustOrder).map((c) =>
27
33
  hex ? c.toHEXColor() : c.toRGBColor()
28
34
  );
29
35
  }
@@ -32,9 +38,14 @@ export class EColor {
32
38
  * Get EColors
33
39
  * @param init Initial color
34
40
  * @param factor Increase factor
41
+ * @param adjustOrder Adjust order to increase difference
35
42
  * @returns Result
36
43
  */
37
- static getEColors(init = '#000', factor: number = 51): EColor[] {
44
+ static getEColors(
45
+ init: string = '#000',
46
+ factor: number = 51,
47
+ adjustOrder: boolean = true
48
+ ): EColor[] {
38
49
  // Init color
39
50
  const initColor = EColor.parse(init) ?? new EColor(0, 0, 0);
40
51
 
@@ -48,18 +59,53 @@ export class EColor {
48
59
  }
49
60
 
50
61
  // RGB loop
51
- const colors: EColor[] = [initColor];
52
-
62
+ const colors: (EColor | undefined)[] = [initColor];
53
63
  for (const r of factors) {
54
64
  for (const g of factors) {
55
65
  for (const b of factors) {
56
- const newColor = initColor.clone(r, g, b);
57
- if (newColor) colors.push(newColor);
66
+ colors.push(initColor.clone(r, g, b));
67
+ }
68
+ }
69
+ }
70
+
71
+ // Non-nullable colors
72
+ const nColors = colors.filter(
73
+ (color): color is EColor => color != null
74
+ );
75
+
76
+ // Adjust order
77
+ if (adjustOrder) {
78
+ const firstColor = nColors.shift();
79
+ if (firstColor) {
80
+ let color = firstColor;
81
+ const newColors: EColor[] = [color];
82
+
83
+ while (nColors.length > 0) {
84
+ const result = nColors.reduce(
85
+ (p, c, index) => {
86
+ const delta = color.getDeltaValue(c);
87
+ if (delta != null && delta > p.delta) {
88
+ p.delta = delta;
89
+ p.color = c;
90
+ p.index = index;
91
+ }
92
+ return p;
93
+ },
94
+ { delta: 0, color, index: -1 }
95
+ );
96
+
97
+ if (result.delta > 0) {
98
+ color = result.color;
99
+ newColors.push(color);
100
+ nColors.splice(result.index, 1);
101
+ }
58
102
  }
103
+
104
+ return newColors;
59
105
  }
60
106
  }
61
107
 
62
- return colors;
108
+ return nColors;
63
109
  }
64
110
 
65
111
  /**
@@ -143,7 +189,7 @@ export class EColor {
143
189
  ) {}
144
190
 
145
191
  /**
146
- * Clone color with adjusts
192
+ * Clone color with adjustments
147
193
  * @param adjustR Adjust R value
148
194
  * @param adjustG Adjust G value
149
195
  * @param adjustB Adjust B value
@@ -169,18 +215,63 @@ export class EColor {
169
215
  }
170
216
 
171
217
  /**
172
- * To RGB color string
173
- * @param includeAlpha Include alpha or not
174
- * @returns RGB color string
218
+ * Get contrast ratio, a value between 0 and 1
219
+ * @param color Contrast color
175
220
  */
176
- toRGBColor(includeAlpha?: boolean) {
177
- // Default case
178
- includeAlpha ??= this.alpha != null;
221
+ getContrastRatio(color: EColor) {
222
+ const lum1 = this.getLuminance();
223
+ const lum2 = color.getLuminance();
224
+ const brightest = Math.max(lum1, lum2);
225
+ const darkest = Math.min(lum1, lum2);
226
+ return (brightest + 0.05) / (darkest + 0.05);
227
+ }
179
228
 
180
- if (includeAlpha)
181
- return `RGBA(${this.r}, ${this.g}, ${this.b}, ${this.alpha ?? 1})`;
229
+ /**
230
+ * Get Delta value (perceptible by human eyes)
231
+ * <= 1, Not perceptible by human eyes
232
+ * 1 - 2, Perceptible through close observation
233
+ * 2 - 10, Perceptible at a glance
234
+ * 11 - 49, Colors are more similar than opposite
235
+ * 100+, Colors are exact opposite
236
+ * @param color Contrast color
237
+ * @returns Value
238
+ */
239
+ getDeltaValue(color: EColor) {
240
+ const labA = this.toLabValue();
241
+ const labB = color.toLabValue();
242
+ const deltaL = labA[0] - labB[0];
243
+ const deltaA = labA[1] - labB[1];
244
+ const deltaB = labA[2] - labB[2];
245
+ const c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
246
+ const c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
247
+ const deltaC = c1 - c2;
248
+ let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
249
+ deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
250
+ const sc = 1.0 + 0.045 * c1;
251
+ const sh = 1.0 + 0.015 * c1;
252
+ const deltaLKlsl = deltaL / 1.0;
253
+ const deltaCkcsc = deltaC / sc;
254
+ const deltaHkhsh = deltaH / sh;
255
+ const i =
256
+ deltaLKlsl * deltaLKlsl +
257
+ deltaCkcsc * deltaCkcsc +
258
+ deltaHkhsh * deltaHkhsh;
259
+ return i < 0 ? 0 : Math.sqrt(i);
260
+ }
182
261
 
183
- return `RGB(${this.r}, ${this.g}, ${this.b})`;
262
+ /**
263
+ * Get luminance
264
+ * Darker one has higher luminance
265
+ * https://dev.to/alvaromontoro/building-your-own-color-contrast-checker-4j7o
266
+ */
267
+ getLuminance() {
268
+ const a = [this.r, this.g, this.b].map((v) => {
269
+ v /= 255;
270
+ return v <= 0.03928
271
+ ? v / 12.92
272
+ : Math.pow((v + 0.055) / 1.055, 2.4);
273
+ });
274
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
184
275
  }
185
276
 
186
277
  /**
@@ -192,4 +283,42 @@ export class EColor {
192
283
  this.b
193
284
  )}`;
194
285
  }
286
+
287
+ /**
288
+ * To Lab value
289
+ * @returns Lab value
290
+ */
291
+ toLabValue(): [number, number, number] {
292
+ let r = this.r / 255,
293
+ g = this.g / 255,
294
+ b = this.b / 255,
295
+ x,
296
+ y,
297
+ z;
298
+ r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
299
+ g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
300
+ b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
301
+ x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
302
+ y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.0;
303
+ z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
304
+ x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;
305
+ y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;
306
+ z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;
307
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
308
+ }
309
+
310
+ /**
311
+ * To RGB color string
312
+ * @param includeAlpha Include alpha or not
313
+ * @returns RGB color string
314
+ */
315
+ toRGBColor(includeAlpha?: boolean) {
316
+ // Default case
317
+ includeAlpha ??= this.alpha != null;
318
+
319
+ if (includeAlpha)
320
+ return `RGBA(${this.r}, ${this.g}, ${this.b}, ${this.alpha ?? 1})`;
321
+
322
+ return `RGB(${this.r}, ${this.g}, ${this.b})`;
323
+ }
195
324
  }