@js-draw/math 1.0.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -46,6 +46,25 @@ export default class Color4 {
46
46
  * ```
47
47
  */
48
48
  mix(other: Color4, fractionTo: number): Color4;
49
+ /**
50
+ * Ignoring this color's alpha component, returns a vector with components,
51
+ * $$
52
+ * \begin{pmatrix} \colorbox{#F44}{\tt r} \\ \colorbox{#4F4}{\tt g} \\ \colorbox{#44F}{\tt b} \end{pmatrix}
53
+ * $$
54
+ */
55
+ get rgb(): Vec3;
56
+ /**
57
+ * Returns the [relative luminance](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef)
58
+ * of this color in the sRGB color space.
59
+ *
60
+ * Ignores the alpha component.
61
+ */
62
+ relativeLuminance(): number;
63
+ /**
64
+ * Returns the [contrast ratio](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef)
65
+ * between `colorA` and `colorB`.
66
+ */
67
+ static contrastRatio(colorA: Color4, colorB: Color4): number;
49
68
  /**
50
69
  * @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
51
70
  */
@@ -57,6 +76,27 @@ export default class Color4 {
57
76
  * The resultant hue is represented in radians and is thus in $[0, 2\pi]$.
58
77
  */
59
78
  asHSV(): Vec3;
79
+ /**
80
+ * Creates a new `Color4` from a representation [in $HSV$](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
81
+ *
82
+ * [Algorithm](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
83
+ *
84
+ * Note that hue must be given **in radians**. While non-standard, this is consistent with
85
+ * {@link asHSV}.
86
+ *
87
+ * `hue` and `value` should range from 0 to 1.
88
+ *
89
+ * @param hue $H \in [0, 2\pi]$
90
+ * @param saturation $S_V \in [0, 1]$
91
+ * @param value $V \in [0, 1]$
92
+ */
93
+ static fromHSV(hue: number, saturation: number, value: number): Color4;
94
+ /**
95
+ * Equivalent to `ofRGB(rgb.x, rgb.y, rgb.z)`.
96
+ *
97
+ * All components should be in the range `[0, 1]` (0 to 1 inclusive).
98
+ */
99
+ static fromRGBVector(rgb: Vec3, alpha?: number): Color4;
60
100
  private hexString;
61
101
  /**
62
102
  * @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
@@ -144,6 +144,49 @@ class Color4 {
144
144
  const fractionOfThis = 1 - fractionTo;
145
145
  return new Color4(this.r * fractionOfThis + other.r * fractionTo, this.g * fractionOfThis + other.g * fractionTo, this.b * fractionOfThis + other.b * fractionTo, this.a * fractionOfThis + other.a * fractionTo);
146
146
  }
147
+ /**
148
+ * Ignoring this color's alpha component, returns a vector with components,
149
+ * $$
150
+ * \begin{pmatrix} \colorbox{#F44}{\tt r} \\ \colorbox{#4F4}{\tt g} \\ \colorbox{#44F}{\tt b} \end{pmatrix}
151
+ * $$
152
+ */
153
+ get rgb() {
154
+ return Vec3_1.default.of(this.r, this.g, this.b);
155
+ }
156
+ /**
157
+ * Returns the [relative luminance](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef)
158
+ * of this color in the sRGB color space.
159
+ *
160
+ * Ignores the alpha component.
161
+ */
162
+ relativeLuminance() {
163
+ // References:
164
+ // - https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
165
+ // - https://stackoverflow.com/a/9733420
166
+ // Normalize the components, as per above
167
+ const components = [this.r, this.g, this.b].map(component => {
168
+ if (component < 0.03928) {
169
+ return component / 12.92;
170
+ }
171
+ else {
172
+ return Math.pow((component + 0.055) / 1.055, 2.4);
173
+ }
174
+ });
175
+ // From w3.org,
176
+ // > For the sRGB colorspace, the relative luminance of a color is
177
+ // > defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B
178
+ // where R, G, B are defined in components above.
179
+ return 0.2126 * components[0] + 0.7152 * components[1] + 0.0722 * components[2];
180
+ }
181
+ /**
182
+ * Returns the [contrast ratio](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef)
183
+ * between `colorA` and `colorB`.
184
+ */
185
+ static contrastRatio(colorA, colorB) {
186
+ const L1 = colorA.relativeLuminance();
187
+ const L2 = colorB.relativeLuminance();
188
+ return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
189
+ }
147
190
  /**
148
191
  * @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
149
192
  */
@@ -229,6 +272,65 @@ class Color4 {
229
272
  const saturation = value > 0 ? chroma / value : 0;
230
273
  return Vec3_1.default.of(hue, saturation, value);
231
274
  }
275
+ /**
276
+ * Creates a new `Color4` from a representation [in $HSV$](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
277
+ *
278
+ * [Algorithm](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
279
+ *
280
+ * Note that hue must be given **in radians**. While non-standard, this is consistent with
281
+ * {@link asHSV}.
282
+ *
283
+ * `hue` and `value` should range from 0 to 1.
284
+ *
285
+ * @param hue $H \in [0, 2\pi]$
286
+ * @param saturation $S_V \in [0, 1]$
287
+ * @param value $V \in [0, 1]$
288
+ */
289
+ static fromHSV(hue, saturation, value) {
290
+ if (hue < 0) {
291
+ hue += Math.PI * 2;
292
+ }
293
+ hue %= Math.PI * 2;
294
+ // Clamp value and saturation to [0, 1]
295
+ value = Math.max(0, Math.min(1, value));
296
+ saturation = Math.max(0, Math.min(1, saturation));
297
+ // Formula from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
298
+ // Saturation can be thought of as scaled chroma. Unapply the scaling.
299
+ // See https://en.wikipedia.org/wiki/HSL_and_HSV#Saturation
300
+ const chroma = value * saturation;
301
+ // Determines which edge of the projected color cube
302
+ const huePrime = hue / (Math.PI / 3);
303
+ const secondLargestComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));
304
+ let rgb;
305
+ if (huePrime < 1) {
306
+ rgb = [chroma, secondLargestComponent, 0];
307
+ }
308
+ else if (huePrime < 2) {
309
+ rgb = [secondLargestComponent, chroma, 0];
310
+ }
311
+ else if (huePrime < 3) {
312
+ rgb = [0, chroma, secondLargestComponent];
313
+ }
314
+ else if (huePrime < 4) {
315
+ rgb = [0, secondLargestComponent, chroma];
316
+ }
317
+ else if (huePrime < 5) {
318
+ rgb = [secondLargestComponent, 0, chroma];
319
+ }
320
+ else {
321
+ rgb = [chroma, 0, secondLargestComponent];
322
+ }
323
+ const adjustment = value - chroma;
324
+ return Color4.ofRGB(rgb[0] + adjustment, rgb[1] + adjustment, rgb[2] + adjustment);
325
+ }
326
+ /**
327
+ * Equivalent to `ofRGB(rgb.x, rgb.y, rgb.z)`.
328
+ *
329
+ * All components should be in the range `[0, 1]` (0 to 1 inclusive).
330
+ */
331
+ static fromRGBVector(rgb, alpha) {
332
+ return Color4.ofRGBA(rgb.x, rgb.y, rgb.z, alpha ?? 1);
333
+ }
232
334
  /**
233
335
  * @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
234
336
  *
@@ -46,6 +46,25 @@ export default class Color4 {
46
46
  * ```
47
47
  */
48
48
  mix(other: Color4, fractionTo: number): Color4;
49
+ /**
50
+ * Ignoring this color's alpha component, returns a vector with components,
51
+ * $$
52
+ * \begin{pmatrix} \colorbox{#F44}{\tt r} \\ \colorbox{#4F4}{\tt g} \\ \colorbox{#44F}{\tt b} \end{pmatrix}
53
+ * $$
54
+ */
55
+ get rgb(): Vec3;
56
+ /**
57
+ * Returns the [relative luminance](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef)
58
+ * of this color in the sRGB color space.
59
+ *
60
+ * Ignores the alpha component.
61
+ */
62
+ relativeLuminance(): number;
63
+ /**
64
+ * Returns the [contrast ratio](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef)
65
+ * between `colorA` and `colorB`.
66
+ */
67
+ static contrastRatio(colorA: Color4, colorB: Color4): number;
49
68
  /**
50
69
  * @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
51
70
  */
@@ -57,6 +76,27 @@ export default class Color4 {
57
76
  * The resultant hue is represented in radians and is thus in $[0, 2\pi]$.
58
77
  */
59
78
  asHSV(): Vec3;
79
+ /**
80
+ * Creates a new `Color4` from a representation [in $HSV$](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
81
+ *
82
+ * [Algorithm](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
83
+ *
84
+ * Note that hue must be given **in radians**. While non-standard, this is consistent with
85
+ * {@link asHSV}.
86
+ *
87
+ * `hue` and `value` should range from 0 to 1.
88
+ *
89
+ * @param hue $H \in [0, 2\pi]$
90
+ * @param saturation $S_V \in [0, 1]$
91
+ * @param value $V \in [0, 1]$
92
+ */
93
+ static fromHSV(hue: number, saturation: number, value: number): Color4;
94
+ /**
95
+ * Equivalent to `ofRGB(rgb.x, rgb.y, rgb.z)`.
96
+ *
97
+ * All components should be in the range `[0, 1]` (0 to 1 inclusive).
98
+ */
99
+ static fromRGBVector(rgb: Vec3, alpha?: number): Color4;
60
100
  private hexString;
61
101
  /**
62
102
  * @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
@@ -138,6 +138,49 @@ class Color4 {
138
138
  const fractionOfThis = 1 - fractionTo;
139
139
  return new Color4(this.r * fractionOfThis + other.r * fractionTo, this.g * fractionOfThis + other.g * fractionTo, this.b * fractionOfThis + other.b * fractionTo, this.a * fractionOfThis + other.a * fractionTo);
140
140
  }
141
+ /**
142
+ * Ignoring this color's alpha component, returns a vector with components,
143
+ * $$
144
+ * \begin{pmatrix} \colorbox{#F44}{\tt r} \\ \colorbox{#4F4}{\tt g} \\ \colorbox{#44F}{\tt b} \end{pmatrix}
145
+ * $$
146
+ */
147
+ get rgb() {
148
+ return Vec3.of(this.r, this.g, this.b);
149
+ }
150
+ /**
151
+ * Returns the [relative luminance](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef)
152
+ * of this color in the sRGB color space.
153
+ *
154
+ * Ignores the alpha component.
155
+ */
156
+ relativeLuminance() {
157
+ // References:
158
+ // - https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
159
+ // - https://stackoverflow.com/a/9733420
160
+ // Normalize the components, as per above
161
+ const components = [this.r, this.g, this.b].map(component => {
162
+ if (component < 0.03928) {
163
+ return component / 12.92;
164
+ }
165
+ else {
166
+ return Math.pow((component + 0.055) / 1.055, 2.4);
167
+ }
168
+ });
169
+ // From w3.org,
170
+ // > For the sRGB colorspace, the relative luminance of a color is
171
+ // > defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B
172
+ // where R, G, B are defined in components above.
173
+ return 0.2126 * components[0] + 0.7152 * components[1] + 0.0722 * components[2];
174
+ }
175
+ /**
176
+ * Returns the [contrast ratio](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef)
177
+ * between `colorA` and `colorB`.
178
+ */
179
+ static contrastRatio(colorA, colorB) {
180
+ const L1 = colorA.relativeLuminance();
181
+ const L2 = colorB.relativeLuminance();
182
+ return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
183
+ }
141
184
  /**
142
185
  * @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
143
186
  */
@@ -223,6 +266,65 @@ class Color4 {
223
266
  const saturation = value > 0 ? chroma / value : 0;
224
267
  return Vec3.of(hue, saturation, value);
225
268
  }
269
+ /**
270
+ * Creates a new `Color4` from a representation [in $HSV$](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
271
+ *
272
+ * [Algorithm](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
273
+ *
274
+ * Note that hue must be given **in radians**. While non-standard, this is consistent with
275
+ * {@link asHSV}.
276
+ *
277
+ * `hue` and `value` should range from 0 to 1.
278
+ *
279
+ * @param hue $H \in [0, 2\pi]$
280
+ * @param saturation $S_V \in [0, 1]$
281
+ * @param value $V \in [0, 1]$
282
+ */
283
+ static fromHSV(hue, saturation, value) {
284
+ if (hue < 0) {
285
+ hue += Math.PI * 2;
286
+ }
287
+ hue %= Math.PI * 2;
288
+ // Clamp value and saturation to [0, 1]
289
+ value = Math.max(0, Math.min(1, value));
290
+ saturation = Math.max(0, Math.min(1, saturation));
291
+ // Formula from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
292
+ // Saturation can be thought of as scaled chroma. Unapply the scaling.
293
+ // See https://en.wikipedia.org/wiki/HSL_and_HSV#Saturation
294
+ const chroma = value * saturation;
295
+ // Determines which edge of the projected color cube
296
+ const huePrime = hue / (Math.PI / 3);
297
+ const secondLargestComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));
298
+ let rgb;
299
+ if (huePrime < 1) {
300
+ rgb = [chroma, secondLargestComponent, 0];
301
+ }
302
+ else if (huePrime < 2) {
303
+ rgb = [secondLargestComponent, chroma, 0];
304
+ }
305
+ else if (huePrime < 3) {
306
+ rgb = [0, chroma, secondLargestComponent];
307
+ }
308
+ else if (huePrime < 4) {
309
+ rgb = [0, secondLargestComponent, chroma];
310
+ }
311
+ else if (huePrime < 5) {
312
+ rgb = [secondLargestComponent, 0, chroma];
313
+ }
314
+ else {
315
+ rgb = [chroma, 0, secondLargestComponent];
316
+ }
317
+ const adjustment = value - chroma;
318
+ return Color4.ofRGB(rgb[0] + adjustment, rgb[1] + adjustment, rgb[2] + adjustment);
319
+ }
320
+ /**
321
+ * Equivalent to `ofRGB(rgb.x, rgb.y, rgb.z)`.
322
+ *
323
+ * All components should be in the range `[0, 1]` (0 to 1 inclusive).
324
+ */
325
+ static fromRGBVector(rgb, alpha) {
326
+ return Color4.ofRGBA(rgb.x, rgb.y, rgb.z, alpha ?? 1);
327
+ }
226
328
  /**
227
329
  * @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
228
330
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-draw/math",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "A math library for js-draw. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -45,5 +45,5 @@
45
45
  "svg",
46
46
  "math"
47
47
  ],
48
- "gitHead": "f5a92284625b49b2ef6541bdafce1bd926c10441"
48
+ "gitHead": "3e77c7d833ecdc13bcb57e905280ba547629680a"
49
49
  }
@@ -49,4 +49,46 @@ describe('Color4', () => {
49
49
  expect(Color4.ofRGB(0.5, 0.5, 0.5).asHSV()).objEq(Vec3.of(0, 0, 0.5));
50
50
  expect(Color4.ofRGB(0.5, 0.25, 0.5).asHSV()).objEq(Vec3.of(Math.PI * 5 / 3, 0.5, 0.5), 0.1);
51
51
  });
52
+
53
+ it('fromHSV(color.asHSV) should return the original color', () => {
54
+ const testColors = [
55
+ Color4.red, Color4.green, Color4.blue,
56
+ Color4.white, Color4.black,
57
+ ];
58
+
59
+ const testWithColor = (color: Color4) => {
60
+ expect(Color4.fromHSV(...color.asHSV().asArray())).objEq(color);
61
+ };
62
+
63
+ for (const color of testColors) {
64
+ testWithColor(color);
65
+ }
66
+
67
+ for (let i = 0; i <= 6; i++) {
68
+ testWithColor(Color4.fromHSV(i * Math.PI / 7, 0.5, 0.5));
69
+ testWithColor(Color4.fromHSV(i * Math.PI / 6, 0.5, 0.5));
70
+ }
71
+ });
72
+
73
+ it('.rgb should return a 3-component vector', () => {
74
+ expect(Color4.red.rgb).objEq(Vec3.of(1, 0, 0));
75
+ expect(Color4.green.rgb).objEq(Vec3.of(0, 1, 0));
76
+ expect(Color4.blue.rgb).objEq(Vec3.of(0, 0, 1));
77
+ });
78
+
79
+ it('should return correct contrast ratios', () => {
80
+ // Expected values from https://webaim.org/resources/contrastchecker/
81
+ const testCases: [ Color4, Color4, number ][] = [
82
+ [ Color4.white, Color4.black, 21 ],
83
+ [ Color4.fromHex('#FF0000'), Color4.black, 5.25 ],
84
+ [ Color4.fromHex('#FF0000'), Color4.fromHex('#0000FF'), 2.14 ],
85
+ [ Color4.fromHex('#300000'), Color4.fromHex('#003000'), 1.26 ],
86
+ [ Color4.fromHex('#300000'), Color4.fromHex('#003000'), 1.26 ],
87
+ [ Color4.fromHex('#D60000'), Color4.fromHex('#003000'), 2.71 ],
88
+ ];
89
+
90
+ for (const [ colorA, colorB, expectedContrast ] of testCases) {
91
+ expect(Color4.contrastRatio(colorA, colorB)).toBeCloseTo(expectedContrast, 1);
92
+ }
93
+ });
52
94
  });
package/src/Color4.ts CHANGED
@@ -170,6 +170,54 @@ export default class Color4 {
170
170
  );
171
171
  }
172
172
 
173
+ /**
174
+ * Ignoring this color's alpha component, returns a vector with components,
175
+ * $$
176
+ * \begin{pmatrix} \colorbox{#F44}{\tt r} \\ \colorbox{#4F4}{\tt g} \\ \colorbox{#44F}{\tt b} \end{pmatrix}
177
+ * $$
178
+ */
179
+ public get rgb() {
180
+ return Vec3.of(this.r, this.g, this.b);
181
+ }
182
+
183
+ /**
184
+ * Returns the [relative luminance](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef)
185
+ * of this color in the sRGB color space.
186
+ *
187
+ * Ignores the alpha component.
188
+ */
189
+ public relativeLuminance(): number {
190
+ // References:
191
+ // - https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
192
+ // - https://stackoverflow.com/a/9733420
193
+
194
+ // Normalize the components, as per above
195
+ const components = [ this.r, this.g, this.b ].map(component => {
196
+ if (component < 0.03928) {
197
+ return component / 12.92;
198
+ } else {
199
+ return Math.pow((component + 0.055) / 1.055, 2.4);
200
+ }
201
+ });
202
+
203
+ // From w3.org,
204
+ // > For the sRGB colorspace, the relative luminance of a color is
205
+ // > defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B
206
+ // where R, G, B are defined in components above.
207
+ return 0.2126 * components[0] + 0.7152 * components[1] + 0.0722 * components[2];
208
+ }
209
+
210
+ /**
211
+ * Returns the [contrast ratio](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef)
212
+ * between `colorA` and `colorB`.
213
+ */
214
+ public static contrastRatio(colorA: Color4, colorB: Color4): number {
215
+ const L1 = colorA.relativeLuminance();
216
+ const L2 = colorB.relativeLuminance();
217
+
218
+ return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
219
+ }
220
+
173
221
  /**
174
222
  * @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
175
223
  */
