@zakkster/lite-color-engine 1.0.4 → 1.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/CHANGELOG.md +32 -0
- package/README.md +37 -312
- package/index.d.ts +101 -9
- package/index.js +11 -2
- package/llms.txt +29 -192
- package/package.json +23 -5
- package/src/Gamut.d.ts +37 -0
- package/src/Gamut.js +228 -0
- package/src/Remap.d.ts +87 -0
- package/src/Remap.js +306 -0
- package/src/authoring.js +47 -6
- package/src/convert.js +127 -5
- package/src/delta.js +37 -0
- package/src/runtime.js +64 -4
package/src/runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { lerp, lerpAngle } from '@zakkster/lite-lerp';
|
|
2
|
+
import { oklchToLinearP3 } from './convert.js';
|
|
2
3
|
|
|
3
4
|
const DEG_TO_RAD = Math.PI / 180;
|
|
4
5
|
|
|
@@ -7,7 +8,7 @@ const DEG_TO_RAD = Math.PI / 180;
|
|
|
7
8
|
*
|
|
8
9
|
* Hue is canonicalized to [0, 360) via `lerpAngle` (shortest-path) so gradients
|
|
9
10
|
* never wrap the long way around the color wheel. Lightness is hard-clamped
|
|
10
|
-
* to [0, 1] and chroma to [0,
|
|
11
|
+
* to [0, 1] and chroma to [0, +inf).
|
|
11
12
|
*
|
|
12
13
|
* Source and destination may be the same buffer at different offsets.
|
|
13
14
|
*
|
|
@@ -60,7 +61,6 @@ const oklchToLinearSrgbClamped = (l, c, h, outRgb) => {
|
|
|
60
61
|
const b = -0.0041960863 * lms_l - 0.7034186147 * lms_m + 1.7076147010 * lms_s;
|
|
61
62
|
|
|
62
63
|
// Hard gamut clamp (fast path; replaces the expensive MINDE algorithm).
|
|
63
|
-
// Chroma reduction (Lab-space gamut mapping) is planned for v1.1.
|
|
64
64
|
outRgb[0] = r < 0 ? 0 : (r > 1 ? 1 : r);
|
|
65
65
|
outRgb[1] = g < 0 ? 0 : (g > 1 ? 1 : g);
|
|
66
66
|
outRgb[2] = b < 0 ? 0 : (b > 1 ? 1 : b);
|
|
@@ -68,11 +68,14 @@ const oklchToLinearSrgbClamped = (l, c, h, outRgb) => {
|
|
|
68
68
|
|
|
69
69
|
// Module-level scratch (zero-GC: single allocation at module load).
|
|
70
70
|
const _scratchRgb = new Float32Array(3);
|
|
71
|
+
const _scratchRgbP3 = new Float32Array(3);
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Encodes a linear-light channel (already clamped to [0, 1]) to an sRGB
|
|
74
75
|
* 8-bit byte using the proper IEC 61966-2-1 transfer function. Round-tripping
|
|
75
76
|
* sRGB(255-bytes) -> OKLCH -> here recovers the original byte exactly.
|
|
77
|
+
*
|
|
78
|
+
* This is also the correct transfer function for Display P3.
|
|
76
79
|
* @internal
|
|
77
80
|
*/
|
|
78
81
|
const linearToSrgbByte = (c) => {
|
|
@@ -84,7 +87,7 @@ const linearToSrgbByte = (c) => {
|
|
|
84
87
|
|
|
85
88
|
/**
|
|
86
89
|
* Converts an OKLCH buffer triplet to a 32-bit unsigned integer in
|
|
87
|
-
* little-endian RGBA byte order
|
|
90
|
+
* little-endian RGBA byte order - the format consumed directly by a
|
|
88
91
|
* `Uint32Array` view of `Canvas ImageData`.
|
|
89
92
|
*
|
|
90
93
|
* Uses the proper sRGB transfer function (`pow(c, 1/2.4)` branch). For a
|
|
@@ -132,10 +135,67 @@ export const packOklchBufferToUint32Fast = (buf, offset, alpha = 1.0) => {
|
|
|
132
135
|
return ((a8 << 24) | (b8 << 16) | (g8 << 8) | r8) >>> 0;
|
|
133
136
|
};
|
|
134
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Internal helper: OKLCH -> linear Display P3 with hard clamp to [0,1].
|
|
140
|
+
* Mirrors the structure of oklchToLinearSrgbClamped but targets the wider P3 gamut.
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
const oklchToLinearP3Clamped = (l, c, h, outRgb) => {
|
|
144
|
+
oklchToLinearP3(l, c, h, outRgb);
|
|
145
|
+
outRgb[0] = outRgb[0] < 0 ? 0 : (outRgb[0] > 1 ? 1 : outRgb[0]);
|
|
146
|
+
outRgb[1] = outRgb[1] < 0 ? 0 : (outRgb[1] > 1 ? 1 : outRgb[1]);
|
|
147
|
+
outRgb[2] = outRgb[2] < 0 ? 0 : (outRgb[2] > 1 ? 1 : outRgb[2]);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Converts an OKLCH buffer triplet to a 32-bit unsigned integer in
|
|
152
|
+
* little-endian RGBA byte order, encoded for **Display P3** color space.
|
|
153
|
+
*
|
|
154
|
+
* Use this when your canvas context was created with `{ colorSpace: 'display-p3' }`.
|
|
155
|
+
* Colors that are out of sRGB but inside P3 will be preserved with higher
|
|
156
|
+
* saturation than the regular sRGB packers.
|
|
157
|
+
*
|
|
158
|
+
* The transfer function is identical to sRGB (IEC 61966-2-1).
|
|
159
|
+
*
|
|
160
|
+
* @param {Float32Array} buf - Buffer containing the OKLCH triplet
|
|
161
|
+
* @param {number} offset - Start index in the buffer
|
|
162
|
+
* @param {number} [alpha=1.0] - Alpha [0, 1]; values outside the range are clamped
|
|
163
|
+
* @returns {number} 32-bit unsigned integer (little-endian RGBA byte order)
|
|
164
|
+
*/
|
|
165
|
+
export const packOklchBufferToUint32P3 = (buf, offset, alpha = 1.0) => {
|
|
166
|
+
oklchToLinearP3Clamped(buf[offset], buf[offset + 1], buf[offset + 2], _scratchRgbP3);
|
|
167
|
+
const r8 = linearToSrgbByte(_scratchRgbP3[0]);
|
|
168
|
+
const g8 = linearToSrgbByte(_scratchRgbP3[1]);
|
|
169
|
+
const b8 = linearToSrgbByte(_scratchRgbP3[2]);
|
|
170
|
+
const a8 = alpha <= 0 ? 0 : (alpha >= 1 ? 255 : (alpha * 255 + 0.5) | 0);
|
|
171
|
+
return ((a8 << 24) | (b8 << 16) | (g8 << 8) | r8) >>> 0;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Faster, less accurate sibling of {@link packOklchBufferToUint32P3}.
|
|
176
|
+
*
|
|
177
|
+
* Uses `Math.sqrt` approximation for the transfer function (same trade-off as
|
|
178
|
+
* the sRGB fast path). Useful for high-volume particle systems or when
|
|
179
|
+
* baking many P3 gradients where absolute round-trip precision is not critical.
|
|
180
|
+
*
|
|
181
|
+
* @param {Float32Array} buf - Buffer containing the OKLCH triplet
|
|
182
|
+
* @param {number} offset - Start index in the buffer
|
|
183
|
+
* @param {number} [alpha=1.0] - Alpha [0, 1]; values outside the range are clamped
|
|
184
|
+
* @returns {number} 32-bit unsigned integer (little-endian RGBA byte order)
|
|
185
|
+
*/
|
|
186
|
+
export const packOklchBufferToUint32P3Fast = (buf, offset, alpha = 1.0) => {
|
|
187
|
+
oklchToLinearP3Clamped(buf[offset], buf[offset + 1], buf[offset + 2], _scratchRgbP3);
|
|
188
|
+
const r8 = (Math.sqrt(_scratchRgbP3[0]) * 255) | 0;
|
|
189
|
+
const g8 = (Math.sqrt(_scratchRgbP3[1]) * 255) | 0;
|
|
190
|
+
const b8 = (Math.sqrt(_scratchRgbP3[2]) * 255) | 0;
|
|
191
|
+
const a8 = alpha <= 0 ? 0 : (alpha >= 1 ? 255 : (alpha * 255) | 0);
|
|
192
|
+
return ((a8 << 24) | (b8 << 16) | (g8 << 8) | r8) >>> 0;
|
|
193
|
+
};
|
|
194
|
+
|
|
135
195
|
/**
|
|
136
196
|
* Zero-GC LUT sampler. Looks up a baked gradient color by `t` in [0, 1].
|
|
137
197
|
*
|
|
138
|
-
* Performs an inline clamp + bitwise-truncated index
|
|
198
|
+
* Performs an inline clamp + bitwise-truncated index - no allocations, no
|
|
139
199
|
* function calls, no bounds checks beyond the single comparison pair. Use
|
|
140
200
|
* inside particle systems, shader-style canvas loops, etc.
|
|
141
201
|
*
|