@js-draw/math 1.0.2 → 1.2.0

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.
@@ -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
  /**