Skip to content

Commit

Permalink
feat(convert): Added Hue XY Conversions (#155)
Browse files Browse the repository at this point in the history
* feat: Added Hue XY Conversions

* feat: Added Docs

* fix: Changed sRGB Logic

* fix: Added XY to Circle colors test
  • Loading branch information
Tynopia authored Sep 22, 2024
1 parent 5babba2 commit 8643165
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 29 deletions.
75 changes: 50 additions & 25 deletions packages/color-convert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,30 @@ validHex("#8c0dba") //=> true
rgbaToHexa({ b: 26, g: 2, r: 209, a: 1 }) // => '#d1021aff'
```

### `hexToXY`

```js
hexToXY('#4780f1') // => { x: 0.261, y: 0.231, bri: 0.863 }
```

### `xyToHex`

```js
xyToHex({ x: 0.261, y: 0.231, bri: 0.863 }) // => #4780f1
```

### `rgbToXY`

```js
rgbToXY({ r: 71, g: 128, b: 241 }) // => { x: 0.261, y: 0.231, bri: 0.863 }
```

### `xyToRgb`

```js
xyToRgb({ x: 0.261, y: 0.231, bri: 0.863 }) // => { r: 71, g: 128, b: 241 }
```

#### `color`

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

```ts
export declare const equalColorObjects: (first: ObjectColor, second: ObjectColor) => boolean;
export declare const equalColorString: (first: string, second: string) => boolean;
export declare const equalHex: (first: string, second: string) => boolean;
export declare const validHex: (hex: string) => boolean;
export declare const getContrastingColor: (str: string | HsvaColor) => "#ffffff" | "#000000";
export type ObjectColor = RgbColor | HslColor | HsvColor | RgbaColor | HslaColor | HsvaColor;
export type ColorResult = {
rgb: RgbColor;
hsl: HslColor;
hsv: HsvColor;
rgba: RgbaColor;
hsla: HslaColor;
hsva: HsvaColor;
hex: string;
hexa: string;
rgb: RgbColor;
hsl: HslColor;
hsv: HsvColor;
rgba: RgbaColor;
hsla: HslaColor;
hsva: HsvaColor;
hex: string;
hexa: string;
};
export interface HsvColor {
h: number;
s: number;
v: number;
h: number;
s: number;
v: number;
}
export interface HsvaColor extends HsvColor {
a: number;
a: number;
}
export interface RgbColor {
r: number;
g: number;
b: number;
r: number;
g: number;
b: number;
}
export interface RgbaColor extends RgbColor {
a: number;
a: number;
}
/**
* ```js
Expand All @@ -268,12 +287,12 @@ export declare const hslaStringToHsva: (hslString: string) => HsvaColor;
export declare const hslStringToHsva: (hslString: string) => HsvaColor;
export declare const hslaToHsva: ({ h, s, l, a }: HslaColor) => HsvaColor;
export interface HslColor {
h: number;
s: number;
l: number;
h: number;
s: number;
l: number;
}
export interface HslaColor extends HslColor {
a: number;
a: number;
}
export declare const hsvaToHsla: ({ h, s, v, a }: HsvaColor) => HslaColor;
export declare const hsvaStringToHsva: (hsvString: string) => HsvaColor;
Expand All @@ -284,6 +303,7 @@ export declare const rgbStringToHsva: (rgbaString: string) => HsvaColor;
/** Converts an RGBA color plus alpha transparency to hex */
export declare const rgbaToHex: ({ r, g, b }: RgbaColor) => string;
export declare const rgbaToHexa: ({ r, g, b, a }: RgbaColor) => string;
export type HexColor = `#${string}`;
export declare const hexToHsva: (hex: string) => HsvaColor;
export declare const hexToRgba: (hex: string) => RgbaColor;
/**
Expand All @@ -299,6 +319,11 @@ export declare const hsvaToHex: (hsva: HsvaColor) => string;
export declare const hsvaToHexa: (hsva: HsvaColor) => string;
export declare const hsvaToHsv: ({ h, s, v }: HsvaColor) => HsvColor;
export declare const color: (str: string | HsvaColor) => ColorResult;
export declare const getContrastingColor: (str: string | HsvaColor) => "#ffffff" | "#000000";
export declare const equalColorObjects: (first: ObjectColor, second: ObjectColor) => boolean;
export declare const equalColorString: (first: string, second: string) => boolean;
export declare const equalHex: (first: string, second: string) => boolean;
export declare const validHex: (hex: string) => hex is HexColor;
```

## Contributors
Expand Down
58 changes: 57 additions & 1 deletion packages/color-convert/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type ColorResult = {
rgba: RgbaColor;
hsla: HslaColor;
hsva: HsvaColor;
xy: XYColor;
hex: string;
hexa: string;
};
Expand All @@ -33,6 +34,12 @@ export interface RgbaColor extends RgbColor {
a: number;
}

export interface XYColor {
x: number;
y: number;
bri?: number;
}

/**
* ```js
* rgbaToHsva({ r: 255, g: 255, b: 255, a: 1 }) //=> { h: 0, s: 0, v: 100, a: 1 }
Expand Down Expand Up @@ -273,6 +280,53 @@ export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
export const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva));
export const hsvaToHexa = (hsva: HsvaColor): string => rgbaToHexa(hsvaToRgba(hsva));
export const hsvaToHsv = ({ h, s, v }: HsvaColor): HsvColor => ({ h, s, v });
export const hexToXY = (hex: string): XYColor => rgbToXY(rgbaToRgb(hexToRgba(hex)));
export const xyToHex = (xy: XYColor): string =>
rgbaToHex({
...xyToRgb(xy),
a: 255,
});

/**
* Converts XY to RGB. Based on formula from https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
* @param color XY color and brightness as an array [0-1, 0-1, 0-1]
*/
export const xyToRgb = ({ x, y, bri = 255 }: XYColor): RgbColor => {
const red = x * 3.2406255 + y * -1.537208 + bri * -0.4986286;
const green = x * -0.9689307 + y * 1.8757561 + bri * 0.0415175;
const blue = x * 0.0557101 + y * -0.2040211 + bri * 1.0569959;

const translate = function (color: number) {
return color <= 0.0031308 ? 12.92 * color : 1.055 * Math.pow(color, 1 / 2.4) - 0.055;
};

return {
r: Math.round(255 * translate(red)),
g: Math.round(255 * translate(green)),
b: Math.round(255 * translate(blue)),
};
};

/**
* Converts RGB to XY. Based on formula from https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
* @param color RGB color as an array [0-255, 0-255, 0-255]
*/
export const rgbToXY = ({ r, g, b }: RgbColor): XYColor => {
const translateColor = function (color: number) {
return color <= 0.04045 ? color / 12.92 : Math.pow((color + 0.055) / 1.055, 2.4);
};

const red = translateColor(r / 255);
const green = translateColor(g / 255);
const blud = translateColor(b / 255);

const xyz = {} as XYColor;
xyz.x = red * 0.4124 + green * 0.3576 + blud * 0.1805;
xyz.y = red * 0.2126 + green * 0.7152 + blud * 0.0722;
xyz.bri = red * 0.0193 + green * 0.1192 + blud * 0.9505;

return xyz;
};

export const color = (str: string | HsvaColor): ColorResult => {
let rgb!: RgbColor;
Expand All @@ -281,6 +335,7 @@ export const color = (str: string | HsvaColor): ColorResult => {
let rgba!: RgbaColor;
let hsla!: HslaColor;
let hsva!: HsvaColor;
let xy!: XYColor;
let hex!: string;
let hexa!: string;
if (typeof str === 'string' && validHex(str)) {
Expand All @@ -297,8 +352,9 @@ export const color = (str: string | HsvaColor): ColorResult => {
hex = hsvaToHex(hsva);
hsl = hslaToHsl(hsla);
rgb = rgbaToRgb(rgba);
xy = rgbToXY(rgb);
}
return { rgb, hsl, hsv, rgba, hsla, hsva, hex, hexa };
return { rgb, hsl, hsv, rgba, hsla, hsva, hex, hexa, xy };
};

export const getContrastingColor = (str: string | HsvaColor) => {
Expand Down
5 changes: 5 additions & 0 deletions test/circle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ it('Circle colors checked', async () => {
v: 95.68627450980392,
a: 1,
},
xy: {
bri: 0.06811140344707436,
x: 0.40822033351750947,
y: 0.24997641962334327,
},
hex: '#f44e3b',
hexa: '#f44e3bff',
});
Expand Down
41 changes: 38 additions & 3 deletions test/convert.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { color, getContrastingColor } from '../packages/color-convert/src';
// HEX
import { hexToHsva, hexToRgba, hsvaToHex, hsvaToHexa } from '../packages/color-convert/src';
import { hexToHsva, hexToRgba, hsvaToHex, hsvaToHexa, hexToXY } from '../packages/color-convert/src';
import { equalHex } from '../packages/color-convert/src';
// HSLA
import { hsvaToHsla, hslaToHsva, HsvaColor, HslaColor } from '../packages/color-convert/src';
Expand All @@ -15,7 +15,7 @@ import { hsvaToRgba, rgbaToHsva, RgbaColor } from '../packages/color-convert/src
// RGBA string
import { hsvaToRgbaString, rgbaStringToHsva } from '../packages/color-convert/src';
// RGB
import { rgbaToRgb, rgbaToHex, rgbaToHexa } from '../packages/color-convert/src';
import { rgbaToRgb, rgbaToHex, rgbaToHexa, rgbToXY } from '../packages/color-convert/src';
// RGB string
import { hsvaToRgbString, rgbStringToHsva } from '../packages/color-convert/src';
// HSVA String
Expand All @@ -25,6 +25,8 @@ import { hsvaToHsv } from '../packages/color-convert/src';
// HSV string
import { hsvaToHsvString, hsvStringToHsva } from '../packages/color-convert/src';
import { equalColorString, equalColorObjects, validHex } from '../packages/color-convert/src';
// XY
import { xyToHex, xyToRgb } from '../packages/color-convert/src';

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

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

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

it('Converts RGB to XY', () => {
expect(rgbToXY({ r: 255, g: 255, b: 255 })).toMatchObject({ x: 0.9505, y: 1, bri: 1.089 });
expect(rgbToXY({ r: 0, g: 0, b: 0 })).toMatchObject({ x: 0.0, y: 0.0, bri: 0 });
expect(rgbToXY({ r: 71, g: 128, b: 241 })).toMatchObject({
x: 0.26194888875915034,
y: 0.23128809648982562,
bri: 0.863027753196167,
});
});

it('Converts HEX to RGBA', () => {
expect(hsvaToHslString(hexToHsva('#d0021b'))).toEqual('hsl(352.71844660194176, 98%, 41%)');
expect(hsvaToHex(rgbaToHsva(hexToRgba('#d0021b')))).toEqual('#d0021b');
Expand All @@ -115,6 +128,12 @@ it('Converts HEX to HSVA', () => {
expect(hexToHsva('#c62182')).toMatchObject({ h: 324.72727272727275, s: 83.33333333333334, v: 77.64705882352942, a: 1 });
});

it('Converts HEX to XY', () => {
expect(hexToXY('#ffffff')).toMatchObject({ x: 0.9505, y: 1, bri: 1.089 });
expect(hexToXY('#000000')).toMatchObject({ x: 0.0, y: 0.0, bri: 0 });
expect(hexToXY('#4780f1')).toMatchObject({ x: 0.26194888875915034, y: 0.23128809648982562, bri: 0.863027753196167 });
});

it('Converts shorthand HEX to HSVA', () => {
expect(hexToHsva('#FFF')).toMatchObject({ h: 0, s: 0, v: 100, a: 1 });
expect(hexToHsva('#FF0')).toMatchObject({ h: 60, s: 100, v: 100, a: 1 });
Expand Down Expand Up @@ -409,3 +428,19 @@ it('Validates HEX colors', () => {
// @ts-ignore
expect(validHex()).toBe(false);
});

it('Converts XY to RGB', () => {
expect(xyToRgb({ x: 0.9505, y: 1, bri: 1.089 })).toMatchObject({ r: 255, g: 255, b: 255 });
expect(xyToRgb({ x: 0.0, y: 0.0, bri: 0 })).toMatchObject({ r: 0, g: 0, b: 0 });
expect(xyToRgb({ x: 0.26194888875915034, y: 0.23128809648982562, bri: 0.863027753196167 })).toMatchObject({
r: 71,
g: 128,
b: 241,
});
});

it('Converts XY to HEX', () => {
expect(xyToHex({ x: 0.9505, y: 1, bri: 1.089 })).toBe('#ffffff');
expect(xyToHex({ x: 0.0, y: 0.0, bri: 0 })).toBe('#000000');
expect(xyToHex({ x: 0.26194888875915034, y: 0.23128809648982562, bri: 0.863027753196167 })).toBe('#4780f1');
});

0 comments on commit 8643165

Please sign in to comment.