diff --git a/packages/color/README.md b/packages/color/README.md
index 321df95047..c20c75eb23 100644
--- a/packages/color/README.md
+++ b/packages/color/README.md
@@ -43,6 +43,7 @@ Fast color space conversions (any direction) between:
- HSLA (float4)
- HSVA (float4)
- Int32 (uint32, `0xaarrggbb`)
+- [Oklab](https://bottosson.github.io/posts/oklab/) (float4)
- RGBA (float4)
- XYZA (float4, aka CIE 1931)
- YCbCr (float4)
@@ -337,7 +338,7 @@ yarn add @thi.ng/color
```
-Package sizes (gzipped, pre-treeshake): ESM: 8.42 KB / CJS: 8.85 KB / UMD: 8.31 KB
+Package sizes (gzipped, pre-treeshake): ESM: 8.94 KB / CJS: 9.39 KB / UMD: 8.81 KB
## Dependencies
diff --git a/packages/color/src/api.ts b/packages/color/src/api.ts
index 360eaef342..4ea70c1050 100644
--- a/packages/color/src/api.ts
+++ b/packages/color/src/api.ts
@@ -3,13 +3,14 @@ import type { IRandom } from "@thi.ng/random";
import type { ReadonlyVec, Vec } from "@thi.ng/vectors";
export type ColorMode =
- | "rgb"
+ | "css"
| "hcy"
- | "hsv"
- | "hsl"
| "hsi"
+ | "hsl"
+ | "hsv"
| "int"
- | "css"
+ | "oklab"
+ | "rgb"
| "xyz"
| "ycbcr";
diff --git a/packages/color/src/convert.ts b/packages/color/src/convert.ts
index 9abd2648d7..78e4df283e 100644
--- a/packages/color/src/convert.ts
+++ b/packages/color/src/convert.ts
@@ -18,6 +18,7 @@ import { hsvaHsla } from "./hsva-hsla";
import { hsvaRgba } from "./hsva-rgba";
import { int32Css } from "./int-css";
import { int32Rgba } from "./int-rgba";
+import { oklabRgba } from "./oklab-rgba";
import { parseCss } from "./parse-css";
import { rgbaCss } from "./rgba-css";
import { rgbaHcya } from "./rgba-hcya";
@@ -25,6 +26,7 @@ import { rgbaHsia } from "./rgba-hsia";
import { rgbaHsla } from "./rgba-hsla";
import { rgbaHsva } from "./rgba-hsva";
import { rgbaInt } from "./rgba-int";
+import { rgbaOklab } from "./rgba-oklab";
import { rgbaXyza } from "./rgba-xyza";
import { rgbaYcbcra } from "./rgba-ycbcra";
import { xyzaRgba } from "./xyza-rgba";
@@ -102,6 +104,15 @@ export function asHSVA(col: any, mode?: ColorMode) {
return convert(col, "hsv", mode);
}
+export function asOklab(col: IColor): Color;
+export function asOklab(
+ col: string | number | ReadonlyColor,
+ mode: ColorMode
+): Color;
+export function asOklab(col: any, mode?: ColorMode) {
+ return convert(col, "oklab", mode);
+}
+
export function asXYZA(col: IColor): Color;
export function asXYZA(
col: string | number | ReadonlyColor,
@@ -152,6 +163,7 @@ defConversion("rgb", "css", (x: any) => parseCss(x));
"hsl",
"hsv",
"int",
+ "oklab",
"xyz",
"ycbcr",
]).forEach((id) =>
@@ -160,13 +172,33 @@ defConversion("rgb", "css", (x: any) => parseCss(x));
// Int
-defConversions("int", int32Rgba, "hcy", "hsi", "hsl", "hsv", "xyz", "ycbcr");
+defConversions(
+ "int",
+ int32Rgba,
+ "hcy",
+ "hsi",
+ "hsl",
+ "hsv",
+ "oklab",
+ "xyz",
+ "ycbcr"
+);
defConversion("css", "int", (x: any) => int32Css(x));
// HCYA
-defConversions("hcy", hcyaRgba, "css", "int", "hsl", "hsv", "xyz", "ycbcr");
+defConversions(
+ "hcy",
+ hcyaRgba,
+ "css",
+ "int",
+ "hsl",
+ "hsv",
+ "oklab",
+ "xyz",
+ "ycbcr"
+);
// HSIA
@@ -178,13 +210,14 @@ defConversions(
"hcy",
"hsl",
"hsv",
+ "oklab",
"xyz",
"ycbcr"
);
// HSLA
-defConversions("hsl", hslaRgba, "hcy", "hsi", "int", "xyz", "ycbcr");
+defConversions("hsl", hslaRgba, "hcy", "hsi", "int", "oklab", "xyz", "ycbcr");
defConversion("css", "hsl", (x: any) => hslaCss(x));
@@ -192,12 +225,27 @@ defConversion("hsv", "hsl", (x: any) => hslaHsva([], x));
// HSVA
-defConversions("hsv", hsvaRgba, "hcy", "hsi", "int", "xyz", "ycbcr");
+defConversions("hsv", hsvaRgba, "hcy", "hsi", "int", "oklab", "xyz", "ycbcr");
defConversion("css", "hsv", (x: any) => hsvaCss(x));
defConversion("hsl", "hsv", (x: any) => hsvaHsla([], x));
+// Oklab
+
+defConversions(
+ "oklab",
+ oklabRgba,
+ "css",
+ "hcy",
+ "hsi",
+ "hsl",
+ "hsv",
+ "int",
+ "xyz",
+ "ycbcr"
+);
+
// RGBA
(<[ColorMode, ColorConversion][]>[
@@ -205,6 +253,7 @@ defConversion("hsl", "hsv", (x: any) => hsvaHsla([], x));
["hsi", rgbaHsia],
["hsl", rgbaHsla],
["hsv", rgbaHsva],
+ ["oklab", rgbaOklab],
["xyz", rgbaXyza],
["ycbcr", rgbaYcbcra],
]).forEach(([id, fn]) => defConversion(id, "rgb", (x: any) => fn([], x)));
@@ -224,6 +273,7 @@ defConversions(
"hsl",
"hsv",
"int",
+ "oklab",
"ycbcr"
);
@@ -238,5 +288,6 @@ defConversions(
"hsl",
"hsv",
"int",
+ "oklab",
"xyz"
);
diff --git a/packages/color/src/index.ts b/packages/color/src/index.ts
index f08948cf1c..24cefa451b 100644
--- a/packages/color/src/index.ts
+++ b/packages/color/src/index.ts
@@ -14,6 +14,9 @@ export * from "./hue-rgba";
export * from "./int-css";
export * from "./int-rgba";
export * from "./kelvin-rgba";
+export * from "./oklab";
+export * from "./oklab-css";
+export * from "./oklab-rgba";
export * from "./resolve";
export * from "./rgba-css";
export * from "./rgba-hcva";
@@ -22,6 +25,7 @@ export * from "./rgba-hsia";
export * from "./rgba-hsla";
export * from "./rgba-hsva";
export * from "./rgba-int";
+export * from "./rgba-oklab";
export * from "./rgba-xyza";
export * from "./rgba-ycbcra";
export * from "./srgba";
@@ -50,6 +54,7 @@ export * from "./color-range";
export * from "./cosine-gradients";
export * from "./distance";
export * from "./invert";
+export * from "./lab-lch";
export * from "./luminance";
export * from "./luminance-rgb";
export * from "./mix";
diff --git a/packages/color/src/lab-lch.ts b/packages/color/src/lab-lch.ts
new file mode 100644
index 0000000000..5af5a8e71c
--- /dev/null
+++ b/packages/color/src/lab-lch.ts
@@ -0,0 +1,21 @@
+import { setC4 } from "@thi.ng/vectors";
+import { cossin } from "@thi.ng/math";
+import type { ColorOp } from "./api";
+import { ensureAlpha } from "./internal/ensure-alpha";
+
+export const labLch: ColorOp = (out, src) =>
+ setC4(
+ out || src,
+ src[0],
+ Math.hypot(src[1], src[2]),
+ Math.atan2(src[2], src[1]),
+ ensureAlpha(src[3])
+ );
+
+export const lchLab: ColorOp = (out, src) =>
+ setC4(
+ out || src,
+ src[0],
+ ...(<[number, number]>cossin(src[2], src[1])),
+ ensureAlpha(src[3])
+ );
diff --git a/packages/color/src/oklab-css.ts b/packages/color/src/oklab-css.ts
new file mode 100644
index 0000000000..ab26664fd6
--- /dev/null
+++ b/packages/color/src/oklab-css.ts
@@ -0,0 +1,5 @@
+import type { ReadonlyColor } from "./api";
+import { oklabRgba } from "./oklab-rgba";
+import { rgbaCss } from "./rgba-css";
+
+export const oklabCss = (src: ReadonlyColor) => rgbaCss(oklabRgba([], src));
diff --git a/packages/color/src/oklab-rgba.ts b/packages/color/src/oklab-rgba.ts
new file mode 100644
index 0000000000..8bdad8ff8f
--- /dev/null
+++ b/packages/color/src/oklab-rgba.ts
@@ -0,0 +1,29 @@
+import type { ColorOp } from "./api";
+import { mulV33 } from "./internal/matrix-ops";
+
+const M = [
+ 4.0767245293,
+ -1.2681437731,
+ -0.0041119885,
+ -3.3072168827,
+ 2.6093323231,
+ -0.7034763098,
+ 0.2307590544,
+ -0.341134429,
+ 1.7068625689,
+];
+
+/**
+ * @remarks
+ * Reference: https://bottosson.github.io/posts/oklab/
+ *
+ * @param out
+ * @param src
+ */
+export const oklabRgba: ColorOp = (out, [l, a, b, alpha]) =>
+ mulV33(out, M, [
+ (l + 0.3963377774 * a + 0.2158037573 * b) ** 3,
+ (l - 0.1055613458 * a - 0.0638541728 * b) ** 3,
+ (l - 0.0894841775 * a - 1.291485548 * b) ** 3,
+ alpha,
+ ]);
diff --git a/packages/color/src/oklab.ts b/packages/color/src/oklab.ts
new file mode 100644
index 0000000000..d52671ed5d
--- /dev/null
+++ b/packages/color/src/oklab.ts
@@ -0,0 +1,43 @@
+import { declareIndices, IVector } from "@thi.ng/vectors";
+import type { Color, ColorMode } from "./api";
+import { AColor } from "./internal/acolor";
+import { ensureArgs } from "./internal/ensure-args";
+
+export function oklab(col: Color, offset?: number, stride?: number): Oklab;
+export function oklab(
+ l?: number,
+ a?: number,
+ b?: number,
+ alpha?: number
+): Oklab;
+export function oklab(...args: any[]) {
+ return new Oklab(...ensureArgs(args));
+}
+/**
+ * @remarks
+ * Reference: https://bottosson.github.io/posts/oklab/
+ */
+export class Oklab extends AColor implements IVector {
+ l!: number;
+ a!: number;
+ b!: number;
+ alpha!: number;
+
+ get mode() {
+ return "oklab";
+ }
+
+ copy() {
+ return new Oklab(this.deref());
+ }
+
+ copyView() {
+ return new Oklab(this.buf, this.offset, this.stride);
+ }
+
+ empty() {
+ return new Oklab();
+ }
+}
+
+declareIndices(Oklab.prototype, ["l", "a", "b", "alpha"]);
diff --git a/packages/color/src/rgba-oklab.ts b/packages/color/src/rgba-oklab.ts
new file mode 100644
index 0000000000..50510a5c93
--- /dev/null
+++ b/packages/color/src/rgba-oklab.ts
@@ -0,0 +1,34 @@
+import type { ColorOp } from "./api";
+import { mulV33 } from "./internal/matrix-ops";
+
+const M = [
+ 0.2104542553,
+ 1.9779984951,
+ 0.0259040371,
+ 0.793617785,
+ -2.428592205,
+ 0.7827717662,
+ -0.0040720468,
+ 0.4505937099,
+ -0.808675766,
+];
+
+/**
+ * @remarks
+ * Reference: https://bottosson.github.io/posts/oklab/
+ *
+ * @param out
+ * @param src
+ */
+export const rgbaOklab: ColorOp = (out, [r, g, b, alpha]) =>
+ mulV33(
+ out,
+ M,
+ [
+ Math.cbrt(0.412165612 * r + 0.536275208 * g + 0.0514575653 * b),
+ Math.cbrt(0.211859107 * r + 0.6807189584 * g + 0.107406579 * b),
+ Math.cbrt(0.0883097947 * r + 0.2818474174 * g + 0.6302613616 * b),
+ alpha,
+ ],
+ false
+ );
diff --git a/packages/color/tools/oklab.ts b/packages/color/tools/oklab.ts
new file mode 100644
index 0000000000..53d6bc8953
--- /dev/null
+++ b/packages/color/tools/oklab.ts
@@ -0,0 +1,19 @@
+import { serialize } from "@thi.ng/hiccup";
+import { svg } from "@thi.ng/hiccup-svg";
+import { TAU } from "@thi.ng/math";
+import { map, normRange, push, transduce } from "@thi.ng/transducers";
+import { writeFileSync } from "fs";
+import { lchLab, oklab, Oklab, swatchesH } from "../src";
+
+const cols = transduce(
+ map((t) => oklab(lchLab(null, [0.9, 0.12, t * TAU]))),
+ push(),
+ normRange(100, false)
+);
+
+writeFileSync(
+ "export/oklab.svg",
+ serialize(
+ svg({ width: 500, height: 50, convert: true }, swatchesH(cols, 5, 50))
+ )
+);
diff --git a/packages/color/tpl.readme.md b/packages/color/tpl.readme.md
index b1f9e5e260..d5499a30ba 100644
--- a/packages/color/tpl.readme.md
+++ b/packages/color/tpl.readme.md
@@ -25,6 +25,7 @@ Fast color space conversions (any direction) between:
- HSLA (float4)
- HSVA (float4)
- Int32 (uint32, `0xaarrggbb`)
+- [Oklab](https://bottosson.github.io/posts/oklab/) (float4)
- RGBA (float4)
- XYZA (float4, aka CIE 1931)
- YCbCr (float4)