From 29e1e74a259bc8c73a79ccc77c654649f95c7d8c Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 30 Jan 2021 16:35:32 +0000 Subject: [PATCH] feat(color): split Lab & XYZ types into D50/D65 - add LabD50/D65, add XYZD50/XYZD65 - rename factory fns (labD50/D65, xyzD50/D65) - update/simplify ColorSpec in all color type defs - add tools/limits.ts to sample RGB gamut limits, add to color defs - add TypedColor.clamp(), update defColor() - fix Oklab<>XYZ conversions (use D65 as default) --- packages/color/src/api.ts | 24 +++++++---- packages/color/src/color.ts | 14 ++++--- packages/color/src/convert.ts | 37 ++++++++++++++++- packages/color/src/css/css.ts | 3 +- packages/color/src/css/parse-css.ts | 1 + packages/color/src/defcolor.ts | 46 ++++++++++++--------- packages/color/src/hcy/hcy.ts | 7 +--- packages/color/src/hsi/hsi.ts | 7 +--- packages/color/src/hsl/hsl.ts | 7 +--- packages/color/src/hsv/hsv.ts | 7 +--- packages/color/src/index.ts | 6 ++- packages/color/src/lab/{lab.ts => lab50.ts} | 33 ++++++++------- packages/color/src/lab/lab65.ts | 46 +++++++++++++++++++++ packages/color/src/lch/lch.ts | 13 ++++-- packages/color/src/oklab/oklab-xyz.ts | 2 +- packages/color/src/oklab/oklab.ts | 14 ++++--- packages/color/src/rgb/rgb.ts | 21 +++++----- packages/color/src/srgb/srgb.ts | 7 +--- packages/color/src/xyy/xyy.ts | 15 ++++--- packages/color/src/xyz/wavelength-xyz.ts | 12 ++++-- packages/color/src/xyz/{xyz.ts => xyz50.ts} | 31 ++++++++------ packages/color/src/xyz/xyz65.ts | 43 +++++++++++++++++++ packages/color/src/ycc/ycc.ts | 7 ++-- packages/color/tools/limits.ts | 38 +++++++++++++++++ 24 files changed, 313 insertions(+), 128 deletions(-) rename packages/color/src/lab/{lab.ts => lab50.ts} (57%) create mode 100644 packages/color/src/lab/lab65.ts rename packages/color/src/xyz/{xyz.ts => xyz50.ts} (58%) create mode 100644 packages/color/src/xyz/xyz65.ts create mode 100644 packages/color/tools/limits.ts diff --git a/packages/color/src/api.ts b/packages/color/src/api.ts index 791ed269bb..910b78898b 100644 --- a/packages/color/src/api.ts +++ b/packages/color/src/api.ts @@ -19,13 +19,15 @@ export type ColorMode = | "hsi" | "hsl" | "hsv" - | "lab" + | "lab50" + | "lab65" | "lch" | "oklab" | "rgb" | "srgb" | "xyy" - | "xyz" + | "xyz50" + | "xyz65" | "ycc"; /** @@ -55,17 +57,15 @@ export interface ChannelSpec { * @defaultValue [0,1] */ range?: Range; - /** - * @defaultValue 0 - */ - default?: number; } export interface ColorSpec { mode: M; - channels: Partial>; + channels?: Partial>; order: readonly K[]; - from: Partial> & { rgb: ColorOp }; + from: Partial> & { + rgb: ColorOp; + }; } export interface ColorFactory> { @@ -132,6 +132,14 @@ export interface TypedColor extends IColor, IDeref, IVector { * @param rnd */ randomize(rnd?: IRandom): this; + /** + * Clamps all color channels so that colors is inside RGB gamut. + * + * @remarks + * Note: This is not a 100% guarantee, due to each channel being clamped + * individually based on pre-determined limits. + */ + clamp(): this; /** * Copies `src` into this color's array. * diff --git a/packages/color/src/color.ts b/packages/color/src/color.ts index b9a1ed8f9e..d8e69a37e2 100644 --- a/packages/color/src/color.ts +++ b/packages/color/src/color.ts @@ -3,20 +3,22 @@ import type { Color, ColorFactory, ColorMode, - TypedColor, ParsedColor, + TypedColor, } from "./api"; import { hcy } from "./hcy/hcy"; import { hsi } from "./hsi/hsi"; import { hsl } from "./hsl/hsl"; import { hsv } from "./hsv/hsv"; -import { lab } from "./lab/lab"; +import { labD50 } from "./lab/lab50"; +import { labD65 } from "./lab/lab65"; import { lch } from "./lch/lch"; import { oklab } from "./oklab/oklab"; import { rgb } from "./rgb/rgb"; import { srgb } from "./srgb/srgb"; import { xyy } from "./xyy/xyy"; -import { xyz } from "./xyz/xyz"; +import { xyzD50 } from "./xyz/xyz50"; +import { xyzD65 } from "./xyz/xyz65"; import { ycc } from "./ycc/ycc"; const FACTORIES: Record> = { @@ -24,13 +26,15 @@ const FACTORIES: Record> = { hsi, hsl: hsl, hsv: hsv, - lab, + lab50: labD50, + lab65: labD65, lch, oklab, rgb, srgb, xyy, - xyz, + xyz50: xyzD50, + xyz65: xyzD65, ycc, }; diff --git a/packages/color/src/convert.ts b/packages/color/src/convert.ts index 12be0c02c3..6f67be0894 100644 --- a/packages/color/src/convert.ts +++ b/packages/color/src/convert.ts @@ -1,11 +1,44 @@ import { assert } from "@thi.ng/api"; +import { isArray } from "@thi.ng/checks"; import { unsupported } from "@thi.ng/errors"; -import type { Color, ColorMode, ColorSpec, ReadonlyColor } from "./api"; +import type { + Color, + ColorMode, + ColorOp, + ColorSpec, + ReadonlyColor, +} from "./api"; export const CONVERSIONS: Partial< - Record["from"]> + Record< + ColorMode, + Partial> & { + rgb: ColorOp | [ColorOp, ColorOp]; + } + > > = {}; +/** + * Registers conversions for given {@link ColorSpec}. Called by + * {@link defColor}. + * + * @param spec + * + * @internal + */ +export const defConversions = ( + spec: ColorSpec +) => { + for (let id in spec.from) { + const val = spec.from[id]; + if (isArray(val)) { + const [a, b] = val; + spec.from[id] = (out, src) => b(out, a(out, src)); + } + } + CONVERSIONS[spec.mode] = spec.from; +}; + export const convert = ( res: T | null, src: ReadonlyColor, diff --git a/packages/color/src/css/css.ts b/packages/color/src/css/css.ts index ab5eeb48a2..24bffebd07 100644 --- a/packages/color/src/css/css.ts +++ b/packages/color/src/css/css.ts @@ -13,7 +13,8 @@ import { srgbCss } from "../srgb/srgb-css"; const CSS_CONVERSIONS: Partial>> = { hsl: hslCss, hsv: hsvCss, - lab: labCss, + lab50: labCss, + lab65: labCss, lch: lchCss, rgb: rgbCss, srgb: srgbCss, diff --git a/packages/color/src/css/parse-css.ts b/packages/color/src/css/parse-css.ts index 49863d410a..d45422db6b 100644 --- a/packages/color/src/css/parse-css.ts +++ b/packages/color/src/css/parse-css.ts @@ -72,6 +72,7 @@ export const parseCss = (src: string | IDeref): IParsedColor => { parseAlpha(d), ]); case "lab": + return new ParsedColor("lab50", [ parsePercent(a, false), parseNumber(b) * 0.01, parseNumber(c) * 0.01, diff --git a/packages/color/src/defcolor.ts b/packages/color/src/defcolor.ts index d4f4d528ff..19fe18206d 100644 --- a/packages/color/src/defcolor.ts +++ b/packages/color/src/defcolor.ts @@ -9,6 +9,7 @@ import { illegalArgs } from "@thi.ng/errors"; import { EPS } from "@thi.ng/math"; import type { IRandom } from "@thi.ng/random"; import { + clamp, declareIndices, eqDelta, mapStridedBuffer, @@ -27,7 +28,7 @@ import type { ReadonlyColor, TypedColor, } from "./api"; -import { CONVERSIONS, convert } from "./convert"; +import { convert, defConversions } from "./convert"; import { parseCss } from "./css/parse-css"; import { int32Rgb } from "./int/int-rgba"; import { ensureArgs } from "./internal/ensure-args"; @@ -41,22 +42,25 @@ type $DefColor = { toJSON(): number[]; } & TypedColor<$DefColor>; -const prepareSpec = (id: string, spec?: ChannelSpec): ChannelSpec => ({ - ...(id === "alpha" ? { range: [1, 1], default: 1 } : { range: [0, 1] }), - ...spec, -}); - export const defColor = ( spec: ColorSpec ) => { - const channels = spec.order; - const numChannels = channels.length; - channels.reduce( - (acc, id) => ((acc[id] = prepareSpec(id, spec.channels[id])), acc), - spec.channels - ); - const min = channels.map((id) => spec.channels[id]!.range![0]); - const max = channels.map((id) => spec.channels[id]!.range![1]); + const channels: Partial> = spec.channels || {}; + const order = spec.order; + const numChannels = order.length; + order.reduce((acc, id) => { + acc[id] = { + range: [0, 1], + ...channels[id], + }; + return acc; + }, channels); + const min = order.map((id) => channels[id]!.range![0]); + const max = order.map((id) => channels[id]!.range![1]); + // fix alpha channel for randomize() + const minR = set([], min); + const maxR = set([], max); + minR[numChannels - 1] = 1; const $clazz = class implements TypedColor<$DefColor> { buf: Color; @@ -104,8 +108,11 @@ export const defColor = ( } set(src: ReadonlyColor) { - set(this, src); - return this; + return set(this, src); + } + + clamp() { + return clamp(null, this, min, max); } eqDelta(o: $DefColor, eps = EPS): boolean { @@ -117,11 +124,12 @@ export const defColor = ( } randomize(rnd?: IRandom): this { - return randMinMax(this, min, max, rnd); + return randMinMax(this, minR, maxR, rnd); } }; - declareIndices($clazz.prototype, channels); - CONVERSIONS[spec.mode] = spec.from; + + declareIndices($clazz.prototype, order); + defConversions(spec); const fromColor = (src: ReadonlyColor, mode: ColorMode, xs: any[]): any => { const res = new $clazz(...xs); diff --git a/packages/color/src/hcy/hcy.ts b/packages/color/src/hcy/hcy.ts index cccc5cf3c9..8a2d42c662 100644 --- a/packages/color/src/hcy/hcy.ts +++ b/packages/color/src/hcy/hcy.ts @@ -16,6 +16,7 @@ export declare class HCY implements TypedColor { readonly mode: "hcy"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): HCY; copyView(): HCY; deref(): Color; @@ -28,12 +29,6 @@ export declare class HCY implements TypedColor { export const hcy = >defColor({ mode: "hcy", - channels: { - // h: {}, - // c: {}, - // y: {}, - // alpha: { default: 1 }, - }, order: ["h", "c", "y", "alpha"], from: { rgb: rgbHcy }, }); diff --git a/packages/color/src/hsi/hsi.ts b/packages/color/src/hsi/hsi.ts index bba22898b6..a5734485f6 100644 --- a/packages/color/src/hsi/hsi.ts +++ b/packages/color/src/hsi/hsi.ts @@ -16,6 +16,7 @@ export declare class HSI implements TypedColor { readonly mode: "hsi"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): HSI; copyView(): HSI; deref(): Color; @@ -28,12 +29,6 @@ export declare class HSI implements TypedColor { export const hsi = >defColor({ mode: "hsi", - channels: { - // h: {}, - // s: {}, - // i: {}, - // alpha: { default: 1 }, - }, order: ["h", "s", "i", "alpha"], from: { rgb: rgbHsi }, }); diff --git a/packages/color/src/hsl/hsl.ts b/packages/color/src/hsl/hsl.ts index e0c49f1920..e64c5b3713 100644 --- a/packages/color/src/hsl/hsl.ts +++ b/packages/color/src/hsl/hsl.ts @@ -48,6 +48,7 @@ export declare class HSL implements TypedColor { readonly mode: "hsl"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): HSL; copyView(): HSL; deref(): Color; @@ -60,12 +61,6 @@ export declare class HSL implements TypedColor { export const hsl = >defColor({ mode: "hsl", - channels: { - // h: {}, - // s: {}, - // l: {}, - // alpha: { default: 1 }, - }, order: ["h", "s", "l", "alpha"], from: { rgb: rgbHsl, hsv: hsvHsl }, }); diff --git a/packages/color/src/hsv/hsv.ts b/packages/color/src/hsv/hsv.ts index 1fd0b98b66..4a78d6d3db 100644 --- a/packages/color/src/hsv/hsv.ts +++ b/packages/color/src/hsv/hsv.ts @@ -48,6 +48,7 @@ export declare class HSV implements TypedColor { readonly mode: "hsv"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): HSV; copyView(): HSV; deref(): Color; @@ -60,12 +61,6 @@ export declare class HSV implements TypedColor { export const hsv = >defColor({ mode: "hsv", - channels: { - // h: {}, - // s: {}, - // v: {}, - // alpha: { default: 1 }, - }, order: ["h", "s", "v", "alpha"], from: { rgb: rgbHsv, hsl: hslHsv }, }); diff --git a/packages/color/src/index.ts b/packages/color/src/index.ts index 6e8db11891..6fda974e58 100644 --- a/packages/color/src/index.ts +++ b/packages/color/src/index.ts @@ -34,7 +34,8 @@ export * from "./lab/lab-css"; export * from "./lab/lab-lch"; export * from "./lab/lab-rgb"; export * from "./lab/lab-xyz"; -export * from "./lab/lab"; +export * from "./lab/lab50"; +export * from "./lab/lab65"; export * from "./lch/lch-css"; export * from "./lch/lch"; @@ -90,7 +91,8 @@ export * from "./xyz/xyz-oklab"; export * from "./xyz/xyz-rgb"; export * from "./xyz/xyz-xyy"; export * from "./xyz/xyz-xyz"; -export * from "./xyz/xyz"; +export * from "./xyz/xyz50"; +export * from "./xyz/xyz65"; export * from "./ycc/ycc-rgb"; export * from "./ycc/ycc"; diff --git a/packages/color/src/lab/lab.ts b/packages/color/src/lab/lab50.ts similarity index 57% rename from packages/color/src/lab/lab.ts rename to packages/color/src/lab/lab50.ts index 1c62a56c0b..5103cb1651 100644 --- a/packages/color/src/lab/lab.ts +++ b/packages/color/src/lab/lab50.ts @@ -4,9 +4,10 @@ import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; import { defColor } from "../defcolor"; import { rgbLab } from "../rgb/rgb-lab"; import { xyzLab } from "../xyz/xyz-lab"; +import { xyzXyzD65_50 } from "../xyz/xyz-xyz"; import { lchLab } from "./lab-lch"; -export declare class Lab implements TypedColor { +export declare class LabD50 implements TypedColor { buf: Vec; offset: number; stride: number; @@ -15,29 +16,31 @@ export declare class Lab implements TypedColor { b: number; alpha: number; [id: number]: number; - readonly mode: "lab"; + readonly mode: "lab50"; readonly length: 4; [Symbol.iterator](): Iterator; - copy(): Lab; - copyView(): Lab; + clamp(): this; + copy(): LabD50; + copyView(): LabD50; deref(): Color; - empty(): Lab; - eqDelta(o: Lab, eps?: number): boolean; + empty(): LabD50; + eqDelta(o: LabD50, eps?: number): boolean; randomize(rnd?: IRandom): this; set(src: ReadonlyColor): this; toJSON(): number[]; } -export const lab = >defColor({ - mode: "lab", +export const labD50 = >defColor({ + mode: "lab50", channels: { - // l: {}, - // ranges based on sRGB: - // https://stackoverflow.com/a/19099064 - a: { range: [-0.86185, 0.98254] }, - b: { range: [-1.07863, 0.94482] }, - // alpha: { default: 1 }, + a: { range: [-0.7929, 0.9355] }, + b: { range: [-1.1203, 0.9339] }, }, order: ["l", "a", "b", "alpha"], - from: { rgb: rgbLab, lch: lchLab, xyz: xyzLab }, + from: { + rgb: rgbLab, + lch: lchLab, + xyz50: xyzLab, + xyz65: [xyzXyzD65_50, xyzLab], + }, }); diff --git a/packages/color/src/lab/lab65.ts b/packages/color/src/lab/lab65.ts new file mode 100644 index 0000000000..2425fc3d48 --- /dev/null +++ b/packages/color/src/lab/lab65.ts @@ -0,0 +1,46 @@ +import type { IRandom } from "@thi.ng/random"; +import type { Vec } from "@thi.ng/vectors"; +import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; +import { defColor } from "../defcolor"; +import { rgbLabD65 } from "../rgb/rgb-lab"; +import { xyzLabD65 } from "../xyz/xyz-lab"; +import { xyzXyzD50_65 } from "../xyz/xyz-xyz"; +import { lchLab } from "./lab-lch"; + +export declare class LabD65 implements TypedColor { + buf: Vec; + offset: number; + stride: number; + l: number; + a: number; + b: number; + alpha: number; + [id: number]: number; + readonly mode: "lab65"; + readonly length: 4; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): LabD65; + copyView(): LabD65; + deref(): Color; + empty(): LabD65; + eqDelta(o: LabD65, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; +} + +export const labD65 = >defColor({ + mode: "lab65", + channels: { + a: { range: [-0.8618, 0.9823] }, + b: { range: [-1.0786, 0.9448] }, + }, + order: ["l", "a", "b", "alpha"], + from: { + rgb: rgbLabD65, + lch: lchLab, + xyz50: [xyzXyzD50_65, xyzLabD65], + xyz65: xyzLabD65, + }, +}); diff --git a/packages/color/src/lch/lch.ts b/packages/color/src/lch/lch.ts index 9f07d42a90..80dc2c78b3 100644 --- a/packages/color/src/lch/lch.ts +++ b/packages/color/src/lch/lch.ts @@ -2,7 +2,7 @@ import type { IRandom } from "@thi.ng/random"; import type { Vec } from "@thi.ng/vectors"; import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; import { defColor } from "../defcolor"; -import { labLch, lchLab } from "../lab/lab-lch"; +import { labLch } from "../lab/lab-lch"; import { rgbLab } from "../rgb/rgb-lab"; export declare class LCH implements TypedColor { @@ -17,6 +17,7 @@ export declare class LCH implements TypedColor { readonly mode: "lch"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): LCH; copyView(): LCH; deref(): Color; @@ -31,10 +32,14 @@ export const lch = >defColor({ mode: "lch", channels: { // l: {}, - // c: {}, + c: { range: [0, 1.312] }, // h: {}, - // alpha: { default: 1 }, + // alpha: {}, }, order: ["l", "c", "h", "alpha"], - from: { rgb: (out, src) => labLch(null, rgbLab(out, src)), lch: lchLab }, + from: { + rgb: (out, src) => labLch(null, rgbLab(out, src)), + lab50: labLch, + lab65: labLch, + }, }); diff --git a/packages/color/src/oklab/oklab-xyz.ts b/packages/color/src/oklab/oklab-xyz.ts index d5db3c7413..d318d89ca3 100644 --- a/packages/color/src/oklab/oklab-xyz.ts +++ b/packages/color/src/oklab/oklab-xyz.ts @@ -28,5 +28,5 @@ const M2I = [ -1.2914855378640917, ]; -export const oklabXyz: ColorOp = (out, src) => +export const oklabXyzD65: ColorOp = (out, src) => mulV33(null, M1I, powN3(null, mulV33(out, M2I, src), 3)); diff --git a/packages/color/src/oklab/oklab.ts b/packages/color/src/oklab/oklab.ts index bb6ab41c8f..94ba58e247 100644 --- a/packages/color/src/oklab/oklab.ts +++ b/packages/color/src/oklab/oklab.ts @@ -4,6 +4,7 @@ import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; import { defColor } from "../defcolor"; import { rgbOklab } from "../rgb/rgb-oklab"; import { xyzOklab } from "../xyz/xyz-oklab"; +import { xyzXyzD50_65 } from "../xyz/xyz-xyz"; export declare class Oklab implements TypedColor { buf: Vec; @@ -17,6 +18,7 @@ export declare class Oklab implements TypedColor { readonly mode: "oklab"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): Oklab; copyView(): Oklab; deref(): Color; @@ -30,11 +32,13 @@ export declare class Oklab implements TypedColor { export const oklab = >defColor({ mode: "oklab", channels: { - // l: {}, - a: { range: [-1, 1] }, - b: { range: [-1, 1] }, - // alpha: { default: 1 }, + a: { range: [-0.2339, 0.2763] }, + b: { range: [-0.3116, 0.1985] }, }, order: ["l", "a", "b", "alpha"], - from: { rgb: rgbOklab, xyz: xyzOklab }, + from: { + rgb: rgbOklab, + xyz50: [xyzXyzD50_65, xyzOklab], + xyz65: xyzOklab, + }, }); diff --git a/packages/color/src/rgb/rgb.ts b/packages/color/src/rgb/rgb.ts index 984d40efbe..bba37ef60b 100644 --- a/packages/color/src/rgb/rgb.ts +++ b/packages/color/src/rgb/rgb.ts @@ -6,10 +6,12 @@ import { hcyRgb } from "../hcy/hcy-rgb"; import { hsiRgb } from "../hsi/hsi-rgb"; import { hslRgb } from "../hsl/hsl-rgb"; import { hsvRgb } from "../hsv/hsv-rgb"; -import { labRgb } from "../lab/lab-rgb"; +import { lchLab } from "../lab/lab-lch"; +import { labRgb, labRgbD65 } from "../lab/lab-rgb"; import { oklabRgb } from "../oklab/oklab-rgb"; import { srgbRgb } from "../srgb/srgb-rgb"; -import { xyzRgb } from "../xyz/xyz-rgb"; +import { xyyXyz } from "../xyy/xyy-xyz"; +import { xyzRgb, xyzRgbD65 } from "../xyz/xyz-rgb"; import { yccRgb } from "../ycc/ycc-rgb"; export declare class RGB implements TypedColor { @@ -24,6 +26,7 @@ export declare class RGB implements TypedColor { readonly mode: "rgb"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): RGB; copyView(): RGB; deref(): Color; @@ -36,23 +39,21 @@ export declare class RGB implements TypedColor { export const rgb = >defColor({ mode: "rgb", - channels: { - // r: {}, - // g: {}, - // b: {}, - // alpha: { default: 1 }, - }, order: ["r", "g", "b", "alpha"], from: { hcy: hcyRgb, hsi: hsiRgb, hsl: hslRgb, hsv: hsvRgb, - lab: labRgb, + lab50: labRgb, + lab65: labRgbD65, + lch: [lchLab, labRgbD65], oklab: oklabRgb, rgb: set4, srgb: srgbRgb, - xyz: xyzRgb, + xyy: [xyyXyz, xyzRgbD65], + xyz50: xyzRgb, + xyz65: xyzRgbD65, ycc: yccRgb, }, }); diff --git a/packages/color/src/srgb/srgb.ts b/packages/color/src/srgb/srgb.ts index caf52d159f..9cdfcc0b2c 100644 --- a/packages/color/src/srgb/srgb.ts +++ b/packages/color/src/srgb/srgb.ts @@ -16,6 +16,7 @@ export declare class SRGB implements TypedColor { readonly mode: "srgb"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): SRGB; copyView(): SRGB; deref(): Color; @@ -28,12 +29,6 @@ export declare class SRGB implements TypedColor { export const srgb = >defColor({ mode: "srgb", - channels: { - // r: {}, - // g: {}, - // b: {}, - // alpha: { default: 1 }, - }, order: ["r", "g", "b", "alpha"], from: { rgb: rgbSrgb }, }); diff --git a/packages/color/src/xyy/xyy.ts b/packages/color/src/xyy/xyy.ts index bf563c8a30..40878944b3 100644 --- a/packages/color/src/xyy/xyy.ts +++ b/packages/color/src/xyy/xyy.ts @@ -2,7 +2,7 @@ import type { IRandom } from "@thi.ng/random"; import type { Vec } from "@thi.ng/vectors"; import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; import { defColor } from "../defcolor"; -import { rgbXyz } from "../rgb/rgb-xyz"; +import { rgbXyzD65 } from "../rgb/rgb-xyz"; import { xyzXyy } from "../xyz/xyz-xyy"; export declare class XYY implements TypedColor { @@ -17,6 +17,7 @@ export declare class XYY implements TypedColor { readonly mode: "xyy"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): XYY; copyView(): XYY; deref(): Color; @@ -30,11 +31,13 @@ export declare class XYY implements TypedColor { export const xyy = >defColor({ mode: "xyy", channels: { - // x: {}, - // y: {}, - // Y: {}, - // alpha: { default: 1 }, + x: { range: [0, 0.6484] }, + y: { range: [0, 0.5979] }, }, order: ["x", "y", "Y", "alpha"], - from: { rgb: (out, src) => xyzXyy(null, rgbXyz(out, src)), xyz: xyzXyy }, + from: { + rgb: (out, src) => xyzXyy(null, rgbXyzD65(out, src)), + xyz50: xyzXyy, + xyz65: xyzXyy, + }, }); diff --git a/packages/color/src/xyz/wavelength-xyz.ts b/packages/color/src/xyz/wavelength-xyz.ts index d3342cc44d..f914780506 100644 --- a/packages/color/src/xyz/wavelength-xyz.ts +++ b/packages/color/src/xyz/wavelength-xyz.ts @@ -1,6 +1,6 @@ import type { FnU5 } from "@thi.ng/api"; import { setC4 } from "@thi.ng/vectors"; -import { xyz, XYZ } from "./xyz"; +import { xyzD65, XYZD65 } from "./xyz65"; /** * Computes XYZA for given wavelength (in nanometers) and optional alpha channel @@ -21,11 +21,15 @@ import { xyz, XYZ } from "./xyz"; * @param lambda * @param alpha */ -export const wavelengthXyz = (out: XYZ | null, lambda: number, alpha = 1) => { +export const wavelengthXyz = ( + out: XYZD65 | null, + lambda: number, + alpha = 1 +) => { lambda *= 10; - return ( + return ( setC4( - out || xyz(), + out || xyzD65(), gaussian(lambda, 1.056, 5998, 379, 310) + gaussian(lambda, 0.362, 4420, 160, 267) + gaussian(lambda, -0.065, 5011, 204, 262), diff --git a/packages/color/src/xyz/xyz.ts b/packages/color/src/xyz/xyz50.ts similarity index 58% rename from packages/color/src/xyz/xyz.ts rename to packages/color/src/xyz/xyz50.ts index 2563f6f138..8583156eda 100644 --- a/packages/color/src/xyz/xyz.ts +++ b/packages/color/src/xyz/xyz50.ts @@ -3,12 +3,13 @@ import type { Vec } from "@thi.ng/vectors"; import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; import { D50 } from "../api/constants"; import { defColor } from "../defcolor"; -import { labXyz } from "../lab/lab-xyz"; -import { oklabXyz } from "../oklab/oklab-xyz"; +import { labXyz, labXyzD65 } from "../lab/lab-xyz"; +import { oklabXyzD65 } from "../oklab/oklab-xyz"; import { rgbXyz } from "../rgb/rgb-xyz"; import { xyyXyz } from "../xyy/xyy-xyz"; +import { xyzXyzD65_50 } from "./xyz-xyz"; -export declare class XYZ implements TypedColor { +export declare class XYZD50 implements TypedColor { buf: Vec; offset: number; stride: number; @@ -17,27 +18,33 @@ export declare class XYZ implements TypedColor { z: number; alpha: number; [id: number]: number; - readonly mode: "xyz"; + readonly mode: "xyz50"; readonly length: 4; [Symbol.iterator](): Iterator; - copy(): XYZ; - copyView(): XYZ; + clamp(): this; + copy(): XYZD50; + copyView(): XYZD50; deref(): Color; - empty(): XYZ; - eqDelta(o: XYZ, eps?: number): boolean; + empty(): XYZD50; + eqDelta(o: XYZD50, eps?: number): boolean; randomize(rnd?: IRandom): this; set(src: ReadonlyColor): this; toJSON(): number[]; } -export const xyz = >defColor({ - mode: "xyz", +export const xyzD50 = >defColor({ + mode: "xyz50", channels: { x: { range: [0, D50[0]] }, y: { range: [0, D50[1]] }, z: { range: [0, D50[2]] }, - // alpha: { default: 1 }, }, order: ["x", "y", "z", "alpha"], - from: { rgb: rgbXyz, lab: labXyz, oklab: oklabXyz, xyy: xyyXyz }, + from: { + rgb: rgbXyz, + lab50: labXyz, + lab65: [labXyzD65, xyzXyzD65_50], + oklab: [oklabXyzD65, xyzXyzD65_50], + xyy: xyyXyz, + }, }); diff --git a/packages/color/src/xyz/xyz65.ts b/packages/color/src/xyz/xyz65.ts new file mode 100644 index 0000000000..7ba4de96f3 --- /dev/null +++ b/packages/color/src/xyz/xyz65.ts @@ -0,0 +1,43 @@ +import type { IRandom } from "@thi.ng/random"; +import type { Vec } from "@thi.ng/vectors"; +import type { Color, ColorFactory, ReadonlyColor, TypedColor } from "../api"; +import { D65 } from "../api/constants"; +import { defColor } from "../defcolor"; +import { labXyzD65 } from "../lab/lab-xyz"; +import { oklabXyzD65 } from "../oklab/oklab-xyz"; +import { rgbXyzD65 } from "../rgb/rgb-xyz"; +import { xyyXyz } from "../xyy/xyy-xyz"; + +export declare class XYZD65 implements TypedColor { + buf: Vec; + offset: number; + stride: number; + x: number; + y: number; + z: number; + alpha: number; + [id: number]: number; + readonly mode: "xyz65"; + readonly length: 4; + [Symbol.iterator](): Iterator; + clamp(): this; + copy(): XYZD65; + copyView(): XYZD65; + deref(): Color; + empty(): XYZD65; + eqDelta(o: XYZD65, eps?: number): boolean; + randomize(rnd?: IRandom): this; + set(src: ReadonlyColor): this; + toJSON(): number[]; +} + +export const xyzD65 = >defColor({ + mode: "xyz65", + channels: { + x: { range: [0, D65[0]] }, + y: { range: [0, D65[1]] }, + z: { range: [0, D65[2]] }, + }, + order: ["x", "y", "z", "alpha"], + from: { rgb: rgbXyzD65, lab65: labXyzD65, oklab: oklabXyzD65, xyy: xyyXyz }, +}); diff --git a/packages/color/src/ycc/ycc.ts b/packages/color/src/ycc/ycc.ts index 54f41b216e..4946f896ad 100644 --- a/packages/color/src/ycc/ycc.ts +++ b/packages/color/src/ycc/ycc.ts @@ -16,6 +16,7 @@ export declare class YCC implements TypedColor { readonly mode: "ycc"; readonly length: 4; [Symbol.iterator](): Iterator; + clamp(): this; copy(): YCC; copyView(): YCC; deref(): Color; @@ -29,10 +30,8 @@ export declare class YCC implements TypedColor { export const ycc = >defColor({ mode: "ycc", channels: { - // y: {}, - cb: { range: [-1, 1] }, - cr: { range: [-1, 1] }, - // alpha: { default: 1 }, + cb: { range: [-0.5, 0.5] }, + cr: { range: [-0.5, 0.5] }, }, order: ["y", "cb", "cr", "alpha"], from: { rgb: rgbYcc }, diff --git a/packages/color/tools/limits.ts b/packages/color/tools/limits.ts new file mode 100644 index 0000000000..f9293be833 --- /dev/null +++ b/packages/color/tools/limits.ts @@ -0,0 +1,38 @@ +import { map, normRange3d, reducer, transduce } from "@thi.ng/transducers"; +import { max4, MAX4, MIN4, min4, Vec } from "@thi.ng/vectors"; +import { vector } from "@thi.ng/strings"; +import { ColorMode, convert } from "../src"; + +const FMT = vector(4, 4); + +const modes: ColorMode[] = [ + "hcy", + "hsi", + "hsl", + "hsv", + "lab50", + "lab65", + "lch", + "oklab", + "srgb", + "xyy", + "xyz50", + "xyz65", + "ycc", +]; + +for (let mode of modes) { + const bounds = transduce( + map((x) => convert(null, x, mode, "rgb")), + reducer( + () => [[...MAX4], [...MIN4]], + (acc, x) => { + min4(null, acc[0], x); + max4(null, acc[1], x); + return acc; + } + ), + normRange3d(100, 100, 100) + ); + console.log(mode, FMT(bounds[0]), FMT(bounds[1])); +}