@@ -263,6 +311,70 @@ export default class Color4 {
263
311
  return Vec3.of(hue, saturation, value);
264
312
  }
265
313
 
314
+ /**
315
+ * Creates a new `Color4` from a representation [in $HSV$](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
316
+ *
317
+ * [Algorithm](https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB).
318
+ *
319
+ * Note that hue must be given **in radians**. While non-standard, this is consistent with
320
+ * {@link asHSV}.
321
+ *
322
+ * `hue` and `value` should range from 0 to 1.
323
+ *
324
+ * @param hue $H \in [0, 2\pi]$
325
+ * @param saturation $S_V \in [0, 1]$
326
+ * @param value $V \in [0, 1]$
327
+ */
328
+ public static fromHSV(hue: number, saturation: number, value: number) {
329
+ if (hue < 0) {
330
+ hue += Math.PI * 2;
331
+ }
332
+ hue %= Math.PI * 2;
333
+
334
+ // Clamp value and saturation to [0, 1]
335
+ value = Math.max(0, Math.min(1, value));
336
+ saturation = Math.max(0, Math.min(1, saturation));
337
+
338
+ // Formula from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
339
+
340
+ // Saturation can be thought of as scaled chroma. Unapply the scaling.
341
+ // See https://en.wikipedia.org/wiki/HSL_and_HSV#Saturation
342
+ const chroma = value * saturation;
343
+
344
+ // Determines which edge of the projected color cube
345
+ const huePrime = hue / (Math.PI / 3);
346
+
347
+ const secondLargestComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));
348
+
349
+ let rgb;
350
+ if (huePrime < 1) {
351
+ rgb = [ chroma, secondLargestComponent, 0 ];
352
+ } else if (huePrime < 2) {
353
+ rgb = [ secondLargestComponent, chroma, 0 ];
354
+ } else if (huePrime < 3) {
355
+ rgb = [ 0, chroma, secondLargestComponent ];
356
+ } else if (huePrime < 4) {
357
+ rgb = [ 0, secondLargestComponent, chroma ];
358
+ } else if (huePrime < 5) {
359
+ rgb = [ secondLargestComponent, 0, chroma ];
360
+ } else {
361
+ rgb = [ chroma, 0, secondLargestComponent ];
362
+ }
363
+
364
+ const adjustment = value - chroma;
365
+ return Color4.ofRGB(rgb[0] + adjustment, rgb[1] + adjustment, rgb[2] + adjustment);
366
+ }
367
+
368
+
369
+ /**
370
+ * Equivalent to `ofRGB(rgb.x, rgb.y, rgb.z)`.
371
+ *
372
+ * All components should be in the range `[0, 1]` (0 to 1 inclusive).
373
+ */
374
+ public static fromRGBVector(rgb: Vec3, alpha?: number) {
375
+ return Color4.ofRGBA(rgb.x, rgb.y, rgb.z, alpha ?? 1);
376
+ }
377
+
266
378
  private hexString: string|null = null;
267
379
 
268
380
  /**