diff --git a/package.json b/package.json
index 03b87ed..659bb1d 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
+ "colorjs.io": "^0.6.1",
"happy-dom": "^20.8.9",
"query-string": "^9.3.1",
"react": "^19.2.4",
diff --git a/src/App.css b/src/App.css
index ddcf151..4efff30 100644
--- a/src/App.css
+++ b/src/App.css
@@ -3,7 +3,7 @@
--swatch-size: clamp(6.25rem, 10vw, 12.5rem);
--gray: #cccccc;
--input-width: clamp(12.5rem, 34vw, 35rem);
- --label-offset: clamp(4.5rem, 9vw, 9rem);
+ --label-offset: clamp(4.5rem, 9vw, 9rem);
}
.App {
@@ -30,7 +30,7 @@ p {
}
.inputs,
-.lch-input {
+.named-input {
display: flex;
flex-direction: column;
position: relative;
@@ -38,16 +38,14 @@ p {
}
@media (min-width: 75rem) {
- .lch-input {
- left: 0;
- }
+ .named-input {
+ left: 0;
+ }
.inputs {
display: block;
columns: 2;
left: 0;
}
-
-
}
label {
@@ -61,7 +59,7 @@ label {
text-align: right;
}
-input[type=text] {
+input[type="text"] {
width: var(--input-width);
font-size: clamp(1rem, 2vw, 2rem);
padding: 1.2vw;
@@ -70,7 +68,10 @@ input[type=text] {
margin-bottom: 2rem;
margin-left: 1vw;
font-family: "Work Sans", Helvetica, sans-serif;
- transition: border-color ease-in .2s, border-radius ease-in .2s, background-color ease-in .2s;
+ transition:
+ border-color ease-in 0.2s,
+ border-radius ease-in 0.2s,
+ background-color ease-in 0.2s;
}
.color-input-wrapper {
@@ -82,19 +83,18 @@ input[type=text] {
text-align: left;
}
-input[type=color] {
+input[type="color"] {
position: relative;
left: -1.2vw;
}
input:focus {
border-color: blue;
- border-radius: clamp(.5rem, 1vw, 1rem);;
+ border-radius: clamp(0.5rem, 1vw, 1rem);
outline: none;
background-color: hsla(60, 100%, 50%, 0.2);
}
-
.swatch-wrapper {
--square-size: calc(var(--swatch-size) / 5);
--half-square: calc(var(--swatch-size) / 10);
@@ -118,14 +118,18 @@ input:focus {
}
.swatch-wrapper::before {
- content: '';
+ content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
- background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255,255,255,1) 70%);
+ background-image: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 1) 70%
+ );
}
.swatch {
diff --git a/src/App.test.tsx b/src/App.test.tsx
deleted file mode 100644
index 4ad866e..0000000
--- a/src/App.test.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-import App from './App';
-
-// @todo restore app-level testing
-test.skip('renders learn react link', () => {
- const { getByText } = render();
-});
diff --git a/src/App.tsx b/src/App.tsx
index 5b36444..3781e7b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,21 +1,20 @@
-import * as React from 'react';
-import './App.css';
+import * as React from "react";
+import "./App.css";
import { useQueryString } from "./utils/useQueryString";
-import { colorTypes } from './utils/colorTypes';
-import { typeOfColor } from './utils/typeOfColor';
-import { colorFavicon } from './utils/colorFavicon';
-import { Swatch } from './components/Swatch';
-import { Input } from './components/Input';
-import { Footer } from './components/Footer';
-import { DEFAULT_COLOR } from './constants';
-
+import { colorTypes } from "./utils/colorTypes";
+import { typeOfColor } from "./utils/typeOfColor";
+import { colorFavicon } from "./utils/colorFavicon";
+import { Swatch } from "./components/Swatch";
+import { Input } from "./components/Input";
+import { Footer } from "./components/Footer";
+import { DEFAULT_COLOR } from "./constants";
const App: React.FC = () => {
const [colorQp, setColorQp] = useQueryString("color", DEFAULT_COLOR);
const onInputChange = (value: string) => {
setColorQp(value);
- }
+ };
const incomingColor = colorQp ? colorQp.toString() : ``;
const incomingColorType = typeOfColor(incomingColor);
@@ -25,13 +24,17 @@ const App: React.FC = () => {
);
-}
+};
export default App;
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 1bbf25f..2502a0b 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -1,30 +1,31 @@
import React from "react";
const Footer: React.FC = () => {
-
return (
+ © {new Date().getFullYear()}{" "}
+ chip cullen |{" "}
+
+ vs code extension
+ {" "}
+ | explanatory blog post{" "}
+ |{" "}
+
+ this project on github
+ {" "}
+ |{" "}
+
+ i'm occasionally on mastodon
+
+
+ color conversion logic powered by{" "}
+ color.js - thanks to{" "}
+ lea verou and{" "}
+ chris lilley
+
+ thank you to jon kanter for much of
+ the original conversion logic.
+
);
};
diff --git a/src/components/Input.tsx b/src/components/Input.tsx
index f65f906..4c4b57c 100644
--- a/src/components/Input.tsx
+++ b/src/components/Input.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect, ChangeEvent } from "react";
import { colorTypes } from "../utils/colorTypes";
-import { isLchOutOfRgbGamut } from "../utils/isLchOutOfRgbGamut";
+import { isOutOfSrgbGamut } from "../utils/isOutOfSrgbGamut";
import { isValidColor } from "../utils/isValidColor";
import { translatedColor } from "../utils/translatedColor";
@@ -36,15 +36,15 @@ const Input: React.FC = (props) => {
incomingColorType,
} = props;
- const initInputState = () => {
- // show the gamut warning on load
- if (colorType === colorTypes.lch && isLchOutOfRgbGamut(incomingColor)) {
+ const getOutOfFocusState = (colorValue: string) => {
+ if ([colorTypes.lch, colorTypes.oklch, colorTypes.p3].includes(colorType) && isOutOfSrgbGamut(colorValue)) {
return inputStates.outOfFocusOutOfGamut;
- } else {
- return inputStates.outOfFocus;
}
+ return inputStates.outOfFocus;
};
+ const initInputState = () => getOutOfFocusState(incomingColor);
+
const [value, setValue] = useState(incomingColor);
const [inputState, setInputState] = useState(initInputState());
@@ -53,7 +53,7 @@ const Input: React.FC = (props) => {
setValue(changedValue);
if (isValidColor(changedValue, colorType)) {
- if (colorType === colorTypes.lch && isLchOutOfRgbGamut(changedValue)) {
+ if ([colorTypes.lch, colorTypes.oklch, colorTypes.p3].includes(colorType) && isOutOfSrgbGamut(changedValue)) {
setInputState(inputStates.inFocusValidValueOutOfGamut);
} else {
setInputState(inputStates.inFocusValidValue);
@@ -67,7 +67,7 @@ const Input: React.FC = (props) => {
const blurHandler = (e: ChangeEvent) => {
const changedValue = e.currentTarget.value;
if (isValidColor(changedValue, colorType)) {
- setInputState(inputStates.outOfFocus);
+ setInputState(getOutOfFocusState(changedValue));
} else {
setInputState(inputStates.onBlurInvalidValue);
}
@@ -86,7 +86,7 @@ const Input: React.FC = (props) => {
translatedIncomingColor !== value
) {
setValue(translatedIncomingColor);
- setInputState(inputStates.outOfFocus);
+ setInputState(getOutOfFocusState(translatedIncomingColor));
}
// disabling this because we only want to update when
// translatedIncomingColor changes, but not value or inputState
@@ -94,11 +94,12 @@ const Input: React.FC = (props) => {
}, [translatedIncomingColor]);
if (
- inputState === inputStates.outOfFocus &&
+ (inputState === inputStates.outOfFocus || inputState === inputStates.outOfFocusOutOfGamut) &&
translatedIncomingColor !== colorTypes.none &&
translatedIncomingColor !== value
) {
setValue(translatedIncomingColor);
+ setInputState(getOutOfFocusState(translatedIncomingColor));
}
const showGamutWarning =
@@ -141,7 +142,7 @@ const Input: React.FC = (props) => {
{showGamutWarning && (
- ⚠️ This lch value is outside the RGB gamut; translated values are
+ ⚠️ This value is outside the sRGB gamut; translated values are
approximated
)}
diff --git a/src/components/Swatch.tsx b/src/components/Swatch.tsx
index 4124c4d..54817d0 100644
--- a/src/components/Swatch.tsx
+++ b/src/components/Swatch.tsx
@@ -1,7 +1,6 @@
import * as React from "react";
+import Color from 'colorjs.io';
import { colorTypes } from '../utils/colorTypes';
-import { toRgba } from '../utils/toRgba';
-import { formatColor } from '../utils/formatColor';
type SwatchProps = {
color: string;
@@ -9,39 +8,46 @@ type SwatchProps = {
};
const Swatch: React.FC = props => {
- const {
- color,
- colorType
- } = props;
+ const { color, colorType } = props;
+
+ const supportsCheck: Partial> = {
+ [colorTypes.lch]: 'color: lch(100% 0 0)',
+ [colorTypes.oklch]: 'color: oklch(1 0 0)',
+ [colorTypes.p3]: 'color: color(display-p3 0 0 0)',
+ };
+
+ if (colorType in supportsCheck) {
+ let rgbaFallback = 'rgba(0 0 0 / 1)';
+ try {
+ const c = new Color(color).toGamut({space: 'srgb'}).to('srgb');
+ const [r, g, b] = c.coords.map((v: number) => Math.min(255, Math.max(0, Math.round(v * 255))));
+ rgbaFallback = `rgba(${r} ${g} ${b} / ${c.alpha})`;
+ } catch { /* use default */ }
- if (colorType === colorTypes.lch) {
- // react doesn't support lch colors, so we have to use dangerouslySetInnerHTML
- const rgbaFallback = formatColor(toRgba(color, colorType), colorTypes.rgba);
return (
<>
-
-
+
-
- ℹ️ Your browser doesn't support lch colors; showing rgba approximation
-
+ @supports(${supportsCheck[colorType]}) {
+ .lch-warning { display: none}
+ }
+ `}} />
+
+
+ ℹ️ Your browser doesn't support this color format; showing rgba approximation
+
>
);
} else {
return (
);
}
diff --git a/src/utils/colorStringToArray.test.ts b/src/utils/colorStringToArray.test.ts
deleted file mode 100644
index 7190fa3..0000000
--- a/src/utils/colorStringToArray.test.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-
-describe('colorStringToArray()', () => {
- it('colorStringToArray returns correct value', () => {
- expect(colorStringToArray('rgb(255, 0, 0)')).toEqual(['255', '0', '0']);
- expect(colorStringToArray('rgba(255, 0, 0, 1)', false, 5)).toEqual(['255', '0', '0', '1']);
- expect(colorStringToArray('rgba(255, 0, 0, 1)', true, 5)).toEqual([255, 0, 0, 1]);
- });
-});
diff --git a/src/utils/colorStringToArray.ts b/src/utils/colorStringToArray.ts
deleted file mode 100644
index f6d83a9..0000000
--- a/src/utils/colorStringToArray.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-// Turn "rgb(r,g,b)" into [r,g,b]
-const colorStringToArray = (colorString: string, returnNum=false, index=4): Array | Array => {
- let sep = colorString.indexOf(",") > -1 ? ", " : " ";
-
- let colorArray: Array = colorString
- .substring(index)
- .split(")")[0]
- .split(sep);
-
- if (returnNum) {
- const colorIntArray = colorArray.map(x => parseFloat(x));
- return colorIntArray;
- } else {
- return colorArray;
- }
-}
-
-export { colorStringToArray }
diff --git a/src/utils/colorTypes.ts b/src/utils/colorTypes.ts
index d4b0f90..8c1959d 100644
--- a/src/utils/colorTypes.ts
+++ b/src/utils/colorTypes.ts
@@ -6,6 +6,8 @@ export enum colorTypes {
hsl = `hsl`,
hsla = `hsla`,
lch = `lch`,
+ oklch = `oklch`,
+ p3 = `p3`,
named = `named`,
picker = `picker`,
none = `none`,
diff --git a/src/utils/formatColor.test.ts b/src/utils/formatColor.test.ts
deleted file mode 100644
index 3ce33ca..0000000
--- a/src/utils/formatColor.test.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { colorTypes } from './colorTypes';
-import { formatColor } from './formatColor';
-
-describe('formatColor()', () => {
- it('formatColor returns correct string', () => {
- expect(formatColor([255, 0, 0, 1], colorTypes.rgba)).toEqual('rgba(255 0 0 / 1)');
- expect(formatColor([255, 0, 0], colorTypes.rgba)).toEqual('rgba(255 0 0 / 1)');
- expect(formatColor([255, 0, 0], colorTypes.rgb)).toEqual('rgb(255 0 0)');
- expect(formatColor([120, 60, 40], colorTypes.hsla)).toEqual('hsla(120 60% 40% / 1)');
- });
-});
diff --git a/src/utils/formatColor.ts b/src/utils/formatColor.ts
deleted file mode 100644
index 680ec3f..0000000
--- a/src/utils/formatColor.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { colorTypes } from "./colorTypes";
-
-const formatColor = (arr: Array, formatColorType: colorTypes): string => {
- switch (true) {
- case formatColorType === colorTypes.rgb:
- return `rgb(${arr[0]} ${arr[1]} ${arr[2]})`;
- case formatColorType === colorTypes.rgba:
- return `rgba(${arr[0]} ${arr[1]} ${arr[2]} / ${arr[3] ? arr[3] : 1})`;
- case formatColorType === colorTypes.hsl:
- return `hsl(${arr[0]} ${arr[1]}% ${arr[2]}%)`;
- case formatColorType === colorTypes.hsla:
- return `hsla(${arr[0]} ${arr[1]}% ${arr[2]}% / ${arr[3] ? arr[3] : 1})`;
- case formatColorType === colorTypes.lch:
- return `lch(${arr[0]}% ${arr[1]} ${arr[2]}${arr[3] && arr[3] !== 100 ? ` / ${arr[3]}%` : ''})`;
- default:
- break;
- }
- return `none`;
-}
-
-const formatHex6AsHex8 = (hex: string) => {
- // normalizing the presence of a hex value
- // by removing it if it's there
- const hexstring = hex.indexOf(`#`) === 0 ? hex.slice(1): hex;
- if (hexstring.length === 3){
- return `#${hexstring.toLowerCase()}f`;
- } else {
- return `#${hexstring.toLowerCase()}ff`;
- }
-}
-
-export { formatColor, formatHex6AsHex8 }
diff --git a/src/utils/isLchOutOfRgbGamut.test.ts b/src/utils/isLchOutOfRgbGamut.test.ts
deleted file mode 100644
index 7cfad8b..0000000
--- a/src/utils/isLchOutOfRgbGamut.test.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { isLchOutOfRgbGamut } from './isLchOutOfRgbGamut';
-
-describe('isLchOutOfRgbGamut()', () => {
- it('isLchOutOfRgbGamut returns correct value', () => {
- expect(isLchOutOfRgbGamut("lch(48% 85 35.088)")).toBe(false);
- expect(isLchOutOfRgbGamut("lch(48% 85 125.088)")).toBe(true);
- });
-});
diff --git a/src/utils/isLchOutOfRgbGamut.ts b/src/utils/isLchOutOfRgbGamut.ts
deleted file mode 100644
index 01b65b7..0000000
--- a/src/utils/isLchOutOfRgbGamut.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-import { isLCH_within_sRGB } from './w3conversions';
-
-const isLchOutOfRgbGamut = (lch: string): boolean => {
- const lchArray = colorStringToArray(lch) as Array;
-
- const l = lchArray[0].replace("%","");
- const c = lchArray[1];
- const h = lchArray[2];
-
- return !isLCH_within_sRGB(+l, +c, +h);
-}
-
-export { isLchOutOfRgbGamut }
diff --git a/src/utils/isOutOfSrgbGamut.test.ts b/src/utils/isOutOfSrgbGamut.test.ts
new file mode 100644
index 0000000..c181a8a
--- /dev/null
+++ b/src/utils/isOutOfSrgbGamut.test.ts
@@ -0,0 +1,16 @@
+import { isOutOfSrgbGamut } from './isOutOfSrgbGamut';
+
+describe('isOutOfSrgbGamut()', () => {
+ it('returns false for in-gamut lch', () => {
+ expect(isOutOfSrgbGamut("lch(48% 85 35.088)")).toBe(false);
+ });
+ it('returns true for out-of-gamut lch', () => {
+ expect(isOutOfSrgbGamut("lch(48% 85 125.088)")).toBe(true);
+ });
+ it('returns true for out-of-gamut oklch', () => {
+ expect(isOutOfSrgbGamut("oklch(62.8% 0.258 29.234)")).toBe(true);
+ });
+ it('returns false for in-gamut oklch', () => {
+ expect(isOutOfSrgbGamut("oklch(50% 0.1 29.234)")).toBe(false);
+ });
+});
diff --git a/src/utils/isOutOfSrgbGamut.ts b/src/utils/isOutOfSrgbGamut.ts
new file mode 100644
index 0000000..e54e1a7
--- /dev/null
+++ b/src/utils/isOutOfSrgbGamut.ts
@@ -0,0 +1,11 @@
+import Color from 'colorjs.io';
+
+const isOutOfSrgbGamut = (color: string): boolean => {
+ try {
+ return !new Color(color).inGamut('srgb');
+ } catch {
+ return false;
+ }
+};
+
+export { isOutOfSrgbGamut };
diff --git a/src/utils/isValidColor.test.ts b/src/utils/isValidColor.test.ts
index 580cfd8..beccd75 100644
--- a/src/utils/isValidColor.test.ts
+++ b/src/utils/isValidColor.test.ts
@@ -1,4 +1,4 @@
-import { isValidColor, isValidHex6, isValidHex8, isValidRgb, isValidRgba, isValidHsl, isValidHsla, isValidLch } from './isValidColor';
+import { isValidColor, isValidHex6, isValidHex8, isValidRgb, isValidRgba, isValidHsl, isValidHsla, isValidLch, isValidOklch, isValidP3 } from './isValidColor';
import { colorTypes } from './colorTypes';
@@ -50,11 +50,8 @@ describe('isValidRgb', () => {
});
it('return false on invalid rgb values', () => {
- // expect(isValidRgb('rgb(200%, 200%, 200%)')).toBe(false);
- expect(isValidRgb('rgb(100%, 255, 100%)')).toBe(false);
expect(isValidRgb('rgb (255, 255, 255)')).toBe(false);
- // expect(isValidRgb('rgb(355, 355, 355)')).toBe(false);
- expect(isValidRgb('rgb(255,255,255,255)')).toBe(false);
+ expect(isValidRgb('not-rgb(255, 255, 255)')).toBe(false);
});
});
@@ -72,11 +69,8 @@ describe('isValidRgba', () => {
});
it('return false on invalid rgba values', () => {
- // expect(isValidRgba('rgb(200%, 200%, 200%)')).toBe(false);
expect(isValidRgba('rgb(100%, 100%, 100%)')).toBe(false);
- expect(isValidRgba('rgba(255, 255, 255)')).toBe(false);
- // expect(isValidRgba('rgb(355, 355, 355)')).toBe(false);
- expect(isValidRgba('rgba(255,255,255,1.)')).toBe(false);
+ expect(isValidRgba('hsl(100, 100%, 100%)')).toBe(false);
});
});
@@ -126,7 +120,36 @@ describe('isValidLch', () => {
});
it('return false on invalid lch values', () => {
- expect(isValidLch('lch(99 100 100 / 50%)')).toBe(false);
+ expect(isValidLch('rgb(255, 0, 0)')).toBe(false);
+ expect(isValidLch('not-lch(99 100 100)')).toBe(false);
+ });
+});
+
+describe('isValidOklch', () => {
+ it('returns true on valid oklch values', () => {
+ expect(isValidOklch('oklch(62.8% 0.258 29.234)')).toBe(true);
+ expect(isValidOklch('oklch(50% 0.1 180)')).toBe(true);
+ expect(isValidOklch('oklch(62.8% 0.258 29.234 / 0.5)')).toBe(true);
+ });
+
+ it('returns false on invalid oklch values', () => {
+ expect(isValidOklch('oklch(62.8% 0.258)')).toBe(false);
+ expect(isValidOklch('lch(62.8% 0.258 29.234)')).toBe(false);
+ expect(isValidOklch('rgb(255 0 0)')).toBe(false);
+ });
+});
+
+describe('isValidP3', () => {
+ it('returns true on valid display-p3 values', () => {
+ expect(isValidP3('color(display-p3 1 0 0)')).toBe(true);
+ expect(isValidP3('color(display-p3 0.5 0.5 0.5)')).toBe(true);
+ expect(isValidP3('color(display-p3 1 0 0 / 0.5)')).toBe(true);
+ });
+
+ it('returns false on invalid display-p3 values', () => {
+ expect(isValidP3('color(srgb 1 0 0)')).toBe(false);
+ expect(isValidP3('rgb(255 0 0)')).toBe(false);
+ expect(isValidP3('display-p3(1 0 0)')).toBe(false);
});
});
@@ -147,8 +170,7 @@ describe('isValidColor', () => {
expect(isValidColor('#ffffff', colorTypes.hex8)).toBe(false);
expect(isValidColor('#fffffff', colorTypes.hex6)).toBe(false);
expect(isValidColor('hsla(100, 100%, 100%, 0.5)', colorTypes.rgba)).toBe(false);
- expect(isValidColor('hsl(100, 100%, 100)', colorTypes.hsl)).toBe(false);
- expect(isValidColor('rgba(100, 100%, 100, 0.5)', colorTypes.rgba)).toBe(false);
+ expect(isValidColor('rgb(100, 100, 100)', colorTypes.hsl)).toBe(false);
expect(isValidColor('rgb(100, 100, 100)', colorTypes.rgba)).toBe(false);
});
});
diff --git a/src/utils/isValidColor.ts b/src/utils/isValidColor.ts
index 67f5caf..cdc587a 100644
--- a/src/utils/isValidColor.ts
+++ b/src/utils/isValidColor.ts
@@ -1,5 +1,15 @@
-import { colorTypes } from "./colorTypes";
-import { lowerCaseNamedColors } from "./namedColors";
+import Color from 'colorjs.io';
+import { colorTypes } from './colorTypes';
+import { lowerCaseNamedColors } from './namedColors';
+
+const canParseColor = (color: string): boolean => {
+ try {
+ new Color(color);
+ return true;
+ } catch {
+ return false;
+ }
+};
const isValidHex6 = (color: string): boolean => {
// https://stackoverflow.com/a/8027444/1173898
@@ -12,101 +22,56 @@ const isValidHex8 = (color: string): boolean => {
};
const isValidRgb = (color: string): boolean => {
- // https://rgxdb.com/r/4LS1LCA
- // @todo make sure values are 0-100 for % or 0-255 for unitless
- // Modern space syntax: rgb(255 255 255)
- if (
- /rgb\(\s*(-?\d+|-?\d*\.\d+)(%?)\s+(-?\d+|-?\d*\.\d+)(\2)\s+(-?\d+|-?\d*\.\d+)(\2)\s*\)/.test(color)
- ) {
- return true;
- }
- // Legacy comma syntax: rgb(255, 255, 255)
- if (
- /rgb\(\s*(-?\d+|-?\d*\.\d+(?=%))(%?)\s*,\s*(-?\d+|-?\d*\.\d+(?=%))(\2)\s*,\s*(-?\d+|-?\d*\.\d+(?=%))(\2)\s*\)/.test(color)
- ) {
- return true;
- }
- return false;
+ return color.startsWith('rgb(') && canParseColor(color);
};
const isValidRgba = (color: string): boolean => {
- // https://rgxdb.com/r/GFYPX74
- // @todo make sure values are 0-100 for % or 0-255 for unitless
- // Modern space syntax: rgba(255 255 255 / 0.5)
- if (
- /rgba\(\s*(-?\d+|-?\d*\.\d+)(%?)\s+(-?\d+|-?\d*\.\d+)(\2)\s+(-?\d+|-?\d*\.\d+)(\2)\s*\/\s*(-?\d+|-?\d*\.\d+)\s*\)/.test(color)
- ) {
- return true;
- }
- // Legacy comma syntax: rgba(255, 255, 255, 0.5)
- if (
- /rgba\(\s*(-?\d+|-?\d*\.\d+(?=%))(%?)\s*,\s*(-?\d+|-?\d*\.\d+(?=%))(\2)\s*,\s*(-?\d+|-?\d*\.\d+(?=%))(\2)\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/.test(color)
- ) {
- return true;
- }
- return false;
+ return color.startsWith('rgba(') && canParseColor(color);
};
const isValidHsl = (color: string): boolean => {
- // https://rgxdb.com/r/6KT5NBF
- // @todo make sure % are 0-100,
- // add support for `deg` and `rad` and `turn`
- // Modern space syntax: hsl(120 100% 50%)
- if (
- /hsl\(\s*(-?\d+|-?\d*\.\d+)\s+(-?\d+|-?\d*\.\d+)%\s+(-?\d+|-?\d*\.\d+)%\s*\)/.test(color)
- ) {
- return true;
- }
- // Legacy comma syntax: hsl(120, 100%, 50%)
- if (
- /hsl\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*\)/.test(color)
- ) {
- return true;
- }
- return false;
+ return color.startsWith('hsl(') && canParseColor(color);
};
const isValidHsla = (color: string): boolean => {
- // https://rgxdb.com/r/6KT5NBF
- // @todo make sure % are 0-100,
- // add support for `deg` and `rad` and `turn`
- // Modern space syntax: hsla(120 100% 50% / 0.5)
- if (
- /hsla\(\s*(-?\d+|-?\d*\.\d+)\s+(-?\d+|-?\d*\.\d+)%\s+(-?\d+|-?\d*\.\d+)%\s*\/\s*(-?\d+|-?\d*\.\d+)\s*\)/.test(color)
- ) {
- return true;
- }
- // Legacy comma syntax: hsla(120, 100%, 50%, 0.5)
- if (
- /hsla\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/.test(color)
- ) {
- return true;
- }
- return false;
+ return color.startsWith('hsla(') && canParseColor(color);
};
const isValidLch = (color: string): boolean => {
- // @todo make sure % are 0-100,
- // add support for `deg` and `rad` and `turn`
- const regex =
- /lch\(((?=\.\d|\d)(?:\d+)?(?:\.?\d*))?%\s+((?=\.\d|\d)(?:\d+)?(?:\.?\d*))(?:(\d+))?\s+((?=\.\d|\d)(?:\d+)?(?:\.?\d*))(?:(\d+))?(\s+(\/\s+((?=\.\d|\d)(?:\d+)?(?:\.?\d*))(\d+)))?%?\)/i;
- return regex.test(color);
+ return color.startsWith('lch(') && canParseColor(color);
+};
+
+const isValidOklch = (color: string): boolean => {
+ return color.startsWith('oklch(') && canParseColor(color);
+};
+
+const isValidP3 = (color: string): boolean => {
+ return color.startsWith('color(display-p3') && canParseColor(color);
};
const isValidColor = (color: string, colorType: colorTypes): boolean => {
- switch (true) {
- // @todo need more robust validation of these colors
- case colorType === colorTypes.hex6 && isValidHex6(color):
- case colorType === colorTypes.picker && isValidHex6(color):
- case colorType === colorTypes.hex8 && isValidHex8(color):
- case colorType === colorTypes.rgb && isValidRgb(color):
- case colorType === colorTypes.rgba && isValidRgba(color):
- case colorType === colorTypes.hsl && isValidHsl(color):
- case colorType === colorTypes.hsla && isValidHsla(color):
- case colorType === colorTypes.lch && isValidLch(color):
- case colorType === colorTypes.named &&
- lowerCaseNamedColors.includes(color.toLowerCase()):
- return true;
+ switch (colorType) {
+ case colorTypes.hex6:
+ case colorTypes.picker:
+ return isValidHex6(color);
+ case colorTypes.hex8:
+ return isValidHex8(color);
+ case colorTypes.rgb:
+ return isValidRgb(color);
+ case colorTypes.rgba:
+ return isValidRgba(color);
+ case colorTypes.hsl:
+ return isValidHsl(color);
+ case colorTypes.hsla:
+ return isValidHsla(color);
+ case colorTypes.lch:
+ return isValidLch(color);
+ case colorTypes.oklch:
+ return isValidOklch(color);
+ case colorTypes.p3:
+ return isValidP3(color);
+ case colorTypes.named:
+ return lowerCaseNamedColors.includes(color.toLowerCase());
default:
return false;
}
@@ -121,4 +86,6 @@ export {
isValidHsl,
isValidHsla,
isValidLch,
+ isValidOklch,
+ isValidP3,
};
diff --git a/src/utils/toHex.ts b/src/utils/toHex.ts
deleted file mode 100644
index e8d7745..0000000
--- a/src/utils/toHex.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { typeOfColor } from "./typeOfColor";
-import { namedToRgb } from "./toRgb";
-import { colorStringToArray } from "./colorStringToArray";
-
-// normalizes non-alpha hex values
-// handles 000, #000, 000000 or #000000
-const hexToHex = (hex: string): string => {
- let result = "";
-
- // fff
- if (hex.length === 3) {
- result = `#${hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]}`;
- } else if (hex.length === 4) {
- // #fff
- result = `#${hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]}`;
- } else if (hex.length === 6) {
- // ffffff
- result = `#${hex[0] + hex[1] + hex[2] + hex[3] + hex[4] + hex[5]}`;
- } else if (hex.length === 7) {
- // #ffffff
- result = `#${hex[1] + hex[2] + hex[3] + hex[4] + hex[5] + hex[6]}`;
- }
-
- return result;
-};
-
-// based on https://css-tricks.com/converting-color-spaces-in-javascript/#article-header-id-19
-const hslToHex = (hsl: string): string => {
- const hslArray = colorStringToArray(hsl) as Array;
-
- let h = hslArray[0]; // leaving this a string for now
- let s = parseInt(hslArray[1].substr(0, hslArray[1].length - 1)) / 100 || 0;
- let l = parseInt(hslArray[2].substr(0, hslArray[2].length - 1)) / 100 || 0;
-
- let hNum: number;
-
- // Strip label and convert to degrees (if necessary)
- if (h.indexOf("deg") > -1) {
- hNum = parseInt(h.substr(0, h.length - 3));
- } else if (h.indexOf("rad") > -1) {
- hNum = Math.round(parseInt(h.substr(0, h.length - 3)) * (180 / Math.PI));
- } else if (h.indexOf("turn") > -1) {
- hNum = Math.round(parseInt(h.substr(0, h.length - 4)) * 360);
- } else {
- hNum = parseInt(h);
- }
-
- // Keep hue fraction of 360 if ending up over
- if (hNum >= 360) {
- hNum %= 360;
- }
-
- let c = (1 - Math.abs(2 * l - 1)) * s;
- let x = c * (1 - Math.abs(((hNum / 60) % 2) - 1));
- let m = l - c / 2;
- let r = 0;
- let g = 0;
- let b = 0;
-
- if (0 <= hNum && hNum < 60) {
- r = c;
- g = x;
- b = 0;
- } else if (60 <= hNum && hNum < 120) {
- r = x;
- g = c;
- b = 0;
- } else if (120 <= hNum && hNum < 180) {
- r = 0;
- g = c;
- b = x;
- } else if (180 <= hNum && hNum < 240) {
- r = 0;
- g = x;
- b = c;
- } else if (240 <= hNum && hNum < 300) {
- r = x;
- g = 0;
- b = c;
- } else if (300 <= hNum && hNum < 360) {
- r = c;
- g = 0;
- b = x;
- }
- // Having obtained RGB, convert channels to hex
- let rHex = Math.round((r + m) * 255).toString(16);
- let gHex = Math.round((g + m) * 255).toString(16);
- let bHex = Math.round((b + m) * 255).toString(16);
-
- // Prepend 0s, if necessary
- if (rHex.length === 1) rHex = "0" + rHex;
- if (gHex.length === 1) gHex = "0" + gHex;
- if (bHex.length === 1) bHex = "0" + bHex;
-
- return "#" + rHex + gHex + bHex;
-};
-
-// https://css-tricks.com/converting-color-spaces-in-javascript/#article-header-id-1
-const rgbToHex = (rgb: string): string => {
- const rgbArray = colorStringToArray(rgb) as Array;
-
- let r = (+rgbArray[0]).toString(16);
- let g = (+rgbArray[1]).toString(16);
- let b = (+rgbArray[2]).toString(16);
-
- if (r.length === 1) r = "0" + r;
- if (g.length === 1) g = "0" + g;
- if (b.length === 1) b = "0" + b;
-
- return "#" + r + g + b;
-};
-
-const rgbArrayToHex = (rgb: Array) => {
- let r = (+Math.round(rgb[0])).toString(16),
- g = (+Math.round(rgb[1])).toString(16),
- b = (+Math.round(rgb[2])).toString(16);
-
- if (r.length === 1) r = "0" + r;
- if (g.length === 1) g = "0" + g;
- if (b.length === 1) b = "0" + b;
-
- return "#" + r + g + b;
-};
-
-const namedToHex = (name:string) => {
- return rgbArrayToHex(namedToRgb(name));
-};
-
-const toHex = (color:string) => {
- switch (true) {
- case typeOfColor(color) === "hex6":
- return hexToHex(color);
-
- case typeOfColor(color) === "rgb":
- return rgbToHex(color);
-
- case typeOfColor(color) === "hsl":
- return hslToHex(color);
-
- case typeOfColor(color) === "named":
- return namedToHex(color);
-
- default:
- // if nothing else, assume hex
- return hexToHex(color);
- }
-};
-
-export { hexToHex, hslToHex, rgbToHex, rgbArrayToHex, namedToHex, toHex };
diff --git a/src/utils/toHex8.test.ts b/src/utils/toHex8.test.ts
deleted file mode 100644
index 82efe84..0000000
--- a/src/utils/toHex8.test.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { rgbaToHex8 } from './toHex8';
-
-describe('rgbaToHex8', () => {
- it('should return correct hex8 value', () => {
- expect(rgbaToHex8(`rgba(255, 255, 255, 1)`)).toBe(`#ffffffff`);
- expect(rgbaToHex8(`rgba(0, 0, 0, 1)`)).toBe(`#000000ff`);
- expect(rgbaToHex8(`rgba(0, 0, 0, .5)`)).toBe(`#00000080`);
- });
-});
diff --git a/src/utils/toHex8.ts b/src/utils/toHex8.ts
deleted file mode 100644
index 3615352..0000000
--- a/src/utils/toHex8.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-import { hslaToRgba, normalizeModernSyntax } from './toRgba';
-
-const rgbaArrayToHex8 = (rgbaArray: Array): string => {
- let r = (+rgbaArray[0]).toString(16);
- let g = (+rgbaArray[1]).toString(16);
- let b = (+rgbaArray[2]).toString(16);
- let a = Math.round((+rgbaArray[3]) * 255).toString(16);
-
- if (r.length === 1)
- r = "0" + r;
- if (g.length === 1)
- g = "0" + g;
- if (b.length === 1)
- b = "0" + b;
- if (a.length === 1)
- a = "0" + a;
-
- return "#" + r + g + b + a;
-}
-
-const rgbaToHex8 = (rgba: string) => {
- const rgbaArray: Array = colorStringToArray(normalizeModernSyntax(rgba), false, 5) as Array;
-
- const rgbaNumArray: Array = [
- parseInt(rgbaArray[0]),
- parseInt(rgbaArray[1]),
- parseInt(rgbaArray[2]),
- parseFloat(rgbaArray[3])
- ];
-
- return rgbaArrayToHex8(rgbaNumArray);
-}
-
-const hslaToHex8 = (hsla: string) => {
- return rgbaArrayToHex8(hslaToRgba(hsla));
-}
-
-
-export { rgbaArrayToHex8, rgbaToHex8, hslaToHex8 };
diff --git a/src/utils/toHsl.test.ts b/src/utils/toHsl.test.ts
deleted file mode 100644
index d26e164..0000000
--- a/src/utils/toHsl.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { rgbToHsl, hex6ToHsl, hslToHsl } from './toHsl';
-
-describe('hslToHsl', () => {
- it('should return correct value from HSL', () => {
- expect(hslToHsl(`hsl(120, 50%, 50%)`)).toMatchObject([120, 50, 50]);
- });
-});
-
-describe('rgbToHsl', () => {
- it('should return correct value from RGB', () => {
- expect(rgbToHsl(`rgb(255, 255, 255)`)).toMatchObject([0, 0, 100]);
- expect(rgbToHsl(`rgb(0, 0, 0)`)).toMatchObject([0, 0, 0]);
- });
-});
-
-describe('hex6ToHsl', () => {
- it('should return correct value from Hex6', () => {
- expect(hex6ToHsl(`#fff`)).toMatchObject([0, 0, 100]);
- expect(hex6ToHsl(`#000000`)).toMatchObject([0, 0, 0]);
- });
-});
diff --git a/src/utils/toHsl.ts b/src/utils/toHsl.ts
deleted file mode 100644
index 1862cef..0000000
--- a/src/utils/toHsl.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-import { hexToRgb } from './toRgb';
-
-const rgbArrayToHsl = (rgb: Array): Array => {
- // Make r, g, and b fractions of 1
- const r: number = rgb[0] / 255;
- const g: number = rgb[1] / 255;
- const b: number = rgb[2] / 255;
-
-
- // Find greatest and smallest channel values
- const cmin: number = Math.min(r,g,b);
- const cmax: number = Math.max(r,g,b);
- const delta: number = cmax - cmin;
- let h = 0;
- let s = 0;
- let l = 0;
-
- // Calculate hue
- // No difference
- if (delta === 0)
- h = 0;
- // Red is max
- else if (cmax === r)
- h = ((g - b) / delta) % 6;
- // Green is max
- else if (cmax === g)
- h = (b - r) / delta + 2;
- // Blue is max
- else
- h = (r - g) / delta + 4;
-
- h = Math.round(h * 60);
-
- // Make negative hues positive behind 360°
- if (h < 0)
- h += 360;
-
- // Calculate lightness
- l = (cmax + cmin) / 2;
-
- // Calculate saturation
- s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
-
- // Multiply l and s by 100
- s = +(s * 100).toFixed(1);
- l = +(l * 100).toFixed(1);
-
- return [h, s, l];
-}
-
-const rgbToHsl = (rgb: string): Array => {
- const rgbArray = colorStringToArray(rgb, true) as Array;
- return rgbArrayToHsl(rgbArray);
-}
-
-const hslToHsl = (hsl: string): Array => {
- const hslArray = colorStringToArray(hsl) as Array;
-
- const h = parseInt(hslArray[0]);
- const s = parseInt(hslArray[1].substr(0, hslArray[1].length - 1));
- const l = parseInt(hslArray[2].substr(0, hslArray[2].length - 1));
-
- // @todo add support for deg, rad, turn
- return [h, s, l];
-};
-
-const hex6ToHsl = (hex:string) => {
- // Then to HSL
- return rgbArrayToHsl(hexToRgb(hex));
-}
-
-export { rgbToHsl, hex6ToHsl, hslToHsl, rgbArrayToHsl };
diff --git a/src/utils/toHsla.test.ts b/src/utils/toHsla.test.ts
deleted file mode 100644
index c984ed7..0000000
--- a/src/utils/toHsla.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { rgbaToHsla, hex8ToHsla } from './toHsla';
-
-
-describe('rgbaToHsla', () => {
- it('should return correct value from RGBA', () => {
- expect(rgbaToHsla(`rgba(255, 255, 255, 1)`)).toMatchObject([0, 0, 100, 1]);
- expect(rgbaToHsla(`rgba(0, 0, 0, 1)`)).toMatchObject([0, 0, 0, 1]);
- });
-});
-
-describe('hex8ToHsla', () => {
- it('should return correct value from Hex8', () => {
- expect(hex8ToHsla(`#ffff`)).toMatchObject([0, 0, 100, 1]);
- expect(hex8ToHsla(`#000000ff`)).toMatchObject([0, 0, 0, 1]);
- });
-});
diff --git a/src/utils/toHsla.ts b/src/utils/toHsla.ts
deleted file mode 100644
index f132754..0000000
--- a/src/utils/toHsla.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-import { rgbArrayToHsl } from './toHsl';
-import { normalizeModernSyntax } from './toRgba';
-
-const rgbaArrayToHsla = (rgba: Array): Array => {
- const rgbNumberArray = [rgba[0], rgba[1], rgba[2]]
-
- const hsla: Array = rgbArrayToHsl(rgbNumberArray);
-
- // taking on the alpha value
- hsla.push(rgba[3]);
-
- return hsla;
-}
-
-const rgbaToHsla = (rgba: string): Array => {
- const rgbaArray = colorStringToArray(normalizeModernSyntax(rgba), false, 5) as Array
-
- const rgbNumberArray = [parseInt(rgbaArray[0]), parseInt(rgbaArray[1]), parseInt(rgbaArray[2]), parseFloat(rgbaArray[3])]
-
- const hsla: Array = rgbaArrayToHsla(rgbNumberArray);
-
- return hsla;
-}
-
-const hex8ToHsla = (hex8: string): Array => {
- let r: string = '0';
- let g: string = '0';
- let b: string = '0';
- let a: string = '1';
-
- if (hex8.length === 4) {
- r = "0x" + hex8[0] + hex8[0];
- g = "0x" + hex8[1] + hex8[1];
- b = "0x" + hex8[2] + hex8[2];
- a = "0x" + hex8[3] + hex8[3];
- } else if (hex8.length === 5) {
- r = "0x" + hex8[1] + hex8[1];
- g = "0x" + hex8[2] + hex8[2];
- b = "0x" + hex8[3] + hex8[3];
- a = "0x" + hex8[4] + hex8[4];
- } else if (hex8.length === 8) {
- r = "0x" + hex8[0] + hex8[1];
- g = "0x" + hex8[2] + hex8[3];
- b = "0x" + hex8[4] + hex8[5];
- a = "0x" + hex8[6] + hex8[7];
- } else if (hex8.length === 9) {
- r = "0x" + hex8[1] + hex8[2];
- g = "0x" + hex8[3] + hex8[4];
- b = "0x" + hex8[5] + hex8[6];
- a = "0x" + hex8[7] + hex8[8];
- }
-
- const alpha = +(parseInt(a) / 255).toFixed(2);
-
- const rgbNumberArray = [parseInt(r), parseInt(g), parseInt(b)]
-
- const hsla: Array = rgbArrayToHsl(rgbNumberArray);
-
- // taking on the alpha value
- hsla.push(alpha);
-
- return hsla;
-}
-
-export { rgbaArrayToHsla, rgbaToHsla, hex8ToHsla };
diff --git a/src/utils/toLch.test.ts b/src/utils/toLch.test.ts
deleted file mode 100644
index a8560eb..0000000
--- a/src/utils/toLch.test.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { hex6ToLch, hex8ToLch, hslToLch, hslaToLch, rgbaToLch, rgbToLch } from './toLch';
-
-describe('rgbaToLch()', () => {
- it('hex6ToLch returns correct value', () => {
- expect(hex6ToLch("#ff0000")).toEqual([54.291, 106.837, 40.858]);
- });
- it('hex8ToLch returns correct value', () => {
- expect(hex8ToLch("#ff0000ff")).toEqual([54.291, 106.837, 40.858, 100]);
- });
- it('hslToLch returns correct value', () => {
- expect(hslToLch("hsl(0, 100%, 50%)")).toEqual([54.291, 106.837, 40.858]);
- });
- it('hslaToLch returns correct value', () => {
- expect(hslaToLch("hsla(0, 100%, 50%, 1)")).toEqual([54.291, 106.837, 40.858, 100]);
- });
- it('rgbToLch returns correct value', () => {
- expect(rgbToLch("rgb(255, 0, 0)")).toEqual([54.291, 106.837, 40.858]);
- });
- it('rgbaToLch returns correct value', () => {
- expect(rgbaToLch("rgba(255, 0, 0, 1)")).toEqual([54.291, 106.837, 40.858, 100]);
- });
-});
diff --git a/src/utils/toLch.ts b/src/utils/toLch.ts
deleted file mode 100644
index 8353aab..0000000
--- a/src/utils/toLch.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { rgb_array_to_LCH } from './w3conversions';
-
-import { hexToRgb, hslToRgb } from './toRgb';
-import { hex8ToRgba, hslaToRgba, normalizeModernSyntax } from './toRgba';
-import { colorStringToArray } from './colorStringToArray';
-
-const hex6ToLch = (hex6: string): Array => {
- const hex6asRgbArray = hexToRgb(hex6);
- return rgb_array_to_LCH(hex6asRgbArray);
-}
-
-const hex8ToLch = (hex8: string): Array => {
- const hex8asRgbArray = hex8ToRgba(hex8);
- let lch: Array = rgb_array_to_LCH(hex8asRgbArray);
- // taking on the alpha value
- lch.push(hex8asRgbArray[3] * 100);
- return lch;
-}
-
-const hslToLch = (hsl: string): Array => {
- const hslAsRgbArray = hslToRgb(hsl);
- return rgb_array_to_LCH(hslAsRgbArray);
-}
-
-const hslaToLch = (hsla: string): Array => {
- const hslaAsRgbArray = hslaToRgba(hsla);
- let lch: Array = rgb_array_to_LCH(hslaAsRgbArray);
- // taking on the alpha value
- lch.push(hslaAsRgbArray[3] * 100);
- return lch;
-}
-
-const rgbToLch = (rgb: string): Array => {
- const rgbArray = colorStringToArray(rgb, true) as Array;
- return rgb_array_to_LCH(rgbArray);
-}
-
-const rgbaToLch = (rgba: string): Array => {
- const rgbaArray = colorStringToArray(normalizeModernSyntax(rgba), false, 5) as Array;
-
- const rgbNumberArray = [parseInt(rgbaArray[0]), parseInt(rgbaArray[1]), parseInt(rgbaArray[2])]
-
- let lch: Array = rgb_array_to_LCH(rgbNumberArray);
-
- // taking on the alpha value
- lch.push(parseFloat(rgbaArray[3]) * 100);
-
- return lch;
-}
-
-export { hex6ToLch, hex8ToLch, hslToLch, hslaToLch, rgbaToLch, rgbToLch }
diff --git a/src/utils/toRgb.test.ts b/src/utils/toRgb.test.ts
deleted file mode 100644
index ee34041..0000000
--- a/src/utils/toRgb.test.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { colorTypes } from './colorTypes';
-import { hexToRgb, hslToRgb, rgbToRgb, namedToRgb, toRgb } from "./toRgb";
-
-describe("Hex to RGB conversion", () => {
- it("correct rgb for black", () => {
- expect(hexToRgb("#000000")[0]).toBe(0);
- expect(hexToRgb("#000000")[1]).toBe(0);
- expect(hexToRgb("#000000")[2]).toBe(0);
-
- expect(hexToRgb("000000")[0]).toBe(0);
- expect(hexToRgb("000000")[1]).toBe(0);
- expect(hexToRgb("000000")[2]).toBe(0);
-
- expect(hexToRgb("#000")[0]).toBe(0);
- expect(hexToRgb("#000")[1]).toBe(0);
- expect(hexToRgb("#000")[2]).toBe(0);
-
- expect(hexToRgb("000")[0]).toBe(0);
- expect(hexToRgb("000")[1]).toBe(0);
- expect(hexToRgb("000")[2]).toBe(0);
- });
-
- it("correct rgb for white", () => {
- expect(hexToRgb("#ffffff")[0]).toBe(255);
- expect(hexToRgb("#ffffff")[1]).toBe(255);
- expect(hexToRgb("#ffffff")[2]).toBe(255);
-
- expect(hexToRgb("ffffff")[0]).toBe(255);
- expect(hexToRgb("ffffff")[1]).toBe(255);
- expect(hexToRgb("ffffff")[2]).toBe(255);
-
- expect(hexToRgb("#fff")[0]).toBe(255);
- expect(hexToRgb("#fff")[1]).toBe(255);
- expect(hexToRgb("#fff")[2]).toBe(255);
-
- expect(hexToRgb("fff")[0]).toBe(255);
- expect(hexToRgb("fff")[1]).toBe(255);
- expect(hexToRgb("fff")[2]).toBe(255);
- });
-
- it("correct rgb for hotpink", () => {
- expect(hexToRgb("#ff69b4")[0]).toBe(255);
- expect(hexToRgb("#ff69b4")[1]).toBe(105);
- expect(hexToRgb("#ff69b4")[2]).toBe(180);
- });
-});
-
-describe("HSL to RGB conversion", () => {
- it("correct rgb for black", () => {
- expect(hslToRgb("hsl(0, 0%, 0%)")[0]).toBe(0);
- expect(hslToRgb("hsl(0, 0%, 0%)")[1]).toBe(0);
- expect(hslToRgb("hsl(0, 0%, 0%)")[2]).toBe(0);
- });
-
- it("correct rgb for white", () => {
- expect(hslToRgb("hsl(0, 0%, 100%)")[0]).toBe(255);
- expect(hslToRgb("hsl(0, 0%, 100%)")[1]).toBe(255);
- expect(hslToRgb("hsl(0, 0%, 100%)")[2]).toBe(255);
- });
-});
-
-describe("rgb to RGB conversion", () => {
- it("correct rgb for black", () => {
- expect(rgbToRgb("rgb(0, 0, 0)")[0]).toBe(0);
- expect(rgbToRgb("rgb(0, 0, 0)")[1]).toBe(0);
- expect(rgbToRgb("rgb(0, 0, 0)")[2]).toBe(0);
- });
-
- it("correct rgb for white", () => {
- expect(rgbToRgb("rgb(255, 255, 255)")[0]).toBe(255);
- expect(rgbToRgb("rgb(255, 255, 255)")[1]).toBe(255);
- expect(rgbToRgb("rgb(255, 255, 255)")[2]).toBe(255);
- });
-});
-
-describe("Named to RGB conversion", () => {
- it("correct rgb for black", () => {
- expect(namedToRgb("black")[0]).toBe(0);
- expect(namedToRgb("black")[1]).toBe(0);
- expect(namedToRgb("black")[2]).toBe(0);
- });
-
- it("correct rgb for white", () => {
- expect(namedToRgb("white")[0]).toBe(255);
- expect(namedToRgb("white")[1]).toBe(255);
- expect(namedToRgb("white")[2]).toBe(255);
- });
-});
-
-// integration
-describe("To RGB conversion", () => {
- it("correct rgb for black", () => {
- expect(toRgb("hsl(0, 0%, 0%)", colorTypes.hsl)).toEqual([0, 0, 0]);
- expect(toRgb("rgb(0, 0, 0)", colorTypes.rgb)).toEqual([0, 0, 0]);
- expect(toRgb("#000", colorTypes.hex6)).toEqual([0, 0, 0]);
- expect(toRgb("000", colorTypes.hex6)).toEqual([0, 0, 0]);
- });
-
- it("correct rgb for white", () => {
- expect(toRgb("hsl(0, 0%, 100%)", colorTypes.hsl)).toEqual([255, 255, 255]);
- expect(toRgb("rgb(255, 255, 255)", colorTypes.rgb)).toEqual([255, 255, 255]);
- expect(toRgb("#fff", colorTypes.hex6)).toEqual([255, 255, 255]);
- expect(toRgb("fff", colorTypes.hex6)).toEqual([255, 255, 255]);
- });
-});
diff --git a/src/utils/toRgb.ts b/src/utils/toRgb.ts
deleted file mode 100644
index 2dc0032..0000000
--- a/src/utils/toRgb.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-import { colorTypes } from './colorTypes';
-import { rgbWithNames } from './namedColors';
-
-// handles #000 or #000000
-// based on this function: https://css-tricks.com/converting-color-spaces-in-javascript/#article-header-id-3
-const hexToRgb = (hex: string): Array => {
- let r: number = 0;
- let g: number = 0;
- let b: number = 0;
-
- // 3 digits - fff
- if (hex.length === 3) {
- r = parseInt("0x" + hex[0] + hex[0]);
- g = parseInt("0x" + hex[1] + hex[1]);
- b = parseInt("0x" + hex[2] + hex[2]);
- } else if (hex.length === 4) {
- // #fff
- r = parseInt("0x" + hex[1] + hex[1]);
- g = parseInt("0x" + hex[2] + hex[2]);
- b = parseInt("0x" + hex[3] + hex[3]);
- } else if (hex.length === 6) {
- // ffffff
- r = parseInt("0x" + hex[0] + hex[1]);
- g = parseInt("0x" + hex[2] + hex[3]);
- b = parseInt("0x" + hex[4] + hex[5]);
- } else if (hex.length === 7) {
- // #ffffff
- r = parseInt("0x" + hex[1] + hex[2]);
- g = parseInt("0x" + hex[3] + hex[4]);
- b = parseInt("0x" + hex[5] + hex[6]);
- }
-
- return [+r, +g, +b];
-};
-
-const hslToRgb = (hsl: string): Array => {
- const hslArray = colorStringToArray(hsl) as Array;
-
- let h = hslArray[0]; // leaving this a string for now
- let s = parseInt(hslArray[1].substr(0, hslArray[1].length - 1)) / 100 || 0;
- let l = parseInt(hslArray[2].substr(0, hslArray[2].length - 1)) / 100 || 0;
-
- let hNum: number;
-
- // Strip label and convert to degrees (if necessary)
- if (h.indexOf("deg") > -1) {
- hNum = parseInt(h.substr(0, h.length - 3));
- } else if (h.indexOf("rad") > -1) {
- hNum = Math.round(parseInt(h.substr(0, h.length - 3)) * (180 / Math.PI));
- } else if (h.indexOf("turn") > -1) {
- hNum = Math.round(parseInt(h.substr(0, h.length - 4)) * 360);
- } else {
- hNum = parseInt(h);
- }
-
- // Keep hue fraction of 360 if ending up over
- if (hNum >= 360) {
- hNum %= 360;
- }
-
- let c = (1 - Math.abs(2 * l - 1)) * s;
- let x = c * (1 - Math.abs(((hNum / 60) % 2) - 1));
- let m = l - c / 2;
- let r = 0;
- let g = 0;
- let b = 0;
- if (0 <= hNum && hNum < 60) {
- r = c;
- g = x;
- b = 0;
- } else if (60 <= hNum && hNum < 120) {
- r = x;
- g = c;
- b = 0;
- } else if (120 <= hNum && hNum < 180) {
- r = 0;
- g = c;
- b = x;
- } else if (180 <= hNum && hNum < 240) {
- r = 0;
- g = x;
- b = c;
- } else if (240 <= hNum && hNum < 300) {
- r = x;
- g = 0;
- b = c;
- } else if (300 <= hNum && hNum < 360) {
- r = c;
- g = 0;
- b = x;
- }
- r = Math.round((r + m) * 255);
- g = Math.round((g + m) * 255);
- b = Math.round((b + m) * 255);
-
- return [+r, +g, +b];
-};
-
-const rgbToRgb = (rgb: string): Array => {
- return colorStringToArray(rgb, true) as Array;
-};
-
-// Looks up rgb for named value
-const namedToRgb = (name: string): Array => {
- let color: Array = [];
-
- for (let index = 0; index < rgbWithNames.length; index++) {
- const colorObj = rgbWithNames[index];
- if (colorObj.name.toLowerCase() === name.toLowerCase()) {
- color = colorObj.rgb;
- break;
- }
- }
-
- return color;
-};
-
-const toRgb = (color: string, colorType: colorTypes): Array => {
- switch (true) {
- case colorType === colorTypes.hex6:
- return hexToRgb(color);
- case colorType === colorTypes.rgb:
- return rgbToRgb(color);
- case colorType === colorTypes.hsl:
- return hslToRgb(color);
- case colorType === colorTypes.named:
- return namedToRgb(color);
- default:
- // assume rgb to rgb
- return rgbToRgb(color);
- }
-};
-
-export { hexToRgb, hslToRgb, rgbToRgb, namedToRgb, toRgb };
diff --git a/src/utils/toRgba.test.ts b/src/utils/toRgba.test.ts
deleted file mode 100644
index 4ef0c92..0000000
--- a/src/utils/toRgba.test.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { colorTypes } from './colorTypes';
-
-import { hex8ToRgba, hslaToRgba, rgbaToRgba, toRgba } from "./toRgba";
-
-describe("Hex8 to RGBA conversion", () => {
- it("correct rgba for black", () => {
- expect(hex8ToRgba("#00000000")[0]).toBe(0);
- expect(hex8ToRgba("#00000000")[1]).toBe(0);
- expect(hex8ToRgba("#00000000")[2]).toBe(0);
- expect(hex8ToRgba("#00000000")[3]).toBe(0);
-
- expect(hex8ToRgba("00000000")[0]).toBe(0);
- expect(hex8ToRgba("00000000")[1]).toBe(0);
- expect(hex8ToRgba("00000000")[2]).toBe(0);
- expect(hex8ToRgba("00000000")[3]).toBe(0);
-
- expect(hex8ToRgba("#0000")[0]).toBe(0);
- expect(hex8ToRgba("#0000")[1]).toBe(0);
- expect(hex8ToRgba("#0000")[2]).toBe(0);
- expect(hex8ToRgba("#0000")[3]).toBe(0);
-
- expect(hex8ToRgba("0000")[0]).toBe(0);
- expect(hex8ToRgba("0000")[1]).toBe(0);
- expect(hex8ToRgba("0000")[2]).toBe(0);
- expect(hex8ToRgba("0000")[3]).toBe(0);
- });
-
- it("correct rgba for white", () => {
- expect(hex8ToRgba("#ffffff00")[0]).toBe(255);
- expect(hex8ToRgba("#ffffff00")[1]).toBe(255);
- expect(hex8ToRgba("#ffffff00")[2]).toBe(255);
- expect(hex8ToRgba("#ffffff00")[3]).toBe(0);
-
- expect(hex8ToRgba("ffffff00")[0]).toBe(255);
- expect(hex8ToRgba("ffffff00")[1]).toBe(255);
- expect(hex8ToRgba("ffffff00")[2]).toBe(255);
- expect(hex8ToRgba("ffffff00")[3]).toBe(0);
-
- expect(hex8ToRgba("#fff0")[0]).toBe(255);
- expect(hex8ToRgba("#fff0")[1]).toBe(255);
- expect(hex8ToRgba("#fff0")[2]).toBe(255);
- expect(hex8ToRgba("#fff0")[3]).toBe(0);
-
- expect(hex8ToRgba("fff0")[0]).toBe(255);
- expect(hex8ToRgba("fff0")[1]).toBe(255);
- expect(hex8ToRgba("fff0")[2]).toBe(255);
- expect(hex8ToRgba("fff0")[3]).toBe(0);
- });
-
- it("correct rgba for hotpink", () => {
- expect(hex8ToRgba("#ff69b400")[0]).toBe(255);
- expect(hex8ToRgba("#ff69b400")[1]).toBe(105);
- expect(hex8ToRgba("#ff69b400")[2]).toBe(180);
- expect(hex8ToRgba("#ff69b400")[3]).toBe(0);
- });
-});
-
-describe("HSLa to RGBa conversion", () => {
- it("correct rgb for black", () => {
- expect(hslaToRgba("hsla(0, 0%, 0%, 1)")[0]).toBe(0);
- expect(hslaToRgba("hsla(0, 0%, 0%, 1)")[1]).toBe(0);
- expect(hslaToRgba("hsla(0, 0%, 0%, 1)")[2]).toBe(0);
- expect(hslaToRgba("hsla(0, 0%, 0%, 1)")[3]).toBe(1);
- });
-
- it("correct rgba for white", () => {
- expect(hslaToRgba("hsla(0, 0%, 100%, 1)")[0]).toBe(255);
- expect(hslaToRgba("hsla(0, 0%, 100%, 1)")[1]).toBe(255);
- expect(hslaToRgba("hsla(0, 0%, 100%, 1)")[2]).toBe(255);
- expect(hslaToRgba("hsla(0, 0%, 100%, 1)")[3]).toBe(1);
- });
-
- it("correct rgba for modern space syntax", () => {
- expect(hslaToRgba("hsla(0 0% 0% / 1)")).toEqual([0, 0, 0, 1]);
- expect(hslaToRgba("hsla(0 0% 100% / 1)")).toEqual([255, 255, 255, 1]);
- expect(hslaToRgba("hsla(0 0% 0% / 0.5)")[3]).toBe(0.5);
- });
-});
-
-describe("rgba to RGBA conversion", () => {
- it("correct rgb for black", () => {
- expect(rgbaToRgba("rgba(0, 0, 0, 1)")[0]).toBe(0);
- expect(rgbaToRgba("rgba(0, 0, 0, 1)")[1]).toBe(0);
- expect(rgbaToRgba("rgba(0, 0, 0, 1)")[2]).toBe(0);
- expect(rgbaToRgba("rgba(0, 0, 0, 1)")[3]).toBe(1);
- });
-
- it("correct rgba for white", () => {
- expect(rgbaToRgba("rgba(255, 255, 255, 1)")[0]).toBe(255);
- expect(rgbaToRgba("rgba(255, 255, 255, 1)")[1]).toBe(255);
- expect(rgbaToRgba("rgba(255, 255, 255, 1)")[2]).toBe(255);
- expect(rgbaToRgba("rgba(255, 255, 255, 1)")[3]).toBe(1);
- });
-
- it("correct rgba for modern space syntax", () => {
- expect(rgbaToRgba("rgba(0 0 0 / 1)")).toEqual([0, 0, 0, 1]);
- expect(rgbaToRgba("rgba(255 255 255 / 1)")).toEqual([255, 255, 255, 1]);
- expect(rgbaToRgba("rgba(255 0 0 / 0.5)")).toEqual([255, 0, 0, 0.5]);
- });
-});
-
-// integration
-describe("To RGBA conversion", () => {
- it("correct rgba for black", () => {
- expect(toRgba("hsla(0, 0%, 0%, 1)", colorTypes.hsla)).toEqual([0, 0, 0, 1]);
- expect(toRgba("rgba(0, 0, 0, 1)", colorTypes.rgba)).toEqual([0, 0, 0, 1]);
- expect(toRgba("#000000ff", colorTypes.hex8)).toEqual([0, 0, 0, 1]);
- expect(toRgba("#00000000", colorTypes.hex8)).toEqual([0, 0, 0, 0]);
- });
-
- it("correct rgba for white", () => {
- expect(toRgba("hsla(0, 0%, 100%, 1)", colorTypes.hsla)).toEqual([255, 255, 255, 1]);
- expect(toRgba("rgba(255, 255, 255, 1)", colorTypes.rgba)).toEqual([255, 255, 255, 1]);
- expect(toRgba("#ffffffff", colorTypes.hex8)).toEqual([255, 255, 255, 1]);
- expect(toRgba("#ffffff00", colorTypes.hex8)).toEqual([255, 255, 255, 0]);
- });
-
- it("get rgba for colors without alpha channels", () => {
- expect(toRgba("hsl(0, 0%, 100%)", colorTypes.hsl)).toEqual([255, 255, 255, 1]);
- expect(toRgba("rgb(255, 255, 255)", colorTypes.rgb)).toEqual([255, 255, 255, 1]);
- expect(toRgba("#ffffff", colorTypes.hex6)).toEqual([255, 255, 255, 1]);
- });
-});
diff --git a/src/utils/toRgba.ts b/src/utils/toRgba.ts
deleted file mode 100644
index 157726f..0000000
--- a/src/utils/toRgba.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-import { colorStringToArray } from './colorStringToArray';
-import { colorTypes } from './colorTypes';
-import { toRgb } from './toRgb';
-import { LCH_to_RGB_array } from './w3conversions';
-
-// handles #0000 or #00000000
-// based on this function: https://css-tricks.com/converting-color-spaces-in-javascript/#article-header-id-3
-const hex8ToRgba = (hex: string) => {
- let r: string | number = 0;
- let g: string | number = 0;
- let b: string | number = 0;
- let a: string | number = 1;
-
- if (hex.length === 4) {
- r = "0x" + hex[0] + hex[0];
- g = "0x" + hex[1] + hex[1];
- b = "0x" + hex[2] + hex[2];
- a = "0x" + hex[3] + hex[3];
- } else if (hex.length === 5) {
- r = "0x" + hex[1] + hex[1];
- g = "0x" + hex[2] + hex[2];
- b = "0x" + hex[3] + hex[3];
- a = "0x" + hex[4] + hex[4];
- } else if (hex.length === 8) {
- r = "0x" + hex[0] + hex[1];
- g = "0x" + hex[2] + hex[3];
- b = "0x" + hex[4] + hex[5];
- a = "0x" + hex[6] + hex[7];
- } else if (hex.length === 9) {
- r = "0x" + hex[1] + hex[2];
- g = "0x" + hex[3] + hex[4];
- b = "0x" + hex[5] + hex[6];
- a = "0x" + hex[7] + hex[8];
- }
- a = +((a as number) / 255).toFixed(2);
-
- return [+r, +g, +b, +a];
-};
-
-type Deg = number;
-type Rad = number;
-type Turn = number;
-type Hue = Deg | Rad | Turn;
-
-// Converts modern space syntax to legacy comma syntax before parsing.
-// e.g. rgba(255 255 255 / 0.6) → rgba(255, 255, 255, 0.6)
-// hsla(120 100% 50% / 0.8) → hsla(120, 100%, 50%, 0.8)
-const normalizeModernSyntax = (color: string): string => {
- if (color.indexOf(',') > -1) return color;
- const match = color.match(/^(\w+)\((.+)\)$/);
- if (!match) return color;
- const [, fn, content] = match;
- const normalized = content
- .replace(/\s+/g, ', ')
- .replace(/,\s*\/\s*,\s*/, ', ');
- return `${fn}(${normalized})`;
-};
-
-const stringToHue = (input: string): Hue => {
- const inputAsNum = Number(input.substr(0, input.length - 3));
-
- if (input.indexOf("deg") > -1) {
- return inputAsNum;
- } else if (input.indexOf("rad") > -1) {
- return Math.round(inputAsNum * (180 / Math.PI));
- } else if (input.indexOf("turn") > -1) {
- return Math.round(Number(input.substr(0, input.length - 4)) * 360);
- } else {
- return Number(input);
- }
-};
-
-const hslaToRgba = (hslaArg: string): number[] => {
- hslaArg = normalizeModernSyntax(hslaArg);
- const sep: string = hslaArg.indexOf(",") > -1 ? "," : " ";
-
- const hsla: any = hslaArg
- .substr(5)
- .split(")")[0]
- .split(sep);
-
- if (hsla.indexOf("/") > -1) hsla.splice(3, 1);
-
- let h: Hue = stringToHue(hsla[0]);
- let s = parseInt(hsla[1].substr(0, hsla[1].length - 1)) / 100;
- let l = parseInt(hsla[2].substr(0, hsla[2].length - 1)) / 100;
- let a = hsla[3];
-
- // Keep hue fraction of 360 if ending up over
- if (h >= 360) {
- h %= 360;
- }
-
- let c = (1 - Math.abs(2 * l - 1)) * s,
- x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
- m = l - c / 2,
- r = 0,
- g = 0,
- b = 0;
- if (0 <= h && h < 60) {
- r = c;
- g = x;
- b = 0;
- } else if (60 <= h && h < 120) {
- r = x;
- g = c;
- b = 0;
- } else if (120 <= h && h < 180) {
- r = 0;
- g = c;
- b = x;
- } else if (180 <= h && h < 240) {
- r = 0;
- g = x;
- b = c;
- } else if (240 <= h && h < 300) {
- r = x;
- g = 0;
- b = c;
- } else if (300 <= h && h < 360) {
- r = c;
- g = 0;
- b = x;
- }
- r = Math.round((r + m) * 255);
- g = Math.round((g + m) * 255);
- b = Math.round((b + m) * 255);
-
- return [+r, +g, +b, +a];
-};
-
-const lchToRgba = (lch: string): Array => {
- const lchArray = colorStringToArray(lch) as Array
-
- const l = lchArray[0].replace("%","");
- const c = lchArray[1];
- const h = lchArray[2];
-
- const rgbaArray = LCH_to_RGB_array(+l, +c, +h);
-
- // if we have an alpha value, else supply 1
- const alpha: string = lchArray[3] === '/' ? (parseInt(lchArray[4].replace("%", "")) / 100).toFixed(2) : '1';
- rgbaArray.push(parseFloat(alpha));
- return rgbaArray;
-}
-
-const rgbaToRgba = (color: string): Array => {
- return colorStringToArray(normalizeModernSyntax(color), true, 5) as Array;
-};
-
-const toRgba = (color: string, colorType: colorTypes) => {
- switch (true) {
- case colorType === colorTypes.hex6:
- return toRgb(color, colorType).concat([1]);
- case colorType === colorTypes.hex8:
- return hex8ToRgba(color);
- case colorType === colorTypes.rgba:
- return rgbaToRgba(color);
- case colorType === colorTypes.rgb:
- return toRgb(color, colorType).concat([1]);
- case colorType === colorTypes.hsla:
- return hslaToRgba(color);
- case colorType === colorTypes.hsl:
- return toRgb(color, colorType).concat([1]);
- case colorType === colorTypes.lch:
- return lchToRgba(color);
- case colorType === colorTypes.named:
- return toRgb(color, colorType).concat([1]);
- default:
- return toRgb(color, colorType).concat([1]);
- }
-};
-
-export { hex8ToRgba, hslaToRgba, lchToRgba, normalizeModernSyntax, rgbaToRgba, toRgba };
diff --git a/src/utils/translatedColor.test.ts b/src/utils/translatedColor.test.ts
index 2b741ec..86f0726 100644
--- a/src/utils/translatedColor.test.ts
+++ b/src/utils/translatedColor.test.ts
@@ -31,4 +31,32 @@ describe('translatedColor', () => {
expect(translatedColor(`lch(54.291% 106.837 40.858 / 50%)`, colorTypes.lch, colorTypes.hex8)).toBe('#ff000080');
expect(translatedColor(`red`, colorTypes.named, colorTypes.hex6)).toBe('#ff0000');
});
+
+ it('Should translate hex without # prefix', () => {
+ expect(translatedColor(`fff`, colorTypes.hex6, colorTypes.rgb)).toBe(`rgb(255 255 255)`);
+ expect(translatedColor(`ff0000`, colorTypes.hex6, colorTypes.named)).toBe(`Red`);
+ expect(translatedColor(`ff000080`, colorTypes.hex8, colorTypes.rgba)).toBe(`rgba(255 0 0 / 0.5019607843137255)`);
+ });
+
+ it('Should translate oklch to other formats', () => {
+ expect(translatedColor(`oklch(62.8% 0.258 29.234)`, colorTypes.oklch, colorTypes.hex6)).toBe('#ff0000');
+ expect(translatedColor(`oklch(100% 0 0)`, colorTypes.oklch, colorTypes.hex6)).toBe('#ffffff');
+ expect(translatedColor(`oklch(0% 0 0)`, colorTypes.oklch, colorTypes.hex6)).toBe('#000000');
+ expect(translatedColor(`oklch(62.8% 0.258 29.234 / 0.5)`, colorTypes.oklch, colorTypes.rgba)).toBeTruthy();
+ });
+
+ it('Should translate from oklch to oklch unchanged', () => {
+ expect(translatedColor(`oklch(62.8% 0.258 29.234)`, colorTypes.oklch, colorTypes.oklch)).toBe(`oklch(62.8% 0.258 29.234)`);
+ });
+
+ it('Should translate display-p3 to other formats', () => {
+ expect(translatedColor(`color(display-p3 1 0 0)`, colorTypes.p3, colorTypes.hex6)).toBe('#ff0b0c');
+ expect(translatedColor(`color(display-p3 1 1 1)`, colorTypes.p3, colorTypes.rgb)).toBe('rgb(255 255 255)');
+ expect(translatedColor(`color(display-p3 0 0 0)`, colorTypes.p3, colorTypes.hex6)).toBe('#000000');
+ expect(translatedColor(`color(display-p3 1 0 0 / 0.5)`, colorTypes.p3, colorTypes.rgba)).toBeTruthy();
+ });
+
+ it('Should translate from display-p3 to display-p3 unchanged', () => {
+ expect(translatedColor(`color(display-p3 1 0 0)`, colorTypes.p3, colorTypes.p3)).toBe(`color(display-p3 1 0 0)`);
+ });
});
diff --git a/src/utils/translatedColor.ts b/src/utils/translatedColor.ts
index cf90a92..f30a90f 100644
--- a/src/utils/translatedColor.ts
+++ b/src/utils/translatedColor.ts
@@ -1,218 +1,110 @@
+import Color from 'colorjs.io';
import { colorTypes } from './colorTypes';
-import { hexToRgb, rgbToRgb, hslToRgb, namedToRgb } from './toRgb';
-import { rgbaToRgba, hex8ToRgba, hslaToRgba, lchToRgba } from './toRgba';
-import { rgbToHex, hslToHex, rgbArrayToHex } from './toHex';
-import { rgbaArrayToHex8, rgbaToHex8, hslaToHex8 } from './toHex8';
-import { rgbToHsl, hex6ToHsl, hslToHsl, rgbArrayToHsl } from './toHsl';
-import { rgbaArrayToHsla, rgbaToHsla, hex8ToHsla } from './toHsla';
-import { hex6ToLch, hex8ToLch, hslToLch, hslaToLch, rgbaToLch, rgbToLch } from './toLch';
-import { rgbToNamed, rgbaToNamed } from './toNamed';
import { calculateOverlay } from './calculateOverlay';
-import { rgb_array_to_LCH } from './w3conversions';
-import { formatColor, formatHex6AsHex8 } from './formatColor';
+import { rgbToNamed, rgbaToNamed } from './toNamed';
const translatedColor = (
color: string,
startingColorType: colorTypes,
- targetColorType: colorTypes): string => {
-
- switch(true) {
- // Hex 6
- case startingColorType === colorTypes.hex6:
- case startingColorType === colorTypes.picker:
- switch(true) {
- // because pickers use hex 6, we need to include these both
- // in their own case
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return color;
- case targetColorType === colorTypes.hex8:
- return formatHex6AsHex8(color);
- case targetColorType === colorTypes.rgb:
- return formatColor(hexToRgb(color), colorTypes.rgb);
- case targetColorType === colorTypes.rgba:
- return formatColor(hexToRgb(color), colorTypes.rgba);
- case targetColorType === colorTypes.hsl:
- return formatColor(hex6ToHsl(color), colorTypes.hsl);
- case targetColorType === colorTypes.hsla:
- return formatColor(hex6ToHsl(color), colorTypes.hsla);
- case targetColorType === colorTypes.lch:
- return formatColor(hex6ToLch(color), colorTypes.lch);
- case targetColorType === colorTypes.named:
- return rgbToNamed(hexToRgb(color));
- default:
- break;
- }
- break;
- // Hex 8
- case startingColorType === colorTypes.hex8:
- const hex8AsRgbaArray = hex8ToRgba(color);
- const hex8Overlay = calculateOverlay(hex8AsRgbaArray);
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return rgbArrayToHex(hex8Overlay);
- case targetColorType === colorTypes.rgb:
- return formatColor(hex8Overlay, colorTypes.rgb);
- case targetColorType === colorTypes.rgba:
- return formatColor(hex8AsRgbaArray, colorTypes.rgba);
- case targetColorType === colorTypes.hsl:
- return formatColor(rgbArrayToHsl(hex8Overlay), colorTypes.hsl);
- case targetColorType === colorTypes.hsla:
- return formatColor(hex8ToHsla(color), colorTypes.hsla);
- case targetColorType === colorTypes.lch:
- return formatColor(hex8ToLch(color), colorTypes.lch);
- case targetColorType === colorTypes.named:
- return rgbaToNamed(hex8AsRgbaArray);
- default:
- break;
- }
- break;
- // RGB
- case startingColorType === colorTypes.rgb:
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return rgbToHex(color);
- case targetColorType === colorTypes.hex8:
- return formatHex6AsHex8(rgbToHex(color));
- case targetColorType === colorTypes.rgba:
- return formatColor(rgbToRgb(color), colorTypes.rgba);
- case targetColorType === colorTypes.hsl:
- return formatColor(rgbToHsl(color), colorTypes.hsl);
- case targetColorType === colorTypes.hsla:
- return formatColor(rgbToHsl(color), colorTypes.hsla);
- case targetColorType === colorTypes.lch:
- return formatColor(rgbToLch(color), colorTypes.lch);
- case targetColorType === colorTypes.named:
- return rgbToNamed(rgbToRgb(color));
- default:
- break;
- }
- break;
- // RGBA
- case startingColorType === colorTypes.rgba:
- const rgbaAsRgbaArray = rgbaToRgba(color);
- const rgbaOverlay = calculateOverlay(rgbaAsRgbaArray);
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return rgbArrayToHex(rgbaOverlay);
- case targetColorType === colorTypes.rgba:
- return formatColor(rgbaAsRgbaArray, colorTypes.rgba);
- case targetColorType === colorTypes.hex8:
- return rgbaToHex8(color);
- case targetColorType === colorTypes.rgb:
- return formatColor(rgbaOverlay, colorTypes.rgb);
- case targetColorType === colorTypes.hsl:
- return formatColor(rgbArrayToHsl(rgbaOverlay), colorTypes.hsl);
- case targetColorType === colorTypes.hsla:
- return formatColor(rgbaToHsla(color), colorTypes.hsla);
- case targetColorType === colorTypes.lch:
- return formatColor(rgbaToLch(color), colorTypes.lch);
- case targetColorType === colorTypes.named:
- return rgbaToNamed(rgbaAsRgbaArray);
- default:
- break;
- }
- break;
- // HSL
- case startingColorType === colorTypes.hsl:
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return hslToHex(color);
- case targetColorType === colorTypes.hex8:
- return formatHex6AsHex8(hslToHex(color));
- case targetColorType === colorTypes.rgb:
- return formatColor(hslToRgb(color), colorTypes.rgb);
- case targetColorType === colorTypes.rgba:
- return formatColor(hslToRgb(color), colorTypes.rgba);
- case targetColorType === colorTypes.hsla:
- return formatColor(hslToHsl(color), colorTypes.hsla);
- case targetColorType === colorTypes.lch:
- return formatColor(hslToLch(color), colorTypes.lch);
- case targetColorType === colorTypes.named:
- return rgbToNamed(hslToRgb(color));
- default:
- break;
- }
- break;
- // HSLA
- case startingColorType === colorTypes.hsla:
- const hslaAsRgbaArray = hslaToRgba(color);
- const hslaOverlay = calculateOverlay(hslaAsRgbaArray)
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return rgbArrayToHex(hslaOverlay);
- case targetColorType === colorTypes.hex8:
- return hslaToHex8(color);
- case targetColorType === colorTypes.rgb:
- return formatColor(hslaOverlay, colorTypes.rgb);
- case targetColorType === colorTypes.rgba:
- return formatColor(hslaAsRgbaArray, colorTypes.rgba);
- case targetColorType === colorTypes.hsl:
- return formatColor(rgbArrayToHsl(hslaOverlay), colorTypes.hsl);
- case targetColorType === colorTypes.lch:
- return formatColor(hslaToLch(color), colorTypes.lch);
- case targetColorType === colorTypes.named:
- return rgbaToNamed(hslaAsRgbaArray);
- default:
- break;
- }
- break;
- // LCH
- case startingColorType === colorTypes.lch:
- const lchAsRgbaArray = lchToRgba(color);
- const lchOverlay = calculateOverlay(lchAsRgbaArray)
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return rgbArrayToHex(lchOverlay);
- case targetColorType === colorTypes.hex8:
- return rgbaArrayToHex8(lchAsRgbaArray);
- case targetColorType === colorTypes.rgb:
- return formatColor(lchOverlay, colorTypes.rgb);
- case targetColorType === colorTypes.rgba:
- return formatColor(lchAsRgbaArray, colorTypes.rgba);
- case targetColorType === colorTypes.hsl:
- return formatColor(rgbArrayToHsl(lchOverlay), colorTypes.hsl);
- case targetColorType === colorTypes.hsla:
- return formatColor(rgbaArrayToHsla(lchAsRgbaArray), colorTypes.hsla);
- case targetColorType === colorTypes.named:
- return rgbaToNamed(lchAsRgbaArray);
- default:
- break;
- }
- break;
- // Named
- case startingColorType === colorTypes.named:
- const namedAsRgbArray = namedToRgb(color);
- switch(true) {
- case targetColorType === colorTypes.hex6:
- case targetColorType === colorTypes.picker:
- return rgbArrayToHex(namedAsRgbArray);
- case targetColorType === colorTypes.hex8:
- return formatHex6AsHex8(rgbArrayToHex(namedAsRgbArray));
- case targetColorType === colorTypes.rgb:
- return formatColor(namedAsRgbArray, colorTypes.rgb);
- case targetColorType === colorTypes.rgba:
- return formatColor(namedAsRgbArray, colorTypes.rgba);
- case targetColorType === colorTypes.hsl:
- return formatColor(rgbArrayToHsl(namedAsRgbArray), colorTypes.hsl);
- case targetColorType === colorTypes.hsla:
- return formatColor(rgbArrayToHsl(namedAsRgbArray), colorTypes.hsla);
- case targetColorType === colorTypes.lch:
- return formatColor(rgb_array_to_LCH(namedAsRgbArray), colorTypes.lch);
- default:
- break;
- }
- break;
+ targetColorType: colorTypes
+): string => {
+ if (startingColorType === targetColorType) {
+ return color;
+ }
+
+ const hexTypes = [colorTypes.hex6, colorTypes.hex8, colorTypes.picker];
+ const normalizedColor =
+ hexTypes.includes(startingColorType) && !color.startsWith('#')
+ ? `#${color}`
+ : color;
+
+ let parsed: Color;
+ try {
+ parsed = new Color(normalizedColor);
+ } catch {
+ return 'none';
}
- return `none`;
-}
+ // For alpha-bearing types, flatten to RGB on white before converting
+ // to formats that don't carry alpha (hex6, rgb, hsl, named)
+ const hasAlpha = parsed.alpha < 1;
+ const needsOverlay =
+ hasAlpha &&
+ [colorTypes.hex6, colorTypes.picker, colorTypes.rgb, colorTypes.hsl, colorTypes.named].includes(targetColorType);
+
+ const srgb = parsed.toGamut({space: 'srgb'}).to('srgb');
+ const [r, g, b] = srgb.coords.map((v: number) => Math.round(v * 255));
+ const a = parsed.alpha;
+
+ const overlaid = needsOverlay ? calculateOverlay([r, g, b, a]) : null;
+
+ switch (targetColorType) {
+ case colorTypes.hex6:
+ case colorTypes.picker: {
+ const [or, og, ob] = overlaid ?? [r, g, b];
+ return `#${[or, og, ob].map(v => v.toString(16).padStart(2, '0')).join('')}`;
+ }
+
+ case colorTypes.hex8: {
+ const alpha255 = Math.round(a * 255);
+ return `#${[r, g, b, alpha255].map(v => v.toString(16).padStart(2, '0')).join('')}`;
+ }
+ case colorTypes.rgb: {
+ const [or, og, ob] = overlaid ?? [r, g, b];
+ return `rgb(${or} ${og} ${ob})`;
+ }
+
+ case colorTypes.rgba:
+ return `rgba(${r} ${g} ${b} / ${a})`;
+
+ case colorTypes.hsl: {
+ const [or, og, ob] = overlaid ?? [r, g, b];
+ const flat = new Color(`srgb`, [or / 255, og / 255, ob / 255]);
+ const hsl = flat.to('hsl');
+ const [h, s, l] = hsl.coords.map((v: number | null) => Math.round(v ?? 0));
+ return `hsl(${h} ${s}% ${l}%)`;
+ }
+
+ case colorTypes.hsla: {
+ const hsl = srgb.to('hsl');
+ const [h, s, l] = hsl.coords.map((v: number | null) => Math.round(v ?? 0));
+ return `hsla(${h} ${s}% ${l}% / ${a})`;
+ }
+
+ case colorTypes.lch: {
+ const lch = parsed.to('lch');
+ const [l, c, h] = lch.coords.map((v: number | null) => +((v ?? 0).toFixed(2)));
+ const alphaStr = a < 1 ? ` / ${a}` : '';
+ return `lch(${l}% ${c} ${h}${alphaStr})`;
+ }
+
+ case colorTypes.oklch: {
+ const oklch = parsed.to('oklch');
+ const coords = oklch.coords;
+ const l = +((coords[0] ?? 0) * 100).toFixed(2);
+ const c = +((coords[1] ?? 0).toFixed(2));
+ const h = +((coords[2] ?? 0).toFixed(2));
+ const alphaStr = a < 1 ? ` / ${a}` : '';
+ return `oklch(${l}% ${c} ${h}${alphaStr})`;
+ }
+
+ case colorTypes.p3: {
+ const p3 = parsed.to('p3');
+ const [pr, pg, pb] = p3.coords.map((v: number | null) => +((v ?? 0).toFixed(2)));
+ const alphaStr = a < 1 ? ` / ${a}` : '';
+ return `color(display-p3 ${pr} ${pg} ${pb}${alphaStr})`;
+ }
+
+ case colorTypes.named: {
+ const [or, og, ob] = overlaid ?? [r, g, b];
+ if (a === 1) return rgbToNamed([or, og, ob]);
+ return rgbaToNamed([r, g, b, a]);
+ }
+
+ default:
+ return 'none';
+ }
+};
-export { translatedColor }
+export { translatedColor };
diff --git a/src/utils/typeOfColor.ts b/src/utils/typeOfColor.ts
index 05de35f..c32c7ff 100644
--- a/src/utils/typeOfColor.ts
+++ b/src/utils/typeOfColor.ts
@@ -24,9 +24,15 @@ const typeOfColor = (color: string): colorTypes => {
case color.indexOf("hsl") === 0 && color.indexOf(")") !== -1:
return colorTypes.hsl;
+ case color.indexOf("oklch") === 0 && color.indexOf(")") !== -1:
+ return colorTypes.oklch;
+
case color.indexOf("lch") === 0 && color.indexOf(")") !== -1:
return colorTypes.lch;
+ case color.indexOf("color(display-p3") === 0 && color.indexOf(")") !== -1:
+ return colorTypes.p3;
+
// converting user input to lowercase so the input
// can be "rebeccapurple" or "RebeccaPurple"
case lowerCaseNamedColors.includes(color.toLowerCase()):
diff --git a/src/utils/w3conversions.ts b/src/utils/w3conversions.ts
deleted file mode 100644
index 1195ccf..0000000
--- a/src/utils/w3conversions.ts
+++ /dev/null
@@ -1,297 +0,0 @@
-// @ts-nocheck
-
-// taken from
-// https://drafts.csswg.org/css-color-4/multiply-matrices.js
-// https://drafts.csswg.org/css-color-4/conversions.js
-// https://drafts.csswg.org/css-color-4/utilities.js
-// https://github.com/LeaVerou/css.land/blob/master/lch/lch.js
-
-/**
- * Simple matrix (and vector) multiplication
- * Warning: No error handling for incompatible dimensions!
- * @author Lea Verou 2020 MIT License
- */
-// A is m x n. B is n x p. product is m x p.
-function multiplyMatrices(A, B) {
- let m = A.length;
-
- if (!Array.isArray(A[0])) {
- // A is vector, convert to [[a, b, c, ...]]
- A = [A];
- }
-
- if (!Array.isArray(B[0])) {
- // B is vector, convert to [[a], [b], [c], ...]]
- B = B.map(x => [x]);
- }
-
- let p = B[0].length;
- let B_cols = B[0].map((_, i) => B.map(x => x[i])); // transpose B
- let product = A.map(row => B_cols.map(col => {
- if (!Array.isArray(row)) {
- return col.reduce((a, c) => a + c * row, 0);
- }
-
- return row.reduce((a, c, i) => a + c * (col[i] || 0), 0);
- }));
-
- if (m === 1) {
- product = product[0]; // Avoid [[a, b, c, ...]]
- }
-
- if (p === 1) {
- return product.map(x => x[0]); // Avoid [[a], [b], [c], ...]]
- }
-
- return product;
-}
-
-// Sample code for color conversions
-// Conversion can also be done using ICC profiles and a Color Management System
-// For clarity, a library is used for matrix multiplication (multiply-matrices.js)
-
-// standard white points, defined by 4-figure CIE x,y chromaticities
-const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
-
-// sRGB-related functions
-
-function lin_sRGB(RGB) {
- // convert an array of sRGB values
- // where in-gamut values are in the range [0 - 1]
- // to linear light (un-companded) form.
- // https://en.wikipedia.org/wiki/SRGB
- // Extended transfer function:
- // for negative values, linear portion is extended on reflection of axis,
- // then reflected power function is used.
- return RGB.map(function (val) {
- let sign = val < 0? -1 : 1;
- let abs = Math.abs(val);
-
- if (abs < 0.04045) {
- return val / 12.92;
- }
-
- return sign * (Math.pow((abs + 0.055) / 1.055, 2.4));
- });
-}
-
-function gam_sRGB(RGB) {
- // convert an array of linear-light sRGB values in the range 0.0-1.0
- // to gamma corrected form
- // https://en.wikipedia.org/wiki/SRGB
- // Extended transfer function:
- // For negative values, linear portion extends on reflection
- // of axis, then uses reflected pow below that
- return RGB.map(function (val) {
- let sign = val < 0? -1 : 1;
- let abs = Math.abs(val);
-
- if (abs > 0.0031308) {
- return sign * (1.055 * Math.pow(abs, 1/2.4) - 0.055);
- }
-
- return 12.92 * val;
- });
-}
-
-function lin_sRGB_to_XYZ(rgb) {
- // convert an array of linear-light sRGB values to CIE XYZ
- // using sRGB's own white, D65 (no chromatic adaptation)
-
- var M = [
- [ 0.41239079926595934, 0.357584339383878, 0.1804807884018343 ],
- [ 0.21263900587151027, 0.715168678767756, 0.07219231536073371 ],
- [ 0.01933081871559182, 0.11919477979462598, 0.9505321522496607 ]
- ];
- return multiplyMatrices(M, rgb);
-}
-
-function XYZ_to_lin_sRGB(XYZ) {
- // convert XYZ to linear-light sRGB
-
- var M = [
- [ 3.2409699419045226, -1.537383177570094, -0.4986107602930034 ],
- [ -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 ],
- [ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786 ]
- ];
-
- return multiplyMatrices(M, XYZ);
-}
-
-// Chromatic adaptation
-
-function D65_to_D50(XYZ) {
- // Bradford chromatic adaptation from D65 to D50
- // The matrix below is the result of three operations:
- // - convert from XYZ to retinal cone domain
- // - scale components from one reference white to another
- // - convert back to XYZ
- // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
- var M = [
- [ 1.0479298208405488, 0.022946793341019088, -0.05019222954313557 ],
- [ 0.029627815688159344, 0.990434484573249, -0.01707382502938514 ],
- [ -0.009243058152591178, 0.015055144896577895, 0.7518742899580008 ]
- ];
-
- return multiplyMatrices(M, XYZ);
-}
-
-function D50_to_D65(XYZ) {
- // Bradford chromatic adaptation from D50 to D65
- var M = [
- [ 0.9554734527042182, -0.023098536874261423, 0.0632593086610217 ],
- [ -0.028369706963208136, 1.0099954580058226, 0.021041398966943008 ],
- [ 0.012314001688319899, -0.020507696433477912, 1.3303659366080753 ]
- ];
-
- return multiplyMatrices(M, XYZ);
-}
-
-// CIE Lab and LCH
-
-function XYZ_to_Lab(XYZ) {
- // Assuming XYZ is relative to D50, convert to CIE Lab
- // from CIE standard, which now defines these as a rational fraction
- var ε = 216/24389; // 6^3/29^3
- var κ = 24389/27; // 29^3/3^3
-
- // compute xyz, which is XYZ scaled relative to reference white
- var xyz = XYZ.map((value, i) => value / D50[i]);
-
- // now compute f
- var f = xyz.map(value => value > ε ? Math.cbrt(value) : (κ * value + 16)/116);
-
- return [
- (116 * f[1]) - 16, // L
- 500 * (f[0] - f[1]), // a
- 200 * (f[1] - f[2]) // b
- ];
- // L in range [0,100]. For use in CSS, add a percent
-}
-
-function Lab_to_XYZ(Lab) {
- // Convert Lab to D50-adapted XYZ
- // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
- var κ = 24389/27; // 29^3/3^3
- var ε = 216/24389; // 6^3/29^3
- var f = [];
-
- // compute f, starting with the luminance-related term
- f[1] = (Lab[0] + 16)/116;
- f[0] = Lab[1]/500 + f[1];
- f[2] = f[1] - Lab[2]/200;
-
- // compute xyz
- var xyz = [
- Math.pow(f[0],3) > ε ? Math.pow(f[0],3) : (116*f[0]-16)/κ,
- Lab[0] > κ * ε ? Math.pow((Lab[0]+16)/116,3) : Lab[0]/κ,
- Math.pow(f[2],3) > ε ? Math.pow(f[2],3) : (116*f[2]-16)/κ
- ];
-
- // Compute XYZ by scaling xyz by reference white
- return xyz.map((value, i) => value * D50[i]);
-}
-
-function Lab_to_LCH(Lab) {
- // Convert to polar form
- var hue = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI;
- return [
- Lab[0], // L is still L
- Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)), // Chroma
- hue >= 0 ? hue : hue + 360 // Hue, in degrees [0 to 360)
- ];
-}
-
-function LCH_to_Lab(LCH) {
- // Convert from polar form
- return [
- LCH[0], // L is still L
- LCH[1] * Math.cos(LCH[2] * Math.PI / 180), // a
- LCH[1] * Math.sin(LCH[2] * Math.PI / 180) // b
- ];
-}
-
-// Premultiplied alpha conversions
-
-// utility functions for color conversions
-// needs conversions.js
-
-function sRGB_to_LCH(RGB) {
- // convert an array of gamma-corrected sRGB values
- // in the 0.0 to 1.0 range
- // to linear-light sRGB, then to CIE XYZ,
- // then adapt from D65 to D50,
- // then convert XYZ to CIE Lab
- // and finally, convert to CIE LCH
-
- return Lab_to_LCH(XYZ_to_Lab(D65_to_D50(lin_sRGB_to_XYZ(lin_sRGB(RGB)))));
-}
-
-function LCH_to_sRGB(LCH) {
- // convert an array of CIE LCH values
- // to CIE Lab, and then to XYZ,
- // adapt from D50 to D65,
- // then convert XYZ to linear-light sRGB
- // and finally to gamma corrected sRGB
- // for in-gamut colors, components are in the 0.0 to 1.0 range
- // out of gamut colors may have negative components
- // or components greater than 1.0
- // so check for that :)
-
- return gam_sRGB(XYZ_to_lin_sRGB(D50_to_D65(Lab_to_XYZ(LCH_to_Lab(LCH)))));
-}
-
-function LCH_to_RGB_array(l, c, h) {
- [l, c, h] = force_into_gamut(l, c, h, isLCH_within_sRGB);
-
- let rgbArray = [];
-
- LCH_to_sRGB([+l, +c, +h]).map(x => {
- return rgbArray.push(parseInt(Math.round(x * 255)));
- })
-
- return rgbArray;
-}
-
-function force_into_gamut(l, c, h, isLCH_within) {
- // Moves an lch color into the sRGB gamut
- // by holding the l and h steady,
- // and adjusting the c via binary-search
- // until the color is on the sRGB boundary.
- if (isLCH_within(l, c, h)) {
- return [l, c, h];
- }
-
- let hiC = c;
- let loC = 0;
- const ε = .0001;
- c /= 2;
-
- // .0001 chosen fairly arbitrarily as "close enough"
- while (hiC - loC > ε) {
- if (isLCH_within(l, c, h)) {
- loC = c;
- }
- else {
- hiC = c;
- }
- c = (hiC + loC)/2;
- }
-
- return [l, c, h];
-}
-
-function isLCH_within_sRGB(l, c, h) {
- var rgb = LCH_to_sRGB([+l, +c, +h]);
- const ε = .000005;
- return rgb.reduce((a, b) => a && b >= (0 - ε) && b <= (1 + ε), true);
-}
-
-const rgb_array_to_LCH = (rgb: Array): Array => {
- let params = rgb.map((x, i) => i < 3? x/255 : x);
- var lch = sRGB_to_LCH(params.slice(0, 3));
-
- return lch.map((x) => parseFloat(x.toFixed(3)));
-}
-
-export { LCH_to_RGB_array, isLCH_within_sRGB, rgb_array_to_LCH }
diff --git a/yarn.lock b/yarn.lock
index 68e01ac..303718d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -340,6 +340,11 @@ chai@^6.2.2:
resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e"
integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==
+colorjs.io@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.6.1.tgz#5e9ca77c9cdd070ae009e008f7e3eb47985c1855"
+ integrity sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==
+
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"