@xivdyetools/core 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +26 -0
- package/README.md +2 -0
- package/dist/data/locales/de.json +8 -1
- package/dist/data/locales/en.json +8 -1
- package/dist/data/locales/fr.json +8 -1
- package/dist/data/locales/ja.json +8 -1
- package/dist/data/locales/ko.json +8 -1
- package/dist/data/locales/zh.json +8 -1
- package/dist/data/munsell-hues.json +175 -0
- package/dist/data/oklch-hue-table.json +297 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/services/DyeService.d.ts +35 -0
- package/dist/services/DyeService.d.ts.map +1 -1
- package/dist/services/DyeService.js +35 -0
- package/dist/services/DyeService.js.map +1 -1
- package/dist/services/LocalizationService.d.ts +5 -1
- package/dist/services/LocalizationService.d.ts.map +1 -1
- package/dist/services/LocalizationService.js +8 -0
- package/dist/services/LocalizationService.js.map +1 -1
- package/dist/services/color/ColorConverter.d.ts +39 -0
- package/dist/services/color/ColorConverter.d.ts.map +1 -1
- package/dist/services/color/ColorConverter.js +166 -22
- package/dist/services/color/ColorConverter.js.map +1 -1
- package/dist/services/dye/HarmonyGenerator.d.ts +20 -0
- package/dist/services/dye/HarmonyGenerator.d.ts.map +1 -1
- package/dist/services/dye/HarmonyGenerator.js +11 -1
- package/dist/services/dye/HarmonyGenerator.js.map +1 -1
- package/dist/services/dye/HarmonySelector.d.ts +15 -1
- package/dist/services/dye/HarmonySelector.d.ts.map +1 -1
- package/dist/services/dye/HarmonySelector.js +20 -8
- package/dist/services/dye/HarmonySelector.js.map +1 -1
- package/dist/services/dye/wheels/ColorWheel.d.ts +29 -0
- package/dist/services/dye/wheels/ColorWheel.d.ts.map +1 -0
- package/dist/services/dye/wheels/ColorWheel.js +63 -0
- package/dist/services/dye/wheels/ColorWheel.js.map +1 -0
- package/dist/services/dye/wheels/hue-warp.d.ts +40 -0
- package/dist/services/dye/wheels/hue-warp.d.ts.map +1 -0
- package/dist/services/dye/wheels/hue-warp.js +144 -0
- package/dist/services/dye/wheels/hue-warp.js.map +1 -0
- package/dist/services/dye/wheels/munsell.d.ts +17 -0
- package/dist/services/dye/wheels/munsell.d.ts.map +1 -0
- package/dist/services/dye/wheels/munsell.js +18 -0
- package/dist/services/dye/wheels/munsell.js.map +1 -0
- package/dist/services/dye/wheels/oklch-hue.d.ts +22 -0
- package/dist/services/dye/wheels/oklch-hue.d.ts.map +1 -0
- package/dist/services/dye/wheels/oklch-hue.js +23 -0
- package/dist/services/dye/wheels/oklch-hue.js.map +1 -0
- package/dist/services/dye/wheels/oklch-lightness.d.ts +24 -0
- package/dist/services/dye/wheels/oklch-lightness.d.ts.map +1 -0
- package/dist/services/dye/wheels/oklch-lightness.js +65 -0
- package/dist/services/dye/wheels/oklch-lightness.js.map +1 -0
- package/dist/services/dye/wheels/rgb-ryb.d.ts +18 -0
- package/dist/services/dye/wheels/rgb-ryb.d.ts.map +1 -0
- package/dist/services/dye/wheels/rgb-ryb.js +47 -0
- package/dist/services/dye/wheels/rgb-ryb.js.map +1 -0
- package/dist/services/dye/wheels/types.d.ts +40 -0
- package/dist/services/dye/wheels/types.d.ts.map +1 -0
- package/dist/services/dye/wheels/types.js +11 -0
- package/dist/services/dye/wheels/types.js.map +1 -0
- package/dist/services/localization/TranslationProvider.d.ts +6 -1
- package/dist/services/localization/TranslationProvider.d.ts.map +1 -1
- package/dist/services/localization/TranslationProvider.js +8 -0
- package/dist/services/localization/TranslationProvider.js.map +1 -1
- package/package.json +8 -3
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { HARMONY_OFFSETS } from '../../constants/index.js';
|
|
30
30
|
import { ColorService } from '../ColorService.js';
|
|
31
|
+
import { DEFAULT_COLOR_WHEEL, getColorWheel } from './wheels/ColorWheel.js';
|
|
31
32
|
/**
|
|
32
33
|
* The single definition of "how far is this dye from the slot's target".
|
|
33
34
|
*
|
|
@@ -91,7 +92,17 @@ export function generateHarmonySlots(baseHex, harmonyType, candidates, config, o
|
|
|
91
92
|
if (!isKnownHarmonyType(harmonyType))
|
|
92
93
|
return [];
|
|
93
94
|
const offsets = HARMONY_OFFSETS[harmonyType];
|
|
94
|
-
const
|
|
95
|
+
const wheel = getColorWheel(config.wheel ?? DEFAULT_COLOR_WHEEL);
|
|
96
|
+
// A wheel that keeps the base's OKLab L and C instead of its HSV S/V has no
|
|
97
|
+
// meaningful hue-distance ranking: scoring by degrees against `targetHue`
|
|
98
|
+
// throws away the lightness the wheel was chosen for, and returns a partner
|
|
99
|
+
// at some other lightness entirely. Built once, used for every scoring call
|
|
100
|
+
// below (pins included) so a slot can never be scored in different units
|
|
101
|
+
// from its neighbours — the BUG-064 rule.
|
|
102
|
+
const effective = wheel.carriesBaseHsv
|
|
103
|
+
? config
|
|
104
|
+
: { ...config, usePerceptualMatching: true };
|
|
105
|
+
const baseWheelHue = wheel.hueOf(baseHex);
|
|
95
106
|
const companionCount = config.companionCount ?? 0;
|
|
96
107
|
const preventDuplicates = config.preventDuplicates ?? false;
|
|
97
108
|
// Two different questions, and they were one set until 2026-09-03.
|
|
@@ -116,12 +127,12 @@ export function generateHarmonySlots(baseHex, harmonyType, candidates, config, o
|
|
|
116
127
|
const slots = [];
|
|
117
128
|
offsets.forEach((offset, index) => {
|
|
118
129
|
const normalisedOffset = ((offset % 360) + 360) % 360;
|
|
119
|
-
const
|
|
120
|
-
// The ideal
|
|
121
|
-
//
|
|
122
|
-
// the
|
|
123
|
-
const targetHex =
|
|
124
|
-
const ranked = rankCandidates(candidates, targetHue, targetHex,
|
|
130
|
+
const wheelHue = (baseWheelHue + normalisedOffset) % 360;
|
|
131
|
+
// The wheel builds the ideal. Warp wheels carry the BASE's saturation and
|
|
132
|
+
// value onto the mapped hue — the whole reason a desaturated base finds
|
|
133
|
+
// desaturated dyes; the constant-lightness wheel carries L and C instead.
|
|
134
|
+
const { targetHex, targetHue } = wheel.target(baseHex, wheelHue);
|
|
135
|
+
const ranked = rankCandidates(candidates, targetHue, targetHex, effective);
|
|
125
136
|
const pin = options?.pinned?.get(index);
|
|
126
137
|
// Every branch below assigns, so no initialiser: an unread `= null` here
|
|
127
138
|
// would be one more place for a future edit to leave a slot silently empty.
|
|
@@ -129,7 +140,7 @@ export function generateHarmonySlots(baseHex, harmonyType, candidates, config, o
|
|
|
129
140
|
if (pin) {
|
|
130
141
|
// An explicit hand-swap wins its slot outright, exclusions included: the
|
|
131
142
|
// user naming a dye outranks our guess about which dyes are eligible.
|
|
132
|
-
chosen = { dye: pin, deviance: devianceFor(pin, targetHue, targetHex,
|
|
143
|
+
chosen = { dye: pin, deviance: devianceFor(pin, targetHue, targetHex, effective) };
|
|
133
144
|
}
|
|
134
145
|
else if (preventDuplicates) {
|
|
135
146
|
// First eligible-and-unused, else the nearest eligible even if already
|
|
@@ -165,6 +176,7 @@ export function generateHarmonySlots(baseHex, harmonyType, candidates, config, o
|
|
|
165
176
|
index,
|
|
166
177
|
offset: normalisedOffset,
|
|
167
178
|
targetHue,
|
|
179
|
+
wheelHue,
|
|
168
180
|
targetHex,
|
|
169
181
|
dye: chosen?.dye ?? null,
|
|
170
182
|
deviance: chosen?.deviance ?? 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HarmonySelector.js","sourceRoot":"","sources":["../../../src/services/dye/HarmonySelector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"HarmonySelector.js","sourceRoot":"","sources":["../../../src/services/dye/HarmonySelector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The colour-wheel registry — the ONE list every surface reads.
|
|
3
|
+
*
|
|
4
|
+
* @module services/dye/wheels/ColorWheel
|
|
5
|
+
*/
|
|
6
|
+
import type { ColorWheelId } from '@xivdyetools/types';
|
|
7
|
+
import type { ColorWheel } from './types.js';
|
|
8
|
+
export type { ColorWheel } from './types.js';
|
|
9
|
+
/** Display order. `rgb` first, and the default. */
|
|
10
|
+
export declare const COLOR_WHEEL_IDS: readonly ["rgb", "ryb", "munsell", "oklch-hue", "oklch-lightness"];
|
|
11
|
+
export declare const DEFAULT_COLOR_WHEEL: ColorWheelId;
|
|
12
|
+
export declare function isColorWheelId(value: unknown): value is ColorWheelId;
|
|
13
|
+
/** Lower-cased, trimmed id when recognised; undefined for anything else (absent, empty, unknown). */
|
|
14
|
+
export declare function parseColorWheelId(value: unknown): ColorWheelId | undefined;
|
|
15
|
+
/** `parseColorWheelId(value) ?? DEFAULT_COLOR_WHEEL` — "absent or unknown means rgb", in one place. */
|
|
16
|
+
export declare function normalizeColorWheelId(value: unknown): ColorWheelId;
|
|
17
|
+
/**
|
|
18
|
+
* The short tag each wheel prints in a card footer. Identifiers, never
|
|
19
|
+
* localised — the OG footer tag sits beside `algoTag`, which is the same kind
|
|
20
|
+
* of token, and a translated one would make two cards of the same wheel
|
|
21
|
+
* unrecognisable as such.
|
|
22
|
+
*/
|
|
23
|
+
export declare const COLOR_WHEEL_TAGS: Record<ColorWheelId, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Own-property lookup: the id arrives from a share URL, and
|
|
26
|
+
* `WHEELS['toString']` would be truthy under a plain index.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getColorWheel(id: ColorWheelId): ColorWheel;
|
|
29
|
+
//# sourceMappingURL=ColorWheel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorWheel.d.ts","sourceRoot":"","sources":["../../../../src/services/dye/wheels/ColorWheel.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,mDAAmD;AACnD,eAAO,MAAM,eAAe,oEAMgB,CAAC;AAE7C,eAAO,MAAM,mBAAmB,EAAE,YAAoB,CAAC;AAEvD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAI1E;AAED,uGAAuG;AACvG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAElE;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAMzD,CAAC;AAUF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,UAAU,CAK1D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The colour-wheel registry — the ONE list every surface reads.
|
|
3
|
+
*
|
|
4
|
+
* @module services/dye/wheels/ColorWheel
|
|
5
|
+
*/
|
|
6
|
+
import { MUNSELL_WHEEL } from './munsell.js';
|
|
7
|
+
import { OKLCH_HUE_WHEEL } from './oklch-hue.js';
|
|
8
|
+
import { OKLCH_LIGHTNESS_WHEEL } from './oklch-lightness.js';
|
|
9
|
+
import { RGB_WHEEL, RYB_WHEEL } from './rgb-ryb.js';
|
|
10
|
+
/** Display order. `rgb` first, and the default. */
|
|
11
|
+
export const COLOR_WHEEL_IDS = [
|
|
12
|
+
'rgb',
|
|
13
|
+
'ryb',
|
|
14
|
+
'munsell',
|
|
15
|
+
'oklch-hue',
|
|
16
|
+
'oklch-lightness',
|
|
17
|
+
];
|
|
18
|
+
export const DEFAULT_COLOR_WHEEL = 'rgb';
|
|
19
|
+
export function isColorWheelId(value) {
|
|
20
|
+
return typeof value === 'string' && COLOR_WHEEL_IDS.includes(value);
|
|
21
|
+
}
|
|
22
|
+
/** Lower-cased, trimmed id when recognised; undefined for anything else (absent, empty, unknown). */
|
|
23
|
+
export function parseColorWheelId(value) {
|
|
24
|
+
if (typeof value !== 'string')
|
|
25
|
+
return undefined;
|
|
26
|
+
const v = value.trim().toLowerCase();
|
|
27
|
+
return isColorWheelId(v) ? v : undefined;
|
|
28
|
+
}
|
|
29
|
+
/** `parseColorWheelId(value) ?? DEFAULT_COLOR_WHEEL` — "absent or unknown means rgb", in one place. */
|
|
30
|
+
export function normalizeColorWheelId(value) {
|
|
31
|
+
return parseColorWheelId(value) ?? DEFAULT_COLOR_WHEEL;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The short tag each wheel prints in a card footer. Identifiers, never
|
|
35
|
+
* localised — the OG footer tag sits beside `algoTag`, which is the same kind
|
|
36
|
+
* of token, and a translated one would make two cards of the same wheel
|
|
37
|
+
* unrecognisable as such.
|
|
38
|
+
*/
|
|
39
|
+
export const COLOR_WHEEL_TAGS = {
|
|
40
|
+
rgb: 'RGB',
|
|
41
|
+
ryb: 'RYB',
|
|
42
|
+
munsell: 'MUNSELL',
|
|
43
|
+
'oklch-hue': 'OKLCH·H',
|
|
44
|
+
'oklch-lightness': 'OKLCH·L',
|
|
45
|
+
};
|
|
46
|
+
const WHEELS = {
|
|
47
|
+
rgb: RGB_WHEEL,
|
|
48
|
+
ryb: RYB_WHEEL,
|
|
49
|
+
munsell: MUNSELL_WHEEL,
|
|
50
|
+
'oklch-hue': OKLCH_HUE_WHEEL,
|
|
51
|
+
'oklch-lightness': OKLCH_LIGHTNESS_WHEEL,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Own-property lookup: the id arrives from a share URL, and
|
|
55
|
+
* `WHEELS['toString']` would be truthy under a plain index.
|
|
56
|
+
*/
|
|
57
|
+
export function getColorWheel(id) {
|
|
58
|
+
if (!Object.hasOwn(WHEELS, id)) {
|
|
59
|
+
throw new RangeError(`Unknown colour wheel: ${String(id)}`);
|
|
60
|
+
}
|
|
61
|
+
return WHEELS[id];
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=ColorWheel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorWheel.js","sourceRoot":"","sources":["../../../../src/services/dye/wheels/ColorWheel.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKpD,mDAAmD;AACnD,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,KAAK;IACL,KAAK;IACL,SAAS;IACT,WAAW;IACX,iBAAiB;CACyB,CAAC;AAE7C,MAAM,CAAC,MAAM,mBAAmB,GAAiB,KAAK,CAAC;AAEvD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,eAAqC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC7F,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3C,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,OAAO,iBAAiB,CAAC,KAAK,CAAC,IAAI,mBAAmB,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAiC;IAC5D,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,OAAO,EAAE,SAAS;IAClB,WAAW,EAAE,SAAS;IACtB,iBAAiB,EAAE,SAAS;CAC7B,CAAC;AAEF,MAAM,MAAM,GAA+C;IACzD,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,SAAS;IACd,OAAO,EAAE,aAAa;IACtB,WAAW,EAAE,eAAe;IAC5B,iBAAiB,EAAE,qBAAqB;CACzC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAgB;IAC5C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,UAAU,CAAC,yBAAyB,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hue-warp wheels: a monotone piecewise-linear bijection between sRGB/HSV hue
|
|
3
|
+
* and a wheel angle. RGB (identity), RYB, Munsell and OKLCH-hue are all this.
|
|
4
|
+
*
|
|
5
|
+
* The target keeps the BASE's HSV saturation and value on the mapped hue —
|
|
6
|
+
* the contract the 2026-09-03 harmony convergence rests on — so on the
|
|
7
|
+
* identity table this reproduces today's output bit for bit.
|
|
8
|
+
*
|
|
9
|
+
* @module services/dye/wheels/hue-warp
|
|
10
|
+
*/
|
|
11
|
+
import type { ColorWheel, ColorWheelId, WarpTable } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Every table must be strictly increasing in both columns and run exactly
|
|
14
|
+
* from `[0,0]` to `[360,360]`; otherwise the inverse is ambiguous and a
|
|
15
|
+
* linear-search inverse returns a hue wrong by far more than the dent
|
|
16
|
+
* (research 05 §7: a 0.16° reversal produced a 13° error, silently).
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertMonotoneTable(table: WarpTable, id: string): void;
|
|
19
|
+
/** sRGB/HSV hue → wheel angle. */
|
|
20
|
+
export declare function toWheelHue(table: WarpTable, hsvHue: number): number;
|
|
21
|
+
/** Wheel angle → sRGB/HSV hue. */
|
|
22
|
+
export declare function fromWheelHue(table: WarpTable, wheelHue: number): number;
|
|
23
|
+
export interface NormalizeOptions {
|
|
24
|
+
/** Largest running-max correction tolerated, in degrees. Default 1. */
|
|
25
|
+
maxCorrectionDeg?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Turn measured `[wheelAngle, hsvHue]` pairs — in any order, with the wheel
|
|
29
|
+
* column wrapping wherever the measurement put its zero — into a valid
|
|
30
|
+
* {@link WarpTable}: sorted by HSV hue, wheel column unwrapped, small dents
|
|
31
|
+
* monotonised with a running maximum, re-zeroed so HSV 0° ↦ wheel 0°, and
|
|
32
|
+
* closed with `[0,0]` and `[360,360]`.
|
|
33
|
+
*
|
|
34
|
+
* Every warp wheel is zeroed at sRGB red, so wheels differ in SPACING only;
|
|
35
|
+
* that is what lets the ring, the nodes and the base spoke share one origin.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeWarpTable(raw: ReadonlyArray<readonly [number, number]>, id: string, options?: NormalizeOptions): WarpTable;
|
|
38
|
+
/** Build a wheel from a table. Throws at construction on a bad table. */
|
|
39
|
+
export declare function hueWarpWheel(id: ColorWheelId, table: WarpTable): ColorWheel;
|
|
40
|
+
//# sourceMappingURL=hue-warp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hue-warp.d.ts","sourceRoot":"","sources":["../../../../src/services/dye/wheels/hue-warp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAItE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAiBtE;AAkBD,kCAAkC;AAClC,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,kCAAkC;AAClC,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC7C,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAoDX;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,GAAG,UAAU,CAuB3E"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hue-warp wheels: a monotone piecewise-linear bijection between sRGB/HSV hue
|
|
3
|
+
* and a wheel angle. RGB (identity), RYB, Munsell and OKLCH-hue are all this.
|
|
4
|
+
*
|
|
5
|
+
* The target keeps the BASE's HSV saturation and value on the mapped hue —
|
|
6
|
+
* the contract the 2026-09-03 harmony convergence rests on — so on the
|
|
7
|
+
* identity table this reproduces today's output bit for bit.
|
|
8
|
+
*
|
|
9
|
+
* @module services/dye/wheels/hue-warp
|
|
10
|
+
*/
|
|
11
|
+
import { ColorConverter } from '../../color/ColorConverter.js';
|
|
12
|
+
const mod360 = (x) => (x >= 0 && x < 360 ? x : ((x % 360) + 360) % 360);
|
|
13
|
+
/**
|
|
14
|
+
* Every table must be strictly increasing in both columns and run exactly
|
|
15
|
+
* from `[0,0]` to `[360,360]`; otherwise the inverse is ambiguous and a
|
|
16
|
+
* linear-search inverse returns a hue wrong by far more than the dent
|
|
17
|
+
* (research 05 §7: a 0.16° reversal produced a 13° error, silently).
|
|
18
|
+
*/
|
|
19
|
+
export function assertMonotoneTable(table, id) {
|
|
20
|
+
if (table.length < 2) {
|
|
21
|
+
throw new Error(`ColorWheel ${id}: table needs at least two pairs`);
|
|
22
|
+
}
|
|
23
|
+
const first = table[0];
|
|
24
|
+
const last = table[table.length - 1];
|
|
25
|
+
if (first[0] !== 0 || first[1] !== 0) {
|
|
26
|
+
throw new Error(`ColorWheel ${id}: table must start at [0,0]`);
|
|
27
|
+
}
|
|
28
|
+
if (last[0] !== 360 || last[1] !== 360) {
|
|
29
|
+
throw new Error(`ColorWheel ${id}: table must end at [360,360]`);
|
|
30
|
+
}
|
|
31
|
+
for (let i = 1; i < table.length; i++) {
|
|
32
|
+
if (!(table[i][0] > table[i - 1][0]) || !(table[i][1] > table[i - 1][1])) {
|
|
33
|
+
throw new Error(`ColorWheel ${id}: table is not strictly increasing at row ${i}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Piecewise-linear map of `x` from column `from` to column `to`. */
|
|
38
|
+
function interpolate(table, x, from, to) {
|
|
39
|
+
const v = mod360(x);
|
|
40
|
+
let lo = 0;
|
|
41
|
+
let hi = table.length - 1;
|
|
42
|
+
while (hi - lo > 1) {
|
|
43
|
+
const mid = (lo + hi) >> 1;
|
|
44
|
+
if (table[mid][from] <= v)
|
|
45
|
+
lo = mid;
|
|
46
|
+
else
|
|
47
|
+
hi = mid;
|
|
48
|
+
}
|
|
49
|
+
const a = table[lo];
|
|
50
|
+
const b = table[hi];
|
|
51
|
+
const t = (v - a[from]) / (b[from] - a[from]);
|
|
52
|
+
return mod360(a[to] + t * (b[to] - a[to]));
|
|
53
|
+
}
|
|
54
|
+
/** sRGB/HSV hue → wheel angle. */
|
|
55
|
+
export function toWheelHue(table, hsvHue) {
|
|
56
|
+
return table.length === 2 ? mod360(hsvHue) : interpolate(table, hsvHue, 1, 0);
|
|
57
|
+
}
|
|
58
|
+
/** Wheel angle → sRGB/HSV hue. */
|
|
59
|
+
export function fromWheelHue(table, wheelHue) {
|
|
60
|
+
return table.length === 2 ? mod360(wheelHue) : interpolate(table, wheelHue, 0, 1);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Turn measured `[wheelAngle, hsvHue]` pairs — in any order, with the wheel
|
|
64
|
+
* column wrapping wherever the measurement put its zero — into a valid
|
|
65
|
+
* {@link WarpTable}: sorted by HSV hue, wheel column unwrapped, small dents
|
|
66
|
+
* monotonised with a running maximum, re-zeroed so HSV 0° ↦ wheel 0°, and
|
|
67
|
+
* closed with `[0,0]` and `[360,360]`.
|
|
68
|
+
*
|
|
69
|
+
* Every warp wheel is zeroed at sRGB red, so wheels differ in SPACING only;
|
|
70
|
+
* that is what lets the ring, the nodes and the base spoke share one origin.
|
|
71
|
+
*/
|
|
72
|
+
export function normalizeWarpTable(raw, id, options = {}) {
|
|
73
|
+
const maxCorrection = options.maxCorrectionDeg ?? 1;
|
|
74
|
+
if (raw.length < 3) {
|
|
75
|
+
throw new Error(`ColorWheel ${id}: need at least three measured pairs`);
|
|
76
|
+
}
|
|
77
|
+
const sorted = raw
|
|
78
|
+
.map(([w, h]) => [mod360(w), mod360(h)])
|
|
79
|
+
.sort((a, b) => a[1] - b[1]);
|
|
80
|
+
// Unwrap the wheel column: a backward step larger than half a turn is the
|
|
81
|
+
// measurement's own zero crossing, not a dent.
|
|
82
|
+
const unwrapped = [];
|
|
83
|
+
let offset = 0;
|
|
84
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
85
|
+
let w = sorted[i][0] + offset;
|
|
86
|
+
if (i > 0 && w < unwrapped[i - 1][0] - 180) {
|
|
87
|
+
offset += 360;
|
|
88
|
+
w += 360;
|
|
89
|
+
}
|
|
90
|
+
unwrapped.push([w, sorted[i][1]]);
|
|
91
|
+
}
|
|
92
|
+
// Monotonise small dents; refuse large ones.
|
|
93
|
+
for (let i = 1; i < unwrapped.length; i++) {
|
|
94
|
+
const prev = unwrapped[i - 1][0];
|
|
95
|
+
if (unwrapped[i][0] <= prev) {
|
|
96
|
+
const correction = prev - unwrapped[i][0];
|
|
97
|
+
if (correction > maxCorrection) {
|
|
98
|
+
throw new Error(`ColorWheel ${id}: wheel column reverses by ${correction.toFixed(3)}° at HSV ` +
|
|
99
|
+
`${unwrapped[i][1].toFixed(2)}°, more than the ${maxCorrection}° tolerance`);
|
|
100
|
+
}
|
|
101
|
+
unwrapped[i][0] = prev + 1e-6;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Wheel angle at HSV 0°, by interpolating across the wrap-around segment.
|
|
105
|
+
const [wFirst, hFirst] = unwrapped[0];
|
|
106
|
+
const [wLast, hLast] = unwrapped[unwrapped.length - 1];
|
|
107
|
+
const t = (360 - hLast) / (hFirst + 360 - hLast);
|
|
108
|
+
const w0 = wLast + t * (wFirst + 360 - wLast);
|
|
109
|
+
const table = [[0, 0]];
|
|
110
|
+
for (const [w, h] of unwrapped) {
|
|
111
|
+
const shifted = w - w0 + 360; // wFirst ≥ w0 − 360 by construction
|
|
112
|
+
if (h > 0 && shifted > 0 && shifted < 360)
|
|
113
|
+
table.push([shifted, h]);
|
|
114
|
+
}
|
|
115
|
+
table.push([360, 360]);
|
|
116
|
+
assertMonotoneTable(table, id);
|
|
117
|
+
return table;
|
|
118
|
+
}
|
|
119
|
+
/** Build a wheel from a table. Throws at construction on a bad table. */
|
|
120
|
+
export function hueWarpWheel(id, table) {
|
|
121
|
+
assertMonotoneTable(table, id);
|
|
122
|
+
return {
|
|
123
|
+
id,
|
|
124
|
+
// Every warp wheel's target is the base's own S/V on a re-spaced hue, so
|
|
125
|
+
// angular hue distance to `targetHue` is a meaningful ranking here.
|
|
126
|
+
carriesBaseHsv: true,
|
|
127
|
+
hueOf(hex) {
|
|
128
|
+
return toWheelHue(table, ColorConverter.hexToHsv(hex).h);
|
|
129
|
+
},
|
|
130
|
+
target(baseHex, wheelHue) {
|
|
131
|
+
const base = ColorConverter.hexToHsv(baseHex);
|
|
132
|
+
const targetHue = fromWheelHue(table, wheelHue);
|
|
133
|
+
return { targetHex: ColorConverter.hsvToHex(targetHue, base.s, base.v), targetHue };
|
|
134
|
+
},
|
|
135
|
+
ringStops(count) {
|
|
136
|
+
const stops = [];
|
|
137
|
+
for (let i = 0; i < count; i++) {
|
|
138
|
+
stops.push(ColorConverter.hsvToHex(fromWheelHue(table, (i * 360) / count), 100, 100));
|
|
139
|
+
}
|
|
140
|
+
return stops;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=hue-warp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hue-warp.js","sourceRoot":"","sources":["../../../../src/services/dye/wheels/hue-warp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Munsell hue wheel: 40 principal hues at 3.6° per ASTM step, anchored
|
|
3
|
+
* to sRGB by the renotation data at Value 6 / Chroma 8 (see NOTICE).
|
|
4
|
+
* Red's complement is 5BG, a blue-green — the third answer after RGB (cyan)
|
|
5
|
+
* and RYB (green), and the one Japan's JIS Z 8721 teaches.
|
|
6
|
+
*
|
|
7
|
+
* The 40 raw renotation anchors the table was derived from live in the
|
|
8
|
+
* separate `data/munsell-anchors.json`, which nothing at runtime imports —
|
|
9
|
+
* only `munsell.test.ts`, for its cross-check. Keeping them out of this module
|
|
10
|
+
* keeps them out of every bundle that draws a wheel.
|
|
11
|
+
*
|
|
12
|
+
* @module services/dye/wheels/munsell
|
|
13
|
+
*/
|
|
14
|
+
import type { WarpTable } from './types.js';
|
|
15
|
+
export declare const MUNSELL_TABLE: WarpTable;
|
|
16
|
+
export declare const MUNSELL_WHEEL: import("./types.js").ColorWheel;
|
|
17
|
+
//# sourceMappingURL=munsell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"munsell.d.ts","sourceRoot":"","sources":["../../../../src/services/dye/wheels/munsell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,aAAa,EAAE,SAAqD,CAAC;AAElF,eAAO,MAAM,aAAa,iCAAyC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Munsell hue wheel: 40 principal hues at 3.6° per ASTM step, anchored
|
|
3
|
+
* to sRGB by the renotation data at Value 6 / Chroma 8 (see NOTICE).
|
|
4
|
+
* Red's complement is 5BG, a blue-green — the third answer after RGB (cyan)
|
|
5
|
+
* and RYB (green), and the one Japan's JIS Z 8721 teaches.
|
|
6
|
+
*
|
|
7
|
+
* The 40 raw renotation anchors the table was derived from live in the
|
|
8
|
+
* separate `data/munsell-anchors.json`, which nothing at runtime imports —
|
|
9
|
+
* only `munsell.test.ts`, for its cross-check. Keeping them out of this module
|
|
10
|
+
* keeps them out of every bundle that draws a wheel.
|
|
11
|
+
*
|
|
12
|
+
* @module services/dye/wheels/munsell
|
|
13
|
+
*/
|
|
14
|
+
import munsellData from '../../../data/munsell-hues.json' with { type: 'json' };
|
|
15
|
+
import { hueWarpWheel } from './hue-warp.js';
|
|
16
|
+
export const MUNSELL_TABLE = munsellData.table;
|
|
17
|
+
export const MUNSELL_WHEEL = hueWarpWheel('munsell', MUNSELL_TABLE);
|
|
18
|
+
//# sourceMappingURL=munsell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"munsell.js","sourceRoot":"","sources":["../../../../src/services/dye/wheels/munsell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,WAAW,MAAM,iCAAiC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,CAAC,MAAM,aAAa,GAAc,WAAW,CAAC,KAA6B,CAAC;AAElF,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OKLCH-hue wheel: the sRGB hue circle re-spaced so equal angles are equal
|
|
3
|
+
* OKLab hue steps. Targets still carry the base's HSV saturation and value, so
|
|
4
|
+
* it behaves like RYB (a different spacing), not like a different colour space.
|
|
5
|
+
*
|
|
6
|
+
* The raw OKLab hue of the pure-hue circle reverses by 0.16° across HSV
|
|
7
|
+
* 231.4°–240° — invisible on the ring, a 13° inverse error if left alone
|
|
8
|
+
* (research 05 §7). `normalizeWarpTable` monotonises it and the tests pin the
|
|
9
|
+
* result.
|
|
10
|
+
*
|
|
11
|
+
* The table is DATA, generated by `scripts/build-oklch-hue-table.ts` and
|
|
12
|
+
* committed: deriving it here meant 72 `hsvToHex` + `hexToOklch` round trips
|
|
13
|
+
* on every module load, in every bundle that touched a colour wheel.
|
|
14
|
+
* `scripts/lib/oklch-hue-table.test.ts` re-runs the derivation and compares,
|
|
15
|
+
* so the JSON cannot drift from the maths that produced it.
|
|
16
|
+
*
|
|
17
|
+
* @module services/dye/wheels/oklch-hue
|
|
18
|
+
*/
|
|
19
|
+
import type { WarpTable } from './types.js';
|
|
20
|
+
export declare const OKLCH_HUE_TABLE: WarpTable;
|
|
21
|
+
export declare const OKLCH_HUE_WHEEL: import("./types.js").ColorWheel;
|
|
22
|
+
//# sourceMappingURL=oklch-hue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oklch-hue.d.ts","sourceRoot":"","sources":["../../../../src/services/dye/wheels/oklch-hue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,eAAO,MAAM,eAAe,EAAE,SAAsD,CAAC;AAErF,eAAO,MAAM,eAAe,iCAA6C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OKLCH-hue wheel: the sRGB hue circle re-spaced so equal angles are equal
|
|
3
|
+
* OKLab hue steps. Targets still carry the base's HSV saturation and value, so
|
|
4
|
+
* it behaves like RYB (a different spacing), not like a different colour space.
|
|
5
|
+
*
|
|
6
|
+
* The raw OKLab hue of the pure-hue circle reverses by 0.16° across HSV
|
|
7
|
+
* 231.4°–240° — invisible on the ring, a 13° inverse error if left alone
|
|
8
|
+
* (research 05 §7). `normalizeWarpTable` monotonises it and the tests pin the
|
|
9
|
+
* result.
|
|
10
|
+
*
|
|
11
|
+
* The table is DATA, generated by `scripts/build-oklch-hue-table.ts` and
|
|
12
|
+
* committed: deriving it here meant 72 `hsvToHex` + `hexToOklch` round trips
|
|
13
|
+
* on every module load, in every bundle that touched a colour wheel.
|
|
14
|
+
* `scripts/lib/oklch-hue-table.test.ts` re-runs the derivation and compares,
|
|
15
|
+
* so the JSON cannot drift from the maths that produced it.
|
|
16
|
+
*
|
|
17
|
+
* @module services/dye/wheels/oklch-hue
|
|
18
|
+
*/
|
|
19
|
+
import oklchHueData from '../../../data/oklch-hue-table.json' with { type: 'json' };
|
|
20
|
+
import { hueWarpWheel } from './hue-warp.js';
|
|
21
|
+
export const OKLCH_HUE_TABLE = oklchHueData.table;
|
|
22
|
+
export const OKLCH_HUE_WHEEL = hueWarpWheel('oklch-hue', OKLCH_HUE_TABLE);
|
|
23
|
+
//# sourceMappingURL=oklch-hue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oklch-hue.js","sourceRoot":"","sources":["../../../../src/services/dye/wheels/oklch-hue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,YAAY,MAAM,oCAAoC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,CAAC,MAAM,eAAe,GAAc,YAAY,CAAC,KAA6B,CAAC;AAErF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The constant-lightness OKLCH wheel: rotate hue at the base's OKLab L and C,
|
|
3
|
+
* then gamut-map. Unlike the warp wheels this abandons the base's HSV S/V, so
|
|
4
|
+
* a light base yields light partners and palettes lean toward mid-tones —
|
|
5
|
+
* a deliberate product decision (spec §2.2), not a side effect.
|
|
6
|
+
*
|
|
7
|
+
* @module services/dye/wheels/oklch-lightness
|
|
8
|
+
*/
|
|
9
|
+
import type { ColorWheel } from './types.js';
|
|
10
|
+
/** Ring stops are painted at this L, at the maximum in-gamut chroma per hue. */
|
|
11
|
+
export declare const RING_LIGHTNESS = 0.65;
|
|
12
|
+
/**
|
|
13
|
+
* Below this OKLCH chroma a colour is treated as grey: no hue, no rotation.
|
|
14
|
+
*
|
|
15
|
+
* Pure White (#F9F8F4) measures OKLCH C ≈ 0.0054 and must stay grey under
|
|
16
|
+
* this wheel, so the threshold sits just above it — not at the 0.005 a first
|
|
17
|
+
* draft used, which is just below that dye's real chroma (cross-checked
|
|
18
|
+
* against culori's oklch converter) and would have let it rotate. 0.006
|
|
19
|
+
* stays well under the next dye up by chroma, Charcoal Grey at C ≈ 0.0083,
|
|
20
|
+
* so it does not swallow any colour meant to keep rotating.
|
|
21
|
+
*/
|
|
22
|
+
export declare const ACHROMATIC_CHROMA = 0.006;
|
|
23
|
+
export declare const OKLCH_LIGHTNESS_WHEEL: ColorWheel;
|
|
24
|
+
//# sourceMappingURL=oklch-lightness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oklch-lightness.d.ts","sourceRoot":"","sources":["../../../../src/services/dye/wheels/oklch-lightness.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,gFAAgF;AAChF,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAMvC,eAAO,MAAM,qBAAqB,EAAE,UAyCnC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The constant-lightness OKLCH wheel: rotate hue at the base's OKLab L and C,
|
|
3
|
+
* then gamut-map. Unlike the warp wheels this abandons the base's HSV S/V, so
|
|
4
|
+
* a light base yields light partners and palettes lean toward mid-tones —
|
|
5
|
+
* a deliberate product decision (spec §2.2), not a side effect.
|
|
6
|
+
*
|
|
7
|
+
* @module services/dye/wheels/oklch-lightness
|
|
8
|
+
*/
|
|
9
|
+
import { ColorConverter } from '../../color/ColorConverter.js';
|
|
10
|
+
/** Ring stops are painted at this L, at the maximum in-gamut chroma per hue. */
|
|
11
|
+
export const RING_LIGHTNESS = 0.65;
|
|
12
|
+
/**
|
|
13
|
+
* Below this OKLCH chroma a colour is treated as grey: no hue, no rotation.
|
|
14
|
+
*
|
|
15
|
+
* Pure White (#F9F8F4) measures OKLCH C ≈ 0.0054 and must stay grey under
|
|
16
|
+
* this wheel, so the threshold sits just above it — not at the 0.005 a first
|
|
17
|
+
* draft used, which is just below that dye's real chroma (cross-checked
|
|
18
|
+
* against culori's oklch converter) and would have let it rotate. 0.006
|
|
19
|
+
* stays well under the next dye up by chroma, Charcoal Grey at C ≈ 0.0083,
|
|
20
|
+
* so it does not swallow any colour meant to keep rotating.
|
|
21
|
+
*/
|
|
22
|
+
export const ACHROMATIC_CHROMA = 0.006;
|
|
23
|
+
const mod360 = (x) => ((x % 360) + 360) % 360;
|
|
24
|
+
let ringCache = null;
|
|
25
|
+
export const OKLCH_LIGHTNESS_WHEEL = {
|
|
26
|
+
id: 'oklch-lightness',
|
|
27
|
+
// This wheel abandons the base's HSV S/V for its OKLab L and C, so ranking
|
|
28
|
+
// candidates by angular hue distance would discard exactly the property the
|
|
29
|
+
// wheel exists to preserve. `HarmonySelector` reads this and forces ΔE.
|
|
30
|
+
carriesBaseHsv: false,
|
|
31
|
+
hueOf(hex) {
|
|
32
|
+
const { C, h } = ColorConverter.hexToOklch(hex);
|
|
33
|
+
// A grey has no hue: its measured OKLab angle is an artefact of 8-bit
|
|
34
|
+
// rounding (#808080 reads ≈ 90°, Pure White ≈ 250°), which would park the
|
|
35
|
+
// base spoke at an arbitrary and unstable angle. Every warp wheel puts a
|
|
36
|
+
// grey at 0 (HSV hue of a grey is 0); this one does too.
|
|
37
|
+
if (C < ACHROMATIC_CHROMA)
|
|
38
|
+
return 0;
|
|
39
|
+
return mod360(h);
|
|
40
|
+
},
|
|
41
|
+
target(baseHex, wheelHue) {
|
|
42
|
+
const base = ColorConverter.hexToOklch(baseHex);
|
|
43
|
+
if (base.C < ACHROMATIC_CHROMA) {
|
|
44
|
+
// A grey has no hue to rotate; every partner of a grey is that grey.
|
|
45
|
+
const { r, g, b } = ColorConverter.hexToRgb(baseHex);
|
|
46
|
+
const hex = ColorConverter.rgbToHex(r, g, b);
|
|
47
|
+
return { targetHex: hex, targetHue: ColorConverter.hexToHsv(hex).h };
|
|
48
|
+
}
|
|
49
|
+
const targetHex = ColorConverter.gamutMapOklch(base.L, base.C, mod360(wheelHue));
|
|
50
|
+
return { targetHex, targetHue: ColorConverter.hexToHsv(targetHex).h };
|
|
51
|
+
},
|
|
52
|
+
ringStops(count) {
|
|
53
|
+
if (ringCache && ringCache.count === count)
|
|
54
|
+
return ringCache.stops;
|
|
55
|
+
const stops = [];
|
|
56
|
+
for (let i = 0; i < count; i++) {
|
|
57
|
+
const h = (i * 360) / count;
|
|
58
|
+
const c = ColorConverter.maxChromaOklch(RING_LIGHTNESS, h);
|
|
59
|
+
stops.push(ColorConverter.gamutMapOklch(RING_LIGHTNESS, c, h));
|
|
60
|
+
}
|
|
61
|
+
ringCache = { count, stops };
|
|
62
|
+
return stops;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=oklch-lightness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oklch-lightness.js","sourceRoot":"","sources":["../../../../src/services/dye/wheels/oklch-lightness.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAG/D,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAEnC;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AAE9D,IAAI,SAAS,GAAyD,IAAI,CAAC;AAE3E,MAAM,CAAC,MAAM,qBAAqB,GAAe;IAC/C,EAAE,EAAE,iBAAiB;IAErB,2EAA2E;IAC3E,4EAA4E;IAC5E,wEAAwE;IACxE,cAAc,EAAE,KAAK;IAErB,KAAK,CAAC,GAAW;QACf,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAChD,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,yDAAyD;QACzD,IAAI,CAAC,GAAG,iBAAiB;YAAE,OAAO,CAAC,CAAC;QACpC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,OAAe,EAAE,QAAgB;QACtC,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,CAAC,GAAG,iBAAiB,EAAE,CAAC;YAC/B,qEAAqE;YACrE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACrD,MAAM,GAAG,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7C,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,CAAC;QACD,MAAM,SAAS,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACxE,CAAC;IAED,SAAS,CAAC,KAAa;QACrB,IAAI,SAAS,IAAI,SAAS,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO,SAAS,CAAC,KAAK,CAAC;QACnE,MAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC;YAC5B,MAAM,CAAC,GAAG,cAAc,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;YAC3D,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,cAAc,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The RGB (identity) and RYB (artist's) wheels.
|
|
3
|
+
*
|
|
4
|
+
* RYB is the 25-pair NodeBox/Paletton/Adobe hue warp. **Column 1 is the RYB
|
|
5
|
+
* angle, column 2 the sRGB hue** — the most-read write-up states it the other
|
|
6
|
+
* way round in prose while implementing it correctly, and reversing it turns
|
|
7
|
+
* red's complement from green into blue while a naïve "≠ 180°" test still
|
|
8
|
+
* passes (research 01 §1a). `registry.test.ts` asserts the values.
|
|
9
|
+
*
|
|
10
|
+
* @module services/dye/wheels/rgb-ryb
|
|
11
|
+
*/
|
|
12
|
+
import type { WarpTable } from './types.js';
|
|
13
|
+
/** The identity: today's Harmony Explorer, bit for bit. */
|
|
14
|
+
export declare const RGB_WHEEL: import("./types.js").ColorWheel;
|
|
15
|
+
/** `[rybAngle, srgbHue]`. Red's complement (RYB 180°) is sRGB 138°. */
|
|
16
|
+
export declare const RYB_TABLE: WarpTable;
|
|
17
|
+
export declare const RYB_WHEEL: import("./types.js").ColorWheel;
|
|
18
|
+
//# sourceMappingURL=rgb-ryb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rgb-ryb.d.ts","sourceRoot":"","sources":["../../../../src/services/dye/wheels/rgb-ryb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,2DAA2D;AAC3D,eAAO,MAAM,SAAS,iCAGpB,CAAC;AAEH,uEAAuE;AACvE,eAAO,MAAM,SAAS,EAAE,SA0BvB,CAAC;AAEF,eAAO,MAAM,SAAS,iCAAiC,CAAC"}
|