Skip to content

Commit 8643165

Browse files
authored
feat(convert): Added Hue XY Conversions (#155)
* feat: Added Hue XY Conversions * feat: Added Docs * fix: Changed sRGB Logic * fix: Added XY to Circle colors test
1 parent 5babba2 commit 8643165

4 files changed

Lines changed: 150 additions & 29 deletions

File tree

packages/color-convert/README.md

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,30 @@ validHex("#8c0dba") //=> true
202202
rgbaToHexa({ b: 26, g: 2, r: 209, a: 1 }) // => '#d1021aff'
203203
```
204204

205+
### `hexToXY`
206+
207+
```js
208+
hexToXY('#4780f1') // => { x: 0.261, y: 0.231, bri: 0.863 }
209+
```
210+
211+
### `xyToHex`
212+
213+
```js
214+
xyToHex({ x: 0.261, y: 0.231, bri: 0.863 }) // => #4780f1
215+
```
216+
217+
### `rgbToXY`
218+
219+
```js
220+
rgbToXY({ r: 71, g: 128, b: 241 }) // => { x: 0.261, y: 0.231, bri: 0.863 }
221+
```
222+
223+
### `xyToRgb`
224+
225+
```js
226+
xyToRgb({ x: 0.261, y: 0.231, bri: 0.863 }) // => { r: 71, g: 128, b: 241 }
227+
```
228+
205229
#### `color`
206230

207231
```js
@@ -221,37 +245,32 @@ const { rgb, rgba, hsl, hsv, hsla, hsva } = color('#d1021a');
221245
## type
222246

223247
```ts
224-
export declare const equalColorObjects: (first: ObjectColor, second: ObjectColor) => boolean;
225-
export declare const equalColorString: (first: string, second: string) => boolean;
226-
export declare const equalHex: (first: string, second: string) => boolean;
227-
export declare const validHex: (hex: string) => boolean;
228-
export declare const getContrastingColor: (str: string | HsvaColor) => "#ffffff" | "#000000";
229248
export type ObjectColor = RgbColor | HslColor | HsvColor | RgbaColor | HslaColor | HsvaColor;
230249
export type ColorResult = {
231-
rgb: RgbColor;
232-
hsl: HslColor;
233-
hsv: HsvColor;
234-
rgba: RgbaColor;
235-
hsla: HslaColor;
236-
hsva: HsvaColor;
237-
hex: string;
238-
hexa: string;
250+
rgb: RgbColor;
251+
hsl: HslColor;
252+
hsv: HsvColor;
253+
rgba: RgbaColor;
254+
hsla: HslaColor;
255+
hsva: HsvaColor;
256+
hex: string;
257+
hexa: string;
239258
};
240259
export interface HsvColor {
241-
h: number;
242-
s: number;
243-
v: number;
260+
h: number;
261+
s: number;
262+
v: number;
244263
}
245264
export interface HsvaColor extends HsvColor {
246-
a: number;
265+
a: number;
247266
}
248267
export interface RgbColor {
249-
r: number;
250-
g: number;
251-
b: number;
268+
r: number;
269+
g: number;
270+
b: number;
252271
}
253272
export interface RgbaColor extends RgbColor {
254-
a: number;
273+
a: number;
255274
}
256275
/**
257276
* ```js
@@ -268,12 +287,12 @@ export declare const hslaStringToHsva: (hslString: string) => HsvaColor;
268287
export declare const hslStringToHsva: (hslString: string) => HsvaColor;
269288
export declare const hslaToHsva: ({ h, s, l, a }: HslaColor) => HsvaColor;
270289
export interface HslColor {
271-
h: number;
272-
s: number;
273-
l: number;
290+
h: number;
291+
s: number;
292+
l: number;
274293
}
275294
export interface HslaColor extends HslColor {
276-
a: number;
295+
a: number;
277296
}
278297
export declare const hsvaToHsla: ({ h, s, v, a }: HsvaColor) => HslaColor;
279298
export declare const hsvaStringToHsva: (hsvString: string) => HsvaColor;
@@ -284,6 +303,7 @@ export declare const rgbStringToHsva: (rgbaString: string) => HsvaColor;
284303
/** Converts an RGBA color plus alpha transparency to hex */
285304
export declare const rgbaToHex: ({ r, g, b }: RgbaColor) => string;
286305
export declare const rgbaToHexa: ({ r, g, b, a }: RgbaColor) => string;
306+
export type HexColor = `#${string}`;
287307
export declare const hexToHsva: (hex: string) => HsvaColor;
288308
export declare const hexToRgba: (hex: string) => RgbaColor;
289309
/**
@@ -299,6 +319,11 @@ export declare const hsvaToHex: (hsva: HsvaColor) => string;
299319
export declare const hsvaToHexa: (hsva: HsvaColor) => string;
300320
export declare const hsvaToHsv: ({ h, s, v }: HsvaColor) => HsvColor;
301321
export declare const color: (str: string | HsvaColor) => ColorResult;
322+
export declare const getContrastingColor: (str: string | HsvaColor) => "#ffffff" | "#000000";
323+
export declare const equalColorObjects: (first: ObjectColor, second: ObjectColor) => boolean;
324+
export declare const equalColorString: (first: string, second: string) => boolean;
325+
export declare const equalHex: (first: string, second: string) => boolean;
326+
export declare const validHex: (hex: string) => hex is HexColor;
302327
```
303328

304329
## Contributors

packages/color-convert/src/index.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type ColorResult = {
1010
rgba: RgbaColor;
1111
hsla: HslaColor;
1212
hsva: HsvaColor;
13+
xy: XYColor;
1314
hex: string;
1415
hexa: string;
1516
};
@@ -33,6 +34,12 @@ export interface RgbaColor extends RgbColor {
3334
a: number;
3435
}
3536

37+
export interface XYColor {
38+
x: number;
39+
y: number;
40+
bri?: number;
41+
}
42+
3643
/**
3744
* ```js
3845
* rgbaToHsva({ r: 255, g: 255, b: 255, a: 1 }) //=> { h: 0, s: 0, v: 100, a: 1 }
@@ -273,6 +280,53 @@ export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
273280
export const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva));
274281
export const hsvaToHexa = (hsva: HsvaColor): string => rgbaToHexa(hsvaToRgba(hsva));
275282
export const hsvaToHsv = ({ h, s, v }: HsvaColor): HsvColor => ({ h, s, v });
283+
export const hexToXY = (hex: string): XYColor => rgbToXY(rgbaToRgb(hexToRgba(hex)));
284+
export const xyToHex = (xy: XYColor): string =>
285+
rgbaToHex({
286+
...xyToRgb(xy),
287+
a: 255,
288+
});
289+
290+
/**
291+
* Converts XY to RGB. Based on formula from https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
292+
* @param color XY color and brightness as an array [0-1, 0-1, 0-1]
293+
*/
294+
export const xyToRgb = ({ x, y, bri = 255 }: XYColor): RgbColor => {
295+
const red = x * 3.2406255 + y * -1.537208 + bri * -0.4986286;
296+
const green = x * -0.9689307 + y * 1.8757561 + bri * 0.0415175;
297+
const blue = x * 0.0557101 + y * -0.2040211 + bri * 1.0569959;
298+
299+
const translate = function (color: number) {
300+
return color <= 0.0031308 ? 12.92 * color : 1.055 * Math.pow(color, 1 / 2.4) - 0.055;
301+
};
302+
303+
return {
304+
r: Math.round(255 * translate(red)),
305+
g: Math.round(255 * translate(green)),
306+
b: Math.round(255 * translate(blue)),
307+
};
308+
};
309+
310+
/**
311+
* Converts RGB to XY. Based on formula from https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
312+
* @param color RGB color as an array [0-255, 0-255, 0-255]
313+
*/
314+
export const rgbToXY = ({ r, g, b }: RgbColor): XYColor => {
315+
const translateColor = function (color: number) {
316+
return color <= 0.04045 ? color / 12.92 : Math.pow((color + 0.055) / 1.055, 2.4);
317+
};
318+
319+
const red = translateColor(r / 255);
320+
const green = translateColor(g / 255);
321+
const blud = translateColor(b / 255);
322+
323+
const xyz = {} as XYColor;
324+
xyz.x = red * 0.4124 + green * 0.3576 + blud * 0.1805;
325+
xyz.y = red * 0.2126 + green * 0.7152 + blud * 0.0722;
326+
xyz.bri = red * 0.0193 + green * 0.1192 + blud * 0.9505;
327+
328+
return xyz;
329+
};
276330

277331
export const color = (str: string | HsvaColor): ColorResult => {
278332
let rgb!: RgbColor;
@@ -281,6 +335,7 @@ export const color = (str: string | HsvaColor): ColorResult => {
281335
let rgba!: RgbaColor;
282336
let hsla!: HslaColor;
283337
let hsva!: HsvaColor;
338+
let xy!: XYColor;
284339
let hex!: string;
285340
let hexa!: string;
286341
if (typeof str === 'string' && validHex(str)) {
@@ -297,8 +352,9 @@ export const color = (str: string | HsvaColor): ColorResult => {
297352
hex = hsvaToHex(hsva);
298353
hsl = hslaToHsl(hsla);
299354
rgb = rgbaToRgb(rgba);
355+
xy = rgbToXY(rgb);
300356
}
301-
return { rgb, hsl, hsv, rgba, hsla, hsva, hex, hexa };
357+
return { rgb, hsl, hsv, rgba, hsla, hsva, hex, hexa, xy };
302358
};
303359

304360
export const getContrastingColor = (str: string | HsvaColor) => {

test/circle.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ it('Circle colors checked', async () => {
6868
v: 95.68627450980392,
6969
a: 1,
7070
},
71+
xy: {
72+
bri: 0.06811140344707436,
73+
x: 0.40822033351750947,
74+
y: 0.24997641962334327,
75+
},
7176
hex: '#f44e3b',
7277
hexa: '#f44e3bff',
7378
});

test/convert.test.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { color, getContrastingColor } from '../packages/color-convert/src';
22
// HEX
3-
import { hexToHsva, hexToRgba, hsvaToHex, hsvaToHexa } from '../packages/color-convert/src';
3+
import { hexToHsva, hexToRgba, hsvaToHex, hsvaToHexa, hexToXY } from '../packages/color-convert/src';
44
import { equalHex } from '../packages/color-convert/src';
55
// HSLA
66
import { hsvaToHsla, hslaToHsva, HsvaColor, HslaColor } from '../packages/color-convert/src';
@@ -15,7 +15,7 @@ import { hsvaToRgba, rgbaToHsva, RgbaColor } from '../packages/color-convert/src
1515
// RGBA string
1616
import { hsvaToRgbaString, rgbaStringToHsva } from '../packages/color-convert/src';
1717
// RGB
18-
import { rgbaToRgb, rgbaToHex, rgbaToHexa } from '../packages/color-convert/src';
18+
import { rgbaToRgb, rgbaToHex, rgbaToHexa, rgbToXY } from '../packages/color-convert/src';
1919
// RGB string
2020
import { hsvaToRgbString, rgbStringToHsva } from '../packages/color-convert/src';
2121
// HSVA String
@@ -25,6 +25,8 @@ import { hsvaToHsv } from '../packages/color-convert/src';
2525
// HSV string
2626
import { hsvaToHsvString, hsvStringToHsva } from '../packages/color-convert/src';
2727
import { equalColorString, equalColorObjects, validHex } from '../packages/color-convert/src';
28+
// XY
29+
import { xyToHex, xyToRgb } from '../packages/color-convert/src';
2830

2931
it('Converts color => getContrastingColor', () => {
3032
expect(getContrastingColor('#d0021b')).toEqual('#ffffff');
@@ -41,7 +43,7 @@ it('Converts color => hslString To Hsl', () => {
4143
});
4244

4345
it('Converts color => HEX to ColorResult', () => {
44-
const { rgb, rgba, hex, hexa, hsl, hsla, hsv, hsva } = color('#d1021a');
46+
const { rgb, rgba, hex, hexa, hsl, hsla, hsv, hsva, xy } = color('#d1021a');
4547
expect(hex).toEqual('#d1021a');
4648
expect(hexa).toEqual('#d1021aff');
4749
expect(color({ h: 353.04347826086956, s: 99.04306220095694, v: 81.96078431372548, a: 1 }).hex).toEqual('#d1021a');
@@ -52,6 +54,7 @@ it('Converts color => HEX to ColorResult', () => {
5254
expect(hsla).toEqual({ h: 353.04347826086956, l: 41.37254901960784, s: 98.10426540284361, a: 1 });
5355
expect(hsv).toEqual({ h: 353.04347826086956, s: 99.04306220095694, v: 81.96078431372548 });
5456
expect(hsva).toEqual({ h: 353.04347826086956, s: 99.04306220095694, v: 81.96078431372548, a: 1 });
57+
expect(xy).toEqual({ x: 0.26502656639062083, y: 0.13673307363113865, bri: 0.022196477290623278 });
5558
});
5659

5760
it('Converts color => HEXA to ColorResult', () => {
@@ -95,6 +98,16 @@ it('Converts RGBA to HEXA', () => {
9598
expect(rgbaToHexa({ b: 26, g: 2, r: 209 } as any)).toEqual('#d1021a');
9699
});
97100

101+
it('Converts RGB to XY', () => {
102+
expect(rgbToXY({ r: 255, g: 255, b: 255 })).toMatchObject({ x: 0.9505, y: 1, bri: 1.089 });
103+
expect(rgbToXY({ r: 0, g: 0, b: 0 })).toMatchObject({ x: 0.0, y: 0.0, bri: 0 });
104+
expect(rgbToXY({ r: 71, g: 128, b: 241 })).toMatchObject({
105+
x: 0.26194888875915034,
106+
y: 0.23128809648982562,
107+
bri: 0.863027753196167,
108+
});
109+
});
110+
98111
it('Converts HEX to RGBA', () => {
99112
expect(hsvaToHslString(hexToHsva('#d0021b'))).toEqual('hsl(352.71844660194176, 98%, 41%)');
100113
expect(hsvaToHex(rgbaToHsva(hexToRgba('#d0021b')))).toEqual('#d0021b');
@@ -115,6 +128,12 @@ it('Converts HEX to HSVA', () => {
115128
expect(hexToHsva('#c62182')).toMatchObject({ h: 324.72727272727275, s: 83.33333333333334, v: 77.64705882352942, a: 1 });
116129
});
117130

131+
it('Converts HEX to XY', () => {
132+
expect(hexToXY('#ffffff')).toMatchObject({ x: 0.9505, y: 1, bri: 1.089 });
133+
expect(hexToXY('#000000')).toMatchObject({ x: 0.0, y: 0.0, bri: 0 });
134+
expect(hexToXY('#4780f1')).toMatchObject({ x: 0.26194888875915034, y: 0.23128809648982562, bri: 0.863027753196167 });
135+
});
136+
118137
it('Converts shorthand HEX to HSVA', () => {
119138
expect(hexToHsva('#FFF')).toMatchObject({ h: 0, s: 0, v: 100, a: 1 });
120139
expect(hexToHsva('#FF0')).toMatchObject({ h: 60, s: 100, v: 100, a: 1 });
@@ -409,3 +428,19 @@ it('Validates HEX colors', () => {
409428
// @ts-ignore
410429
expect(validHex()).toBe(false);
411430
});
431+
432+
it('Converts XY to RGB', () => {
433+
expect(xyToRgb({ x: 0.9505, y: 1, bri: 1.089 })).toMatchObject({ r: 255, g: 255, b: 255 });
434+
expect(xyToRgb({ x: 0.0, y: 0.0, bri: 0 })).toMatchObject({ r: 0, g: 0, b: 0 });
435+
expect(xyToRgb({ x: 0.26194888875915034, y: 0.23128809648982562, bri: 0.863027753196167 })).toMatchObject({
436+
r: 71,
437+
g: 128,
438+
b: 241,
439+
});
440+
});
441+
442+
it('Converts XY to HEX', () => {
443+
expect(xyToHex({ x: 0.9505, y: 1, bri: 1.089 })).toBe('#ffffff');
444+
expect(xyToHex({ x: 0.0, y: 0.0, bri: 0 })).toBe('#000000');
445+
expect(xyToHex({ x: 0.26194888875915034, y: 0.23128809648982562, bri: 0.863027753196167 })).toBe('#4780f1');
446+
});

0 commit comments

Comments
 (0)