@flighthq/color 0.2.1-next.410.a23f189
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/dist/colorFromKelvin.d.ts +2 -0
- package/dist/colorFromKelvin.d.ts.map +1 -0
- package/dist/colorFromKelvin.js +44 -0
- package/dist/colorFromKelvin.js.map +1 -0
- package/dist/hslColor.d.ts +5 -0
- package/dist/hslColor.d.ts.map +1 -0
- package/dist/hslColor.js +67 -0
- package/dist/hslColor.js.map +1 -0
- package/dist/hsvColor.d.ts +5 -0
- package/dist/hsvColor.d.ts.map +1 -0
- package/dist/hsvColor.js +83 -0
- package/dist/hsvColor.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/lerpColor.d.ts +4 -0
- package/dist/lerpColor.d.ts.map +1 -0
- package/dist/lerpColor.js +41 -0
- package/dist/lerpColor.js.map +1 -0
- package/dist/luminance.d.ts +5 -0
- package/dist/luminance.d.ts.map +1 -0
- package/dist/luminance.js +35 -0
- package/dist/luminance.js.map +1 -0
- package/dist/oklab.d.ts +3 -0
- package/dist/oklab.d.ts.map +1 -0
- package/dist/oklab.js +34 -0
- package/dist/oklab.js.map +1 -0
- package/dist/packColor.d.ts +13 -0
- package/dist/packColor.d.ts.map +1 -0
- package/dist/packColor.js +81 -0
- package/dist/packColor.js.map +1 -0
- package/dist/premultiplyColorAlpha.d.ts +3 -0
- package/dist/premultiplyColorAlpha.d.ts.map +1 -0
- package/dist/premultiplyColorAlpha.js +24 -0
- package/dist/premultiplyColorAlpha.js.map +1 -0
- package/dist/srgbTransfer.d.ts +3 -0
- package/dist/srgbTransfer.d.ts.map +1 -0
- package/dist/srgbTransfer.js +13 -0
- package/dist/srgbTransfer.js.map +1 -0
- package/package.json +42 -0
- package/src/colorFromKelvin.test.ts +48 -0
- package/src/hslColor.test.ts +67 -0
- package/src/hsvColor.test.ts +57 -0
- package/src/lerpColor.test.ts +53 -0
- package/src/luminance.test.ts +53 -0
- package/src/oklab.test.ts +35 -0
- package/src/packColor.test.ts +159 -0
- package/src/premultiplyColorAlpha.test.ts +36 -0
- package/src/srgbTransfer.test.ts +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colorFromKelvin.d.ts","sourceRoot":"","sources":["../src/colorFromKelvin.ts"],"names":[],"mappings":"AAOA,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqC5D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Creates a packed sRgb-albedo RGBA color (0xrrggbbaa) from a color temperature in Kelvin.
|
|
2
|
+
// Uses the Tanner Helland piecewise approximation (accurate to within ±1% for 1000 K–40000 K),
|
|
3
|
+
// the same algorithm used in Blender, three.js, and Filament's color-temperature helper.
|
|
4
|
+
// Returns opaque white (0xffffffff) for temperatures outside the 1000–40000 K range.
|
|
5
|
+
//
|
|
6
|
+
// Common temperatures: 1800 K = candlelight, 3000 K = warm bulb, 5500 K = noon sunlight,
|
|
7
|
+
// 6500 K = D65 white, 10000 K = overcast sky.
|
|
8
|
+
export function createColorFromKelvin(kelvin) {
|
|
9
|
+
// Clamp to the valid range.
|
|
10
|
+
const temp = Math.max(1000, Math.min(40000, kelvin)) / 100;
|
|
11
|
+
let r;
|
|
12
|
+
let g;
|
|
13
|
+
let b;
|
|
14
|
+
// Red channel.
|
|
15
|
+
if (temp <= 66) {
|
|
16
|
+
r = 255;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
r = 329.698727446 * Math.pow(temp - 60, -0.1332047592);
|
|
20
|
+
}
|
|
21
|
+
// Green channel.
|
|
22
|
+
if (temp <= 66) {
|
|
23
|
+
g = 99.4708025861 * Math.log(temp) - 161.1195681661;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
g = 288.1221695283 * Math.pow(temp - 60, -0.0755148492);
|
|
27
|
+
}
|
|
28
|
+
// Blue channel.
|
|
29
|
+
if (temp >= 66) {
|
|
30
|
+
b = 255;
|
|
31
|
+
}
|
|
32
|
+
else if (temp <= 19) {
|
|
33
|
+
b = 0;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
b = 138.5177312231 * Math.log(temp - 10) - 305.0447927307;
|
|
37
|
+
}
|
|
38
|
+
const ri = Math.max(0, Math.min(255, Math.round(r)));
|
|
39
|
+
const gi = Math.max(0, Math.min(255, Math.round(g)));
|
|
40
|
+
const bi = Math.max(0, Math.min(255, Math.round(b)));
|
|
41
|
+
// Pack as 0xrrggbbaa with fully opaque alpha.
|
|
42
|
+
return ((ri << 24) | (gi << 16) | (bi << 8) | 0xff) >>> 0;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=colorFromKelvin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colorFromKelvin.js","sourceRoot":"","sources":["../src/colorFromKelvin.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,+FAA+F;AAC/F,yFAAyF;AACzF,qFAAqF;AACrF,EAAE;AACF,yFAAyF;AACzF,8CAA8C;AAC9C,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAClD,4BAA4B;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC;IAE3D,IAAI,CAAS,CAAC;IACd,IAAI,CAAS,CAAC;IACd,IAAI,CAAS,CAAC;IAEd,eAAe;IACf,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACf,CAAC,GAAG,GAAG,CAAC;IACV,CAAC;SAAM,CAAC;QACN,CAAC,GAAG,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;IACzD,CAAC;IAED,iBAAiB;IACjB,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACf,CAAC,GAAG,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,CAAC,GAAG,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;IAC1D,CAAC;IAED,gBAAgB;IAChB,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACf,CAAC,GAAG,GAAG,CAAC;IACV,CAAC;SAAM,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACtB,CAAC,GAAG,CAAC,CAAC;IACR,CAAC;SAAM,CAAC;QACN,CAAC,GAAG,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,cAAc,CAAC;IAC5D,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,8CAA8C;IAC9C,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type HslColor = [number, number, number];
|
|
2
|
+
export declare function createHslColor(): HslColor;
|
|
3
|
+
export declare function hslToRgb(out: [number, number, number, number], h: number, s: number, l: number): void;
|
|
4
|
+
export declare function rgbToHsl(out: HslColor, color: number): HslColor;
|
|
5
|
+
//# sourceMappingURL=hslColor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hslColor.d.ts","sourceRoot":"","sources":["../src/hslColor.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAGhD,wBAAgB,cAAc,IAAI,QAAQ,CAEzC;AAKD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAarG;AAMD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CA2B/D"}
|
package/dist/hslColor.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Allocates a fresh zeroed `HslColor` for use as an `rgbToHsl` out parameter.
|
|
2
|
+
export function createHslColor() {
|
|
3
|
+
return [0, 0, 0];
|
|
4
|
+
}
|
|
5
|
+
// Converts HSL to sRGB floats. Writes sRGB [0, 1] R/G/B to out[0..2] (h in [0, 360), s/l in
|
|
6
|
+
// [0, 1]); leaves out[3] (alpha) unmodified. The conversion operates in sRGB (non-linear)
|
|
7
|
+
// space, matching artist-facing color pickers.
|
|
8
|
+
export function hslToRgb(out, h, s, l) {
|
|
9
|
+
if (s === 0) {
|
|
10
|
+
out[0] = l;
|
|
11
|
+
out[1] = l;
|
|
12
|
+
out[2] = l;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
16
|
+
const p = 2 * l - q;
|
|
17
|
+
const hn = h / 360;
|
|
18
|
+
out[0] = hueToRgbChannel(p, q, hn + 1 / 3);
|
|
19
|
+
out[1] = hueToRgbChannel(p, q, hn);
|
|
20
|
+
out[2] = hueToRgbChannel(p, q, hn - 1 / 3);
|
|
21
|
+
}
|
|
22
|
+
// Converts a packed sRGB `0xRRGGBBAA` color to HSL and writes to `out`.
|
|
23
|
+
// `out[0]` = hue in [0, 360), `out[1]` = saturation [0, 1], `out[2]` = lightness [0, 1].
|
|
24
|
+
// Alpha is ignored. The conversion operates in sRGB (non-linear) space, consistent with
|
|
25
|
+
// artist-facing HSL color pickers. Returns `out`.
|
|
26
|
+
export function rgbToHsl(out, color) {
|
|
27
|
+
const r = ((color >>> 24) & 0xff) / 0xff;
|
|
28
|
+
const g = ((color >>> 16) & 0xff) / 0xff;
|
|
29
|
+
const b = ((color >>> 8) & 0xff) / 0xff;
|
|
30
|
+
const max = Math.max(r, g, b);
|
|
31
|
+
const min = Math.min(r, g, b);
|
|
32
|
+
const l = (max + min) / 2;
|
|
33
|
+
if (max === min) {
|
|
34
|
+
out[0] = 0;
|
|
35
|
+
out[1] = 0;
|
|
36
|
+
out[2] = l;
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
const d = max - min;
|
|
40
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
41
|
+
let h;
|
|
42
|
+
if (max === r) {
|
|
43
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
44
|
+
}
|
|
45
|
+
else if (max === g) {
|
|
46
|
+
h = ((b - r) / d + 2) / 6;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
h = ((r - g) / d + 4) / 6;
|
|
50
|
+
}
|
|
51
|
+
out[0] = h * 360;
|
|
52
|
+
out[1] = s;
|
|
53
|
+
out[2] = l;
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
// HSL helper: interpolates a single channel from hue position `t`.
|
|
57
|
+
function hueToRgbChannel(p, q, t) {
|
|
58
|
+
const tn = ((t % 1) + 1) % 1;
|
|
59
|
+
if (tn < 1 / 6)
|
|
60
|
+
return p + (q - p) * 6 * tn;
|
|
61
|
+
if (tn < 1 / 2)
|
|
62
|
+
return q;
|
|
63
|
+
if (tn < 2 / 3)
|
|
64
|
+
return p + (q - p) * (2 / 3 - tn) * 6;
|
|
65
|
+
return p;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=hslColor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hslColor.js","sourceRoot":"","sources":["../src/hslColor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type HsvColor = [number, number, number];
|
|
2
|
+
export declare function createHsvColor(): HsvColor;
|
|
3
|
+
export declare function hsvToRgb(out: [number, number, number, number], h: number, s: number, v: number): void;
|
|
4
|
+
export declare function rgbToHsv(out: HsvColor, color: number): HsvColor;
|
|
5
|
+
//# sourceMappingURL=hsvColor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsvColor.d.ts","sourceRoot":"","sources":["../src/hsvColor.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAGhD,wBAAgB,cAAc,IAAI,QAAQ,CAEzC;AAID,wBAAgB,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CA6CrG;AAKD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAuB/D"}
|
package/dist/hsvColor.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Allocates a fresh zeroed `HsvColor` for use as an `rgbToHsv` out parameter.
|
|
2
|
+
export function createHsvColor() {
|
|
3
|
+
return [0, 0, 0];
|
|
4
|
+
}
|
|
5
|
+
// Converts HSV to RGB floats. Writes sRGB [0, 1] to out[0..2] (h in [0, 360), s/v in [0, 1]).
|
|
6
|
+
// Alpha in out[3] is not modified.
|
|
7
|
+
export function hsvToRgb(out, h, s, v) {
|
|
8
|
+
if (s === 0) {
|
|
9
|
+
out[0] = v;
|
|
10
|
+
out[1] = v;
|
|
11
|
+
out[2] = v;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const hn = ((h % 360) + 360) % 360;
|
|
15
|
+
const i = Math.floor(hn / 60) % 6;
|
|
16
|
+
const f = hn / 60 - Math.floor(hn / 60);
|
|
17
|
+
const p = v * (1 - s);
|
|
18
|
+
const q = v * (1 - f * s);
|
|
19
|
+
const t = v * (1 - (1 - f) * s);
|
|
20
|
+
switch (i) {
|
|
21
|
+
case 0:
|
|
22
|
+
out[0] = v;
|
|
23
|
+
out[1] = t;
|
|
24
|
+
out[2] = p;
|
|
25
|
+
break;
|
|
26
|
+
case 1:
|
|
27
|
+
out[0] = q;
|
|
28
|
+
out[1] = v;
|
|
29
|
+
out[2] = p;
|
|
30
|
+
break;
|
|
31
|
+
case 2:
|
|
32
|
+
out[0] = p;
|
|
33
|
+
out[1] = v;
|
|
34
|
+
out[2] = t;
|
|
35
|
+
break;
|
|
36
|
+
case 3:
|
|
37
|
+
out[0] = p;
|
|
38
|
+
out[1] = q;
|
|
39
|
+
out[2] = v;
|
|
40
|
+
break;
|
|
41
|
+
case 4:
|
|
42
|
+
out[0] = t;
|
|
43
|
+
out[1] = p;
|
|
44
|
+
out[2] = v;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
out[0] = v;
|
|
48
|
+
out[1] = p;
|
|
49
|
+
out[2] = q;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Converts a packed sRGB `0xRRGGBBAA` color to HSV and writes to `out`.
|
|
54
|
+
// `out[0]` = hue in [0, 360), `out[1]` = saturation [0, 1], `out[2]` = value [0, 1].
|
|
55
|
+
// Alpha is ignored. The conversion operates in sRGB (non-linear) space. Returns `out`.
|
|
56
|
+
export function rgbToHsv(out, color) {
|
|
57
|
+
const r = ((color >>> 24) & 0xff) / 0xff;
|
|
58
|
+
const g = ((color >>> 16) & 0xff) / 0xff;
|
|
59
|
+
const b = ((color >>> 8) & 0xff) / 0xff;
|
|
60
|
+
const max = Math.max(r, g, b);
|
|
61
|
+
const min = Math.min(r, g, b);
|
|
62
|
+
const d = max - min;
|
|
63
|
+
const v = max;
|
|
64
|
+
const s = max === 0 ? 0 : d / max;
|
|
65
|
+
let h;
|
|
66
|
+
if (d === 0) {
|
|
67
|
+
h = 0;
|
|
68
|
+
}
|
|
69
|
+
else if (max === r) {
|
|
70
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
71
|
+
}
|
|
72
|
+
else if (max === g) {
|
|
73
|
+
h = ((b - r) / d + 2) / 6;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
h = ((r - g) / d + 4) / 6;
|
|
77
|
+
}
|
|
78
|
+
out[0] = h * 360;
|
|
79
|
+
out[1] = s;
|
|
80
|
+
out[2] = v;
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=hsvColor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsvColor.js","sourceRoot":"","sources":["../src/hsvColor.ts"],"names":[],"mappings":"AAIA,8EAA8E;AAC9E,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACnB,CAAC;AAED,8FAA8F;AAC9F,mCAAmC;AACnC,MAAM,UAAU,QAAQ,CAAC,GAAqC,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS;IAC7F,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACX,OAAO;IACT,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,CAAC;YACJ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,MAAM;QACR,KAAK,CAAC;YACJ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,MAAM;QACR;YACE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACX,MAAM;IACV,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,qFAAqF;AACrF,uFAAuF;AACvF,MAAM,UAAU,QAAQ,CAAC,GAAa,EAAE,KAAa;IACnD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC;IACpB,MAAM,CAAC,GAAG,GAAG,CAAC;IACd,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IAClC,IAAI,CAAC,CAAC;IACN,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,CAAC,GAAG,CAAC,CAAC;IACR,CAAC;SAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACrB,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;SAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACrB,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;SAAM,CAAC;QACN,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IACD,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACjB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACX,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './colorFromKelvin';
|
|
2
|
+
export * from './hslColor';
|
|
3
|
+
export * from './hsvColor';
|
|
4
|
+
export * from './lerpColor';
|
|
5
|
+
export * from './luminance';
|
|
6
|
+
export * from './oklab';
|
|
7
|
+
export * from './packColor';
|
|
8
|
+
export * from './premultiplyColorAlpha';
|
|
9
|
+
export * from './srgbTransfer';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './colorFromKelvin';
|
|
2
|
+
export * from './hslColor';
|
|
3
|
+
export * from './hsvColor';
|
|
4
|
+
export * from './lerpColor';
|
|
5
|
+
export * from './luminance';
|
|
6
|
+
export * from './oklab';
|
|
7
|
+
export * from './packColor';
|
|
8
|
+
export * from './premultiplyColorAlpha';
|
|
9
|
+
export * from './srgbTransfer';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LinearColor } from '@flighthq/types';
|
|
2
|
+
export declare function lerpColor(start: number, end: number, t: number): number;
|
|
3
|
+
export declare function lerpLinearColor(out: LinearColor, start: Readonly<LinearColor>, end: Readonly<LinearColor>, t: number): LinearColor;
|
|
4
|
+
//# sourceMappingURL=lerpColor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lerpColor.d.ts","sourceRoot":"","sources":["../src/lerpColor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AASnD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAevE;AAID,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC5B,GAAG,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC1B,CAAC,EAAE,MAAM,GACR,WAAW,CAeb"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { packLinearToColor } from './packColor';
|
|
2
|
+
import { srgbChannelToLinear } from './srgbTransfer';
|
|
3
|
+
// Linearly interpolates between two packed sRGB colors `start` and `end` by `t` in [0, 1].
|
|
4
|
+
// Interpolation is performed in linear space for perceptual correctness (gamma-correct mix),
|
|
5
|
+
// then repacked to sRGB. Alpha is interpolated linearly (alpha is already linear coverage).
|
|
6
|
+
// `t` is clamped to [0, 1].
|
|
7
|
+
export function lerpColor(start, end, t) {
|
|
8
|
+
const tc = Math.min(1, Math.max(0, t));
|
|
9
|
+
const sr = srgbChannelToLinear(((start >>> 24) & 0xff) / 0xff);
|
|
10
|
+
const sg = srgbChannelToLinear(((start >>> 16) & 0xff) / 0xff);
|
|
11
|
+
const sb = srgbChannelToLinear(((start >>> 8) & 0xff) / 0xff);
|
|
12
|
+
const sa = (start & 0xff) / 0xff;
|
|
13
|
+
const er = srgbChannelToLinear(((end >>> 24) & 0xff) / 0xff);
|
|
14
|
+
const eg = srgbChannelToLinear(((end >>> 16) & 0xff) / 0xff);
|
|
15
|
+
const eb = srgbChannelToLinear(((end >>> 8) & 0xff) / 0xff);
|
|
16
|
+
const ea = (end & 0xff) / 0xff;
|
|
17
|
+
const r = sr + (er - sr) * tc;
|
|
18
|
+
const g = sg + (eg - sg) * tc;
|
|
19
|
+
const b = sb + (eb - sb) * tc;
|
|
20
|
+
const a = sa + (ea - sa) * tc;
|
|
21
|
+
return packLinearToColor([r, g, b, a]);
|
|
22
|
+
}
|
|
23
|
+
// Linearly interpolates between two LinearColors in linear space and writes the result to
|
|
24
|
+
// `out`. `t` is clamped to [0, 1]. Alias-safe: reads all input values before writing `out`.
|
|
25
|
+
export function lerpLinearColor(out, start, end, t) {
|
|
26
|
+
const tc = Math.min(1, Math.max(0, t));
|
|
27
|
+
const r0 = start[0];
|
|
28
|
+
const g0 = start[1];
|
|
29
|
+
const b0 = start[2];
|
|
30
|
+
const a0 = start[3];
|
|
31
|
+
const r1 = end[0];
|
|
32
|
+
const g1 = end[1];
|
|
33
|
+
const b1 = end[2];
|
|
34
|
+
const a1 = end[3];
|
|
35
|
+
out[0] = r0 + (r1 - r0) * tc;
|
|
36
|
+
out[1] = g0 + (g1 - g0) * tc;
|
|
37
|
+
out[2] = b0 + (b1 - b0) * tc;
|
|
38
|
+
out[3] = a0 + (a1 - a0) * tc;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=lerpColor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lerpColor.js","sourceRoot":"","sources":["../src/lerpColor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function getColorContrastRatio(a: number, b: number): number;
|
|
2
|
+
export declare function getColorLuminance(color: number): number;
|
|
3
|
+
export declare function getRec2020LuminanceWeights(out: [number, number, number]): void;
|
|
4
|
+
export declare function getRec709LuminanceWeights(out: [number, number, number]): void;
|
|
5
|
+
//# sourceMappingURL=luminance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"luminance.d.ts","sourceRoot":"","sources":["../src/luminance.ts"],"names":[],"mappings":"AAKA,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlE;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD;AAID,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAI9E;AAID,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAI7E"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { srgbChannelToLinear } from './srgbTransfer';
|
|
2
|
+
// WCAG 2.x contrast ratio between two packed sRGB `0xRRGGBBAA` colors (alpha ignored).
|
|
3
|
+
// Returns the ratio in [1, 21]; 1 = no contrast, 21 = black on white. The formula is
|
|
4
|
+
// (L1 + 0.05) / (L2 + 0.05) where L1 ≥ L2 are the relative luminances.
|
|
5
|
+
export function getColorContrastRatio(a, b) {
|
|
6
|
+
const la = getColorLuminance(a);
|
|
7
|
+
const lb = getColorLuminance(b);
|
|
8
|
+
const lighter = Math.max(la, lb);
|
|
9
|
+
const darker = Math.min(la, lb);
|
|
10
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
11
|
+
}
|
|
12
|
+
// Rec. 709 relative luminance of a packed sRGB `0xRRGGBBAA` color (alpha ignored).
|
|
13
|
+
// Gamma-decodes RGB to linear, then applies the Rec. 709 luminance weights. Returns a
|
|
14
|
+
// value in [0, 1] where 0 is black and 1 is white.
|
|
15
|
+
export function getColorLuminance(color) {
|
|
16
|
+
const r = srgbChannelToLinear(((color >>> 24) & 0xff) / 0xff);
|
|
17
|
+
const g = srgbChannelToLinear(((color >>> 16) & 0xff) / 0xff);
|
|
18
|
+
const b = srgbChannelToLinear(((color >>> 8) & 0xff) / 0xff);
|
|
19
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
20
|
+
}
|
|
21
|
+
// Rec. 2020 luminance weights (ITU-R BT.2020). Writes [r, g, b] weights into `out`.
|
|
22
|
+
// L = 0.2627 * R + 0.6780 * G + 0.0593 * B.
|
|
23
|
+
export function getRec2020LuminanceWeights(out) {
|
|
24
|
+
out[0] = 0.2627;
|
|
25
|
+
out[1] = 0.678;
|
|
26
|
+
out[2] = 0.0593;
|
|
27
|
+
}
|
|
28
|
+
// Rec. 709 / sRGB luminance weights (ITU-R BT.709). Writes [r, g, b] weights into `out`.
|
|
29
|
+
// L = 0.2126 * R + 0.7152 * G + 0.0722 * B.
|
|
30
|
+
export function getRec709LuminanceWeights(out) {
|
|
31
|
+
out[0] = 0.2126;
|
|
32
|
+
out[1] = 0.7152;
|
|
33
|
+
out[2] = 0.0722;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=luminance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"luminance.js","sourceRoot":"","sources":["../src/luminance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,uFAAuF;AACvF,qFAAqF;AACrF,uEAAuE;AACvE,MAAM,UAAU,qBAAqB,CAAC,CAAS,EAAE,CAAS;IACxD,MAAM,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAChC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,mFAAmF;AACnF,sFAAsF;AACtF,mDAAmD;AACnD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7D,OAAO,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,oFAAoF;AACpF,4CAA4C;AAC5C,MAAM,UAAU,0BAA0B,CAAC,GAA6B;IACtE,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IAChB,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IACf,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAClB,CAAC;AAED,yFAAyF;AACzF,4CAA4C;AAC5C,MAAM,UAAU,yBAAyB,CAAC,GAA6B;IACrE,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IAChB,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;IAChB,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAClB,CAAC"}
|
package/dist/oklab.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oklab.d.ts","sourceRoot":"","sources":["../src/oklab.ts"],"names":[],"mappings":"AAGA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAarG;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAarG"}
|
package/dist/oklab.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Converts linear sRGB [0..1] to Oklab, writing [L, a, b] into `out` (L∈[0..1],
|
|
2
|
+
// a/b∈~[-0.5..0.5]). Oklab is a perceptually uniform space; equal steps in L, a, b are
|
|
3
|
+
// visually equal. Reference: Ottosson 2020. Alias-safe.
|
|
4
|
+
export function linearRgbToOklab(out, r, g, b) {
|
|
5
|
+
// linear sRGB → LMS via a 3×3 matrix.
|
|
6
|
+
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
7
|
+
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
8
|
+
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
9
|
+
// Cube-root compress.
|
|
10
|
+
const lc = Math.cbrt(Math.max(0, l));
|
|
11
|
+
const mc = Math.cbrt(Math.max(0, m));
|
|
12
|
+
const sc = Math.cbrt(Math.max(0, s));
|
|
13
|
+
// LMS → Oklab.
|
|
14
|
+
out[0] = 0.2104542553 * lc + 0.793617785 * mc - 0.0040720468 * sc;
|
|
15
|
+
out[1] = 1.9779984951 * lc - 2.428592205 * mc + 0.4505937099 * sc;
|
|
16
|
+
out[2] = 0.0259040371 * lc + 0.7827717662 * mc - 0.808675766 * sc;
|
|
17
|
+
}
|
|
18
|
+
// Converts Oklab [L, a, b] back to linear sRGB [0..1], writing [r, g, b] into `out`.
|
|
19
|
+
// Alias-safe.
|
|
20
|
+
export function oklabToLinearRgb(out, L, a, b) {
|
|
21
|
+
// Oklab → LMS.
|
|
22
|
+
const lc = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
23
|
+
const mc = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
24
|
+
const sc = L - 0.0894841775 * a - 1.291485548 * b;
|
|
25
|
+
// Cube.
|
|
26
|
+
const l = lc * lc * lc;
|
|
27
|
+
const m = mc * mc * mc;
|
|
28
|
+
const s = sc * sc * sc;
|
|
29
|
+
// LMS → linear sRGB.
|
|
30
|
+
out[0] = Math.max(0, 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s);
|
|
31
|
+
out[1] = Math.max(0, -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s);
|
|
32
|
+
out[2] = Math.max(0, -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=oklab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oklab.js","sourceRoot":"","sources":["../src/oklab.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,uFAAuF;AACvF,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAAC,GAA6B,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS;IAC7F,sCAAsC;IACtC,MAAM,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC;IACjE,MAAM,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC;IACjE,MAAM,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC;IACjE,sBAAsB;IACtB,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrC,eAAe;IACf,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,EAAE,GAAG,WAAW,GAAG,EAAE,GAAG,YAAY,GAAG,EAAE,CAAC;IAClE,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,EAAE,GAAG,WAAW,GAAG,EAAE,GAAG,YAAY,GAAG,EAAE,CAAC;IAClE,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,EAAE,GAAG,YAAY,GAAG,EAAE,GAAG,WAAW,GAAG,EAAE,CAAC;AACpE,CAAC;AAED,qFAAqF;AACrF,cAAc;AACd,MAAM,UAAU,gBAAgB,CAAC,GAA6B,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS;IAC7F,eAAe;IACf,MAAM,EAAE,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC;IAClD,QAAQ;IACR,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvB,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvB,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvB,qBAAqB;IACrB,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC;IAC7E,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC;IAC9E,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,YAAY,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC;AAC/E,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LinearColor } from '@flighthq/types';
|
|
2
|
+
export type { LinearColor };
|
|
3
|
+
export declare function computeRgbHexString(color: number): string;
|
|
4
|
+
export declare function createLinearColor(): LinearColor;
|
|
5
|
+
export declare function getColorAlpha(color: number): number;
|
|
6
|
+
export declare function getColorRgb(color: number): number;
|
|
7
|
+
export declare function packColor(r: number, g: number, b: number, a: number): number;
|
|
8
|
+
export declare function packLinearToColor(color: Readonly<LinearColor>): number;
|
|
9
|
+
export declare function packOpaqueColor(rgb: number): number;
|
|
10
|
+
export declare function setColorAlpha(color: number, alpha: number): number;
|
|
11
|
+
export declare function unpackColorRgba(out: [number, number, number, number], color: number): void;
|
|
12
|
+
export declare function unpackColorToLinear(out: LinearColor, color: number): LinearColor;
|
|
13
|
+
//# sourceMappingURL=packColor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packColor.d.ts","sourceRoot":"","sources":["../src/packColor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,YAAY,EAAE,WAAW,EAAE,CAAC;AAK5B,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAGD,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C;AAKD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAKD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAKD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5E;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,MAAM,CAMtE;AAQD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAKD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlE;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAK1F;AAQD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAMhF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { linearChannelToSrgb, srgbChannelToLinear } from './srgbTransfer';
|
|
2
|
+
// Takes a 24-bit RGB color (`0xRRGGBB`, e.g. a TextFormat color) and returns a
|
|
3
|
+
// CSS `#RRGGBB` string. Any high-byte bits are masked off, so a 32-bit RGBA
|
|
4
|
+
// value would keep `GGBBAA` — pass RGB, not RGBA.
|
|
5
|
+
export function computeRgbHexString(color) {
|
|
6
|
+
return `#${(color & 0xffffff).toString(16).padStart(6, '0')}`;
|
|
7
|
+
}
|
|
8
|
+
// Allocates a fresh zeroed `LinearColor` for use as an `unpackColorToLinear` out parameter.
|
|
9
|
+
export function createLinearColor() {
|
|
10
|
+
return [0, 0, 0, 0];
|
|
11
|
+
}
|
|
12
|
+
// Returns the alpha channel of a packed `0xRRGGBBAA` color as a float in [0, 1] — the inverse of
|
|
13
|
+
// `setColorAlpha`. RGB is ignored. Alpha is linear coverage, so this is a plain byte read with no
|
|
14
|
+
// gamma decode (unlike `unpackColorToLinear`, which decodes the RGB channels).
|
|
15
|
+
export function getColorAlpha(color) {
|
|
16
|
+
return (color & 0xff) / 0xff;
|
|
17
|
+
}
|
|
18
|
+
// Returns the 24-bit RGB part (`0xRRGGBB`) of a packed `0xRRGGBBAA` color, dropping alpha — the
|
|
19
|
+
// inverse of `packOpaqueColor`'s widening. Useful when handing a color to an API that wants the
|
|
20
|
+
// alpha-less form (a CSS `#RRGGBB` via `computeRgbHexString`, a 24-bit format field).
|
|
21
|
+
export function getColorRgb(color) {
|
|
22
|
+
return (color >>> 8) & 0xffffff;
|
|
23
|
+
}
|
|
24
|
+
// Packs four sRGB-space components (each in [0, 1]) to a `0xRRGGBBAA` integer.
|
|
25
|
+
// Does NOT gamma-encode — use `packLinearToColor` when starting from linear floats.
|
|
26
|
+
// Components are clamped to [0, 1] and rounded to 8-bit precision.
|
|
27
|
+
export function packColor(r, g, b, a) {
|
|
28
|
+
const ri = Math.round(Math.min(1, Math.max(0, r)) * 0xff);
|
|
29
|
+
const gi = Math.round(Math.min(1, Math.max(0, g)) * 0xff);
|
|
30
|
+
const bi = Math.round(Math.min(1, Math.max(0, b)) * 0xff);
|
|
31
|
+
const ai = Math.round(Math.min(1, Math.max(0, a)) * 0xff);
|
|
32
|
+
return ((ri << 24) | (gi << 16) | (bi << 8) | ai) >>> 0;
|
|
33
|
+
}
|
|
34
|
+
// Packs a linear-space RGBA float color to a `0xRRGGBBAA` integer (the inverse of
|
|
35
|
+
// `unpackColorToLinear`). RGB channels are gamma-encoded to sRGB; alpha passes through
|
|
36
|
+
// unchanged (alpha is linear coverage, never gamma-encoded). Channels are clamped to [0, 1].
|
|
37
|
+
export function packLinearToColor(color) {
|
|
38
|
+
const r = Math.round(Math.min(1, Math.max(0, linearChannelToSrgb(color[0]))) * 0xff);
|
|
39
|
+
const g = Math.round(Math.min(1, Math.max(0, linearChannelToSrgb(color[1]))) * 0xff);
|
|
40
|
+
const b = Math.round(Math.min(1, Math.max(0, linearChannelToSrgb(color[2]))) * 0xff);
|
|
41
|
+
const a = Math.round(Math.min(1, Math.max(0, color[3])) * 0xff);
|
|
42
|
+
return ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
|
|
43
|
+
}
|
|
44
|
+
// Widens a 24-bit RGB color (`0xRRGGBB` — a CSS hex literal, a Flash/AwayJS/TextFormat color, a
|
|
45
|
+
// 24-bit format field) to a fully opaque packed `0xRRGGBBAA` integer, Flight's canonical color form.
|
|
46
|
+
// Bits above 24 are masked off, so `packOpaqueColor(0xff8800)` is `0xff8800ff`. The complement of
|
|
47
|
+
// `getColorRgb`; use `setColorAlpha` afterward for a non-opaque alpha. The value stays sRGB — Flight
|
|
48
|
+
// packed colors are sRGB-encoded and decoded at render (`unpackColorToLinear`), so DO NOT pre-linearize
|
|
49
|
+
// before packing: that double-decodes and loses 8-bit precision.
|
|
50
|
+
export function packOpaqueColor(rgb) {
|
|
51
|
+
return (((rgb & 0xffffff) << 8) | 0xff) >>> 0;
|
|
52
|
+
}
|
|
53
|
+
// Replaces the alpha channel of a packed `0xRRGGBBAA` color with `alpha` (a float in [0, 1], clamped
|
|
54
|
+
// and rounded to 8 bits), leaving RGB untouched — the inverse of `getColorAlpha`. Use to fade or set
|
|
55
|
+
// the opacity of a fixed color without unpacking it.
|
|
56
|
+
export function setColorAlpha(color, alpha) {
|
|
57
|
+
const a = Math.round(Math.min(1, Math.max(0, alpha)) * 0xff);
|
|
58
|
+
return ((color & 0xffffff00) | a) >>> 0;
|
|
59
|
+
}
|
|
60
|
+
// Converts a packed sRGB-space `0xRRGGBBAA` integer into four components in [0, 1] and
|
|
61
|
+
// writes them to `out`. Does NOT gamma-decode — use `unpackColorToLinear` for linear space.
|
|
62
|
+
export function unpackColorRgba(out, color) {
|
|
63
|
+
out[0] = ((color >>> 24) & 0xff) / 0xff;
|
|
64
|
+
out[1] = ((color >>> 16) & 0xff) / 0xff;
|
|
65
|
+
out[2] = ((color >>> 8) & 0xff) / 0xff;
|
|
66
|
+
out[3] = (color & 0xff) / 0xff;
|
|
67
|
+
}
|
|
68
|
+
// The single sRgb→linear decode seam for the whole SDK (§0.2): packed `0xRRGGBBAA` is
|
|
69
|
+
// sRgb-albedo, so RGB is gamma-decoded to linear while alpha passes through unchanged
|
|
70
|
+
// (alpha is already linear coverage, never gamma-encoded). Writes four floats in [0, 1]
|
|
71
|
+
// into `out` and returns it. A packed 8-bit integer cannot carry HDR — light and emissive
|
|
72
|
+
// radiance is `unpackColorToLinear(out, color) × intensity`, computed by the caller.
|
|
73
|
+
// Backends must not re-decode packed colors; they consume this output.
|
|
74
|
+
export function unpackColorToLinear(out, color) {
|
|
75
|
+
out[0] = srgbChannelToLinear(((color >>> 24) & 0xff) / 0xff);
|
|
76
|
+
out[1] = srgbChannelToLinear(((color >>> 16) & 0xff) / 0xff);
|
|
77
|
+
out[2] = srgbChannelToLinear(((color >>> 8) & 0xff) / 0xff);
|
|
78
|
+
out[3] = (color & 0xff) / 0xff;
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=packColor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packColor.js","sourceRoot":"","sources":["../src/packColor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"premultiplyColorAlpha.d.ts","sourceRoot":"","sources":["../src/premultiplyColorAlpha.ts"],"names":[],"mappings":"AAIA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM3D;AAKD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Premultiplies the RGB channels of a packed sRGB `0xRRGGBBAA` color by its alpha channel
|
|
2
|
+
// and returns a new packed color. Output RGB = round(RGB × alpha). The alpha channel
|
|
3
|
+
// is preserved unchanged. Fully-transparent (alpha=0) results in black-with-alpha-0.
|
|
4
|
+
// Used when a renderer requires premultiplied-alpha color values.
|
|
5
|
+
export function premultiplyColorAlpha(color) {
|
|
6
|
+
const a = (color & 0xff) / 0xff;
|
|
7
|
+
const r = Math.round(((color >>> 24) & 0xff) * a);
|
|
8
|
+
const g = Math.round(((color >>> 16) & 0xff) * a);
|
|
9
|
+
const b = Math.round(((color >>> 8) & 0xff) * a);
|
|
10
|
+
return ((r << 24) | (g << 16) | (b << 8) | (color & 0xff)) >>> 0;
|
|
11
|
+
}
|
|
12
|
+
// Reverses premultiplied-alpha encoding for a packed sRGB `0xRRGGBBAA` color.
|
|
13
|
+
// Output RGB = round(RGB / alpha), clamped to [0, 255]. Returns the input unchanged
|
|
14
|
+
// when alpha is 0 (division-by-zero guard: fully-transparent stays black-with-alpha-0).
|
|
15
|
+
export function unpremultiplyColorAlpha(color) {
|
|
16
|
+
const a = (color & 0xff) / 0xff;
|
|
17
|
+
if (a === 0)
|
|
18
|
+
return color;
|
|
19
|
+
const r = Math.min(255, Math.round(((color >>> 24) & 0xff) / a));
|
|
20
|
+
const g = Math.min(255, Math.round(((color >>> 16) & 0xff) / a));
|
|
21
|
+
const b = Math.min(255, Math.round(((color >>> 8) & 0xff) / a));
|
|
22
|
+
return ((r << 24) | (g << 16) | (b << 8) | (color & 0xff)) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=premultiplyColorAlpha.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"premultiplyColorAlpha.js","sourceRoot":"","sources":["../src/premultiplyColorAlpha.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,qFAAqF;AACrF,qFAAqF;AACrF,kEAAkE;AAClE,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,8EAA8E;AAC9E,oFAAoF;AACpF,wFAAwF;AACxF,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srgbTransfer.d.ts","sourceRoot":"","sources":["../src/srgbTransfer.ts"],"names":[],"mappings":"AAGA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAKD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The IEC 61966-2-1 linear-to-sRGB inverse OETF for a single channel in [0, 1].
|
|
2
|
+
// The canonical linear→sRGB channel transfer for the whole SDK; the packed form is
|
|
3
|
+
// `packLinearToColor`. Does not clamp — callers that need HDR clamping do so explicitly.
|
|
4
|
+
export function linearChannelToSrgb(value) {
|
|
5
|
+
return value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055;
|
|
6
|
+
}
|
|
7
|
+
// The IEC 61966-2-1 sRGB electro-optical transfer function for a single channel in [0, 1].
|
|
8
|
+
// The canonical sRGB→linear channel transfer for the whole SDK — the single decode seam
|
|
9
|
+
// (§0.2) `unpackColorToLinear` and `getColorLuminance` build on. Does not clamp.
|
|
10
|
+
export function srgbChannelToLinear(value) {
|
|
11
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=srgbTransfer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srgbTransfer.js","sourceRoot":"","sources":["../src/srgbTransfer.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mFAAmF;AACnF,yFAAyF;AACzF,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC;AACjF,CAAC;AAED,2FAA2F;AAC3F,wFAAwF;AACxF,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC;AAC7E,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/color",
|
|
3
|
+
"version": "0.2.1-next.410.a23f189",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/flighthq/flight.git",
|
|
7
|
+
"directory": "packages/color"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src/**/*.test.ts",
|
|
21
|
+
"!dist/**/*.test.js",
|
|
22
|
+
"!dist/**/*.test.d.ts",
|
|
23
|
+
"!dist/**/*.test.js.map",
|
|
24
|
+
"!dist/**/*.test.d.ts.map"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -b",
|
|
28
|
+
"clean": "tsc -b --clean",
|
|
29
|
+
"test": "vitest run --config vitest.config.ts",
|
|
30
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
31
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
32
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@flighthq/types": "0.2.1-next.410.a23f189"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"typescript": "^5.3.0"
|
|
39
|
+
},
|
|
40
|
+
"description": "Bedrock color primitive — packed-RGBA pack/unpack, sRGB↔linear transfer, HSL/HSV, OkLab, luminance/contrast, premultiply, hex; pure value/space math over plain numbers and LinearColor",
|
|
41
|
+
"sideEffects": false
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createColorFromKelvin } from './colorFromKelvin';
|
|
2
|
+
|
|
3
|
+
describe('createColorFromKelvin', () => {
|
|
4
|
+
it('returns opaque alpha (0xff) for all temperatures', () => {
|
|
5
|
+
expect(createColorFromKelvin(3000) & 0xff).toBe(0xff);
|
|
6
|
+
expect(createColorFromKelvin(6500) & 0xff).toBe(0xff);
|
|
7
|
+
expect(createColorFromKelvin(10000) & 0xff).toBe(0xff);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('returns white (0xffffffff) for D65 daylight (~6500 K)', () => {
|
|
11
|
+
const color = createColorFromKelvin(6500);
|
|
12
|
+
const r = (color >>> 24) & 0xff;
|
|
13
|
+
const g = (color >>> 16) & 0xff;
|
|
14
|
+
const b = (color >>> 8) & 0xff;
|
|
15
|
+
// D65 should be close to neutral white — all channels near 255.
|
|
16
|
+
expect(r).toBeGreaterThan(240);
|
|
17
|
+
expect(g).toBeGreaterThan(240);
|
|
18
|
+
expect(b).toBeGreaterThan(230);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('produces warmer (more red, less blue) color for lower temperatures', () => {
|
|
22
|
+
const warm = createColorFromKelvin(2000);
|
|
23
|
+
const cool = createColorFromKelvin(10000);
|
|
24
|
+
const warmR = (warm >>> 24) & 0xff;
|
|
25
|
+
const warmB = (warm >>> 8) & 0xff;
|
|
26
|
+
const coolR = (cool >>> 24) & 0xff;
|
|
27
|
+
const coolB = (cool >>> 8) & 0xff;
|
|
28
|
+
// Warm candlelight should have more red and less blue than cool sky light.
|
|
29
|
+
expect(warmR).toBeGreaterThanOrEqual(coolR);
|
|
30
|
+
expect(warmB).toBeLessThanOrEqual(coolB);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('clamps inputs below 1000 K to 1000 K', () => {
|
|
34
|
+
expect(createColorFromKelvin(500)).toBe(createColorFromKelvin(1000));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('clamps inputs above 40000 K to 40000 K', () => {
|
|
38
|
+
expect(createColorFromKelvin(50000)).toBe(createColorFromKelvin(40000));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns a valid packed RGBA integer (unsigned 32-bit)', () => {
|
|
42
|
+
const color = createColorFromKelvin(5500);
|
|
43
|
+
expect(color).toBeGreaterThanOrEqual(0);
|
|
44
|
+
expect(color).toBeLessThanOrEqual(0xffffffff);
|
|
45
|
+
// Must be an integer.
|
|
46
|
+
expect(color % 1).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHslColor, hslToRgb, rgbToHsl } from './hslColor';
|
|
2
|
+
|
|
3
|
+
describe('createHslColor', () => {
|
|
4
|
+
it('allocates a zeroed three-component HSL color', () => {
|
|
5
|
+
expect(createHslColor()).toEqual([0, 0, 0]);
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('hslToRgb', () => {
|
|
10
|
+
it('produces achromatic RGB for saturation = 0', () => {
|
|
11
|
+
const out: [number, number, number, number] = [0, 0, 0, 1];
|
|
12
|
+
hslToRgb(out, 0, 0, 0.5);
|
|
13
|
+
expect(out[0]).toBeCloseTo(0.5);
|
|
14
|
+
expect(out[1]).toBeCloseTo(0.5);
|
|
15
|
+
expect(out[2]).toBeCloseTo(0.5);
|
|
16
|
+
});
|
|
17
|
+
it('round-trips with rgbToHsl for a primary color', () => {
|
|
18
|
+
const out: [number, number, number, number] = [0, 0, 0, 1];
|
|
19
|
+
hslToRgb(out, 0, 1, 0.5);
|
|
20
|
+
expect(out[0]).toBeCloseTo(1, 5);
|
|
21
|
+
expect(out[1]).toBeCloseTo(0, 5);
|
|
22
|
+
expect(out[2]).toBeCloseTo(0, 5);
|
|
23
|
+
});
|
|
24
|
+
it('does not modify out[3] (alpha)', () => {
|
|
25
|
+
const out: [number, number, number, number] = [0, 0, 0, 0.75];
|
|
26
|
+
hslToRgb(out, 120, 1, 0.5);
|
|
27
|
+
expect(out[3]).toBe(0.75);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('rgbToHsl', () => {
|
|
32
|
+
it('converts pure red to hue=0, s=1, l=0.5', () => {
|
|
33
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
34
|
+
rgbToHsl(out, 0xff0000ff);
|
|
35
|
+
expect(out[0]).toBeCloseTo(0, 3);
|
|
36
|
+
expect(out[1]).toBeCloseTo(1, 5);
|
|
37
|
+
expect(out[2]).toBeCloseTo(0.5, 5);
|
|
38
|
+
});
|
|
39
|
+
it('converts white to hue=0, s=0, l=1', () => {
|
|
40
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
41
|
+
rgbToHsl(out, 0xffffffff);
|
|
42
|
+
expect(out[0]).toBe(0);
|
|
43
|
+
expect(out[1]).toBe(0);
|
|
44
|
+
expect(out[2]).toBeCloseTo(1, 5);
|
|
45
|
+
});
|
|
46
|
+
it('converts black to hue=0, s=0, l=0', () => {
|
|
47
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
48
|
+
rgbToHsl(out, 0x000000ff);
|
|
49
|
+
expect(out[0]).toBe(0);
|
|
50
|
+
expect(out[1]).toBe(0);
|
|
51
|
+
expect(out[2]).toBeCloseTo(0, 5);
|
|
52
|
+
});
|
|
53
|
+
it('returns the out instance', () => {
|
|
54
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
55
|
+
expect(rgbToHsl(out, 0xff0000ff)).toBe(out);
|
|
56
|
+
});
|
|
57
|
+
it('round-trips with hslToRgb within 1 LSB', () => {
|
|
58
|
+
const hsl: [number, number, number] = [0, 0, 0];
|
|
59
|
+
rgbToHsl(hsl, 0x3c8ab5ff);
|
|
60
|
+
const rgb: [number, number, number, number] = [0, 0, 0, 1];
|
|
61
|
+
hslToRgb(rgb, hsl[0], hsl[1], hsl[2]);
|
|
62
|
+
const repacked =
|
|
63
|
+
(Math.round(rgb[0] * 0xff) << 24) | (Math.round(rgb[1] * 0xff) << 16) | (Math.round(rgb[2] * 0xff) << 8) | 0xff;
|
|
64
|
+
const orig = 0x3c8ab5ff >>> 0;
|
|
65
|
+
expect(Math.abs(((repacked >>> 24) & 0xff) - ((orig >>> 24) & 0xff))).toBeLessThanOrEqual(1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createHsvColor, hsvToRgb, rgbToHsv } from './hsvColor';
|
|
2
|
+
|
|
3
|
+
describe('createHsvColor', () => {
|
|
4
|
+
it('allocates a zeroed three-component HSV color', () => {
|
|
5
|
+
expect(createHsvColor()).toEqual([0, 0, 0]);
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('hsvToRgb', () => {
|
|
10
|
+
it('produces achromatic RGB for saturation = 0', () => {
|
|
11
|
+
const out: [number, number, number, number] = [0, 0, 0, 1];
|
|
12
|
+
hsvToRgb(out, 0, 0, 0.6);
|
|
13
|
+
expect(out[0]).toBeCloseTo(0.6);
|
|
14
|
+
expect(out[1]).toBeCloseTo(0.6);
|
|
15
|
+
expect(out[2]).toBeCloseTo(0.6);
|
|
16
|
+
});
|
|
17
|
+
it('produces pure red for h=0, s=1, v=1', () => {
|
|
18
|
+
const out: [number, number, number, number] = [0, 0, 0, 1];
|
|
19
|
+
hsvToRgb(out, 0, 1, 1);
|
|
20
|
+
expect(out[0]).toBeCloseTo(1, 5);
|
|
21
|
+
expect(out[1]).toBeCloseTo(0, 5);
|
|
22
|
+
expect(out[2]).toBeCloseTo(0, 5);
|
|
23
|
+
});
|
|
24
|
+
it('round-trips with rgbToHsv for a pure green', () => {
|
|
25
|
+
const out: [number, number, number, number] = [0, 0, 0, 1];
|
|
26
|
+
hsvToRgb(out, 120, 1, 1);
|
|
27
|
+
expect(out[0]).toBeCloseTo(0, 5);
|
|
28
|
+
expect(out[1]).toBeCloseTo(1, 5);
|
|
29
|
+
expect(out[2]).toBeCloseTo(0, 5);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('rgbToHsv', () => {
|
|
34
|
+
it('converts pure red to hue=0, s=1, v=1', () => {
|
|
35
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
36
|
+
rgbToHsv(out, 0xff0000ff);
|
|
37
|
+
expect(out[0]).toBeCloseTo(0, 3);
|
|
38
|
+
expect(out[1]).toBeCloseTo(1, 5);
|
|
39
|
+
expect(out[2]).toBeCloseTo(1, 5);
|
|
40
|
+
});
|
|
41
|
+
it('converts black to s=0, v=0', () => {
|
|
42
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
43
|
+
rgbToHsv(out, 0x000000ff);
|
|
44
|
+
expect(out[1]).toBe(0);
|
|
45
|
+
expect(out[2]).toBeCloseTo(0, 5);
|
|
46
|
+
});
|
|
47
|
+
it('converts white to s=0, v=1', () => {
|
|
48
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
49
|
+
rgbToHsv(out, 0xffffffff);
|
|
50
|
+
expect(out[1]).toBe(0);
|
|
51
|
+
expect(out[2]).toBeCloseTo(1, 5);
|
|
52
|
+
});
|
|
53
|
+
it('returns the out instance', () => {
|
|
54
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
55
|
+
expect(rgbToHsv(out, 0xff0000ff)).toBe(out);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { lerpColor, lerpLinearColor } from './lerpColor';
|
|
2
|
+
import { createLinearColor } from './packColor';
|
|
3
|
+
|
|
4
|
+
describe('lerpColor', () => {
|
|
5
|
+
it('returns start at t=0', () => {
|
|
6
|
+
expect(lerpColor(0xff0000ff, 0x0000ffff, 0)).toBe(0xff0000ff);
|
|
7
|
+
});
|
|
8
|
+
it('returns end at t=1', () => {
|
|
9
|
+
expect(lerpColor(0xff0000ff, 0x0000ffff, 1)).toBe(0x0000ffff);
|
|
10
|
+
});
|
|
11
|
+
it('clamps t below 0 to 0', () => {
|
|
12
|
+
expect(lerpColor(0xff0000ff, 0x0000ffff, -1)).toBe(0xff0000ff);
|
|
13
|
+
});
|
|
14
|
+
it('clamps t above 1 to 1', () => {
|
|
15
|
+
expect(lerpColor(0xff0000ff, 0x0000ffff, 2)).toBe(0x0000ffff);
|
|
16
|
+
});
|
|
17
|
+
it('midpoint between black and white is approximately mid-gray', () => {
|
|
18
|
+
const mid = lerpColor(0x000000ff, 0xffffffff, 0.5);
|
|
19
|
+
const r = (mid >>> 24) & 0xff;
|
|
20
|
+
expect(r).toBeCloseTo(0xbc, -1);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('lerpLinearColor', () => {
|
|
25
|
+
it('returns start at t=0', () => {
|
|
26
|
+
const out = createLinearColor();
|
|
27
|
+
const start: [number, number, number, number] = [1, 0, 0, 1];
|
|
28
|
+
const end: [number, number, number, number] = [0, 0, 1, 1];
|
|
29
|
+
lerpLinearColor(out, start, end, 0);
|
|
30
|
+
expect(out).toEqual(start);
|
|
31
|
+
});
|
|
32
|
+
it('returns end at t=1', () => {
|
|
33
|
+
const out = createLinearColor();
|
|
34
|
+
const start: [number, number, number, number] = [1, 0, 0, 1];
|
|
35
|
+
const end: [number, number, number, number] = [0, 0, 1, 1];
|
|
36
|
+
lerpLinearColor(out, start, end, 1);
|
|
37
|
+
expect(out).toEqual(end);
|
|
38
|
+
});
|
|
39
|
+
it('is alias-safe when out is the same object as start', () => {
|
|
40
|
+
const start: [number, number, number, number] = [1, 0, 0, 1];
|
|
41
|
+
const end: [number, number, number, number] = [0, 0, 1, 1];
|
|
42
|
+
const out = start;
|
|
43
|
+
lerpLinearColor(out, out, end, 0.5);
|
|
44
|
+
expect(out[0]).toBeCloseTo(0.5, 8);
|
|
45
|
+
expect(out[2]).toBeCloseTo(0.5, 8);
|
|
46
|
+
});
|
|
47
|
+
it('returns the out instance', () => {
|
|
48
|
+
const out = createLinearColor();
|
|
49
|
+
const start: [number, number, number, number] = [0, 0, 0, 0];
|
|
50
|
+
const end: [number, number, number, number] = [1, 1, 1, 1];
|
|
51
|
+
expect(lerpLinearColor(out, start, end, 0.5)).toBe(out);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getColorContrastRatio,
|
|
3
|
+
getColorLuminance,
|
|
4
|
+
getRec2020LuminanceWeights,
|
|
5
|
+
getRec709LuminanceWeights,
|
|
6
|
+
} from './luminance';
|
|
7
|
+
|
|
8
|
+
describe('getColorContrastRatio', () => {
|
|
9
|
+
it('returns 21 for black on white', () => {
|
|
10
|
+
expect(getColorContrastRatio(0xffffffff, 0x000000ff)).toBeCloseTo(21, 0);
|
|
11
|
+
});
|
|
12
|
+
it('returns 1 for identical colors', () => {
|
|
13
|
+
expect(getColorContrastRatio(0x808080ff, 0x808080ff)).toBeCloseTo(1, 3);
|
|
14
|
+
});
|
|
15
|
+
it('is symmetric — order of arguments does not matter', () => {
|
|
16
|
+
const ratio1 = getColorContrastRatio(0xffffffff, 0x808080ff);
|
|
17
|
+
const ratio2 = getColorContrastRatio(0x808080ff, 0xffffffff);
|
|
18
|
+
expect(ratio1).toBeCloseTo(ratio2, 8);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('getColorLuminance', () => {
|
|
23
|
+
it('returns 1 for white', () => {
|
|
24
|
+
expect(getColorLuminance(0xffffffff)).toBeCloseTo(1, 5);
|
|
25
|
+
});
|
|
26
|
+
it('returns 0 for black', () => {
|
|
27
|
+
expect(getColorLuminance(0x000000ff)).toBeCloseTo(0, 5);
|
|
28
|
+
});
|
|
29
|
+
it('ignores the alpha channel', () => {
|
|
30
|
+
expect(getColorLuminance(0xffffffff)).toBe(getColorLuminance(0xffffff00));
|
|
31
|
+
});
|
|
32
|
+
it('matches expected Rec. 709 value for mid-gray sRGB', () => {
|
|
33
|
+
expect(getColorLuminance(0x808080ff)).toBeCloseTo(0.21586, 4);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('getRec2020LuminanceWeights', () => {
|
|
38
|
+
it('writes the ITU-R BT.2020 weights that sum to 1', () => {
|
|
39
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
40
|
+
getRec2020LuminanceWeights(out);
|
|
41
|
+
expect(out).toEqual([0.2627, 0.678, 0.0593]);
|
|
42
|
+
expect(out[0] + out[1] + out[2]).toBeCloseTo(1, 8);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('getRec709LuminanceWeights', () => {
|
|
47
|
+
it('writes the ITU-R BT.709 weights that sum to 1', () => {
|
|
48
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
49
|
+
getRec709LuminanceWeights(out);
|
|
50
|
+
expect(out).toEqual([0.2126, 0.7152, 0.0722]);
|
|
51
|
+
expect(out[0] + out[1] + out[2]).toBeCloseTo(1, 8);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { linearRgbToOklab, oklabToLinearRgb } from './oklab';
|
|
2
|
+
|
|
3
|
+
describe('linearRgbToOklab', () => {
|
|
4
|
+
it('maps linear white to L≈1, a≈0, b≈0', () => {
|
|
5
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
6
|
+
linearRgbToOklab(out, 1, 1, 1);
|
|
7
|
+
expect(out[0]).toBeCloseTo(1, 3);
|
|
8
|
+
expect(out[1]).toBeCloseTo(0, 4);
|
|
9
|
+
expect(out[2]).toBeCloseTo(0, 4);
|
|
10
|
+
});
|
|
11
|
+
it('maps black to the origin', () => {
|
|
12
|
+
const out: [number, number, number] = [0, 0, 0];
|
|
13
|
+
linearRgbToOklab(out, 0, 0, 0);
|
|
14
|
+
expect(out).toEqual([0, 0, 0]);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('oklabToLinearRgb', () => {
|
|
19
|
+
it('round-trips linear RGB through Oklab for a saturated color', () => {
|
|
20
|
+
const lab: [number, number, number] = [0, 0, 0];
|
|
21
|
+
linearRgbToOklab(lab, 0.5, 0.1, 0.8);
|
|
22
|
+
const rgb: [number, number, number] = [0, 0, 0];
|
|
23
|
+
oklabToLinearRgb(rgb, lab[0], lab[1], lab[2]);
|
|
24
|
+
expect(rgb[0]).toBeCloseTo(0.5, 5);
|
|
25
|
+
expect(rgb[1]).toBeCloseTo(0.1, 5);
|
|
26
|
+
expect(rgb[2]).toBeCloseTo(0.8, 5);
|
|
27
|
+
});
|
|
28
|
+
it('clamps negative linear channels to zero', () => {
|
|
29
|
+
const rgb: [number, number, number] = [0, 0, 0];
|
|
30
|
+
oklabToLinearRgb(rgb, 0, 1, 0);
|
|
31
|
+
expect(rgb[0]).toBeGreaterThanOrEqual(0);
|
|
32
|
+
expect(rgb[1]).toBeGreaterThanOrEqual(0);
|
|
33
|
+
expect(rgb[2]).toBeGreaterThanOrEqual(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeRgbHexString,
|
|
3
|
+
createLinearColor,
|
|
4
|
+
getColorAlpha,
|
|
5
|
+
getColorRgb,
|
|
6
|
+
packColor,
|
|
7
|
+
packLinearToColor,
|
|
8
|
+
packOpaqueColor,
|
|
9
|
+
setColorAlpha,
|
|
10
|
+
unpackColorRgba,
|
|
11
|
+
unpackColorToLinear,
|
|
12
|
+
} from './packColor';
|
|
13
|
+
|
|
14
|
+
describe('computeRgbHexString', () => {
|
|
15
|
+
it('returns a 6-digit hex string of the lower 24 bits', () => {
|
|
16
|
+
expect(computeRgbHexString(0x00ff0000)).toBe('#ff0000');
|
|
17
|
+
expect(computeRgbHexString(0x0000ff00)).toBe('#00ff00');
|
|
18
|
+
expect(computeRgbHexString(0x000000ff)).toBe('#0000ff');
|
|
19
|
+
expect(computeRgbHexString(0x00000000)).toBe('#000000');
|
|
20
|
+
expect(computeRgbHexString(0x00ffffff)).toBe('#ffffff');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('createLinearColor', () => {
|
|
25
|
+
it('allocates a zeroed four-component color', () => {
|
|
26
|
+
expect(createLinearColor()).toEqual([0, 0, 0, 0]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('getColorAlpha', () => {
|
|
31
|
+
it('returns the alpha byte as a float in [0, 1]', () => {
|
|
32
|
+
expect(getColorAlpha(0x11223344)).toBeCloseTo(0x44 / 0xff);
|
|
33
|
+
expect(getColorAlpha(0xffffffff)).toBe(1);
|
|
34
|
+
expect(getColorAlpha(0xabcdef00)).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('getColorRgb', () => {
|
|
39
|
+
it('drops the alpha byte, returning the 24-bit RGB', () => {
|
|
40
|
+
expect(getColorRgb(0xff8800ff)).toBe(0xff8800);
|
|
41
|
+
expect(getColorRgb(0x11223344)).toBe(0x112233);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('round-trips with packOpaqueColor', () => {
|
|
45
|
+
expect(getColorRgb(packOpaqueColor(0x3366cc))).toBe(0x3366cc);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('packColor', () => {
|
|
50
|
+
it('packs white components to 0xffffffff', () => {
|
|
51
|
+
expect(packColor(1, 1, 1, 1)).toBe(0xffffffff);
|
|
52
|
+
});
|
|
53
|
+
it('packs black components to 0x000000ff', () => {
|
|
54
|
+
expect(packColor(0, 0, 0, 1)).toBe(0x000000ff);
|
|
55
|
+
});
|
|
56
|
+
it('clamps out-of-range values', () => {
|
|
57
|
+
expect(packColor(2, -1, 0.5, 1)).toBe(packColor(1, 0, 0.5, 1));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('packLinearToColor', () => {
|
|
62
|
+
it('is the inverse of unpackColorToLinear for white', () => {
|
|
63
|
+
const out = createLinearColor();
|
|
64
|
+
unpackColorToLinear(out, 0xffffffff);
|
|
65
|
+
expect(packLinearToColor(out)).toBe(0xffffffff);
|
|
66
|
+
});
|
|
67
|
+
it('is the inverse of unpackColorToLinear for black (opaque)', () => {
|
|
68
|
+
const out = createLinearColor();
|
|
69
|
+
unpackColorToLinear(out, 0x000000ff);
|
|
70
|
+
expect(packLinearToColor(out)).toBe(0x000000ff);
|
|
71
|
+
});
|
|
72
|
+
it('passes alpha through without gamma encoding', () => {
|
|
73
|
+
const color = 0x000000_80 >>> 0;
|
|
74
|
+
const out = createLinearColor();
|
|
75
|
+
unpackColorToLinear(out, color);
|
|
76
|
+
const repacked = packLinearToColor(out);
|
|
77
|
+
expect(repacked & 0xff).toBe(0x80);
|
|
78
|
+
});
|
|
79
|
+
it('round-trips a mid-gray color within 1 LSB', () => {
|
|
80
|
+
const color = 0x808080ff >>> 0;
|
|
81
|
+
const out = createLinearColor();
|
|
82
|
+
unpackColorToLinear(out, color);
|
|
83
|
+
const repacked = packLinearToColor(out);
|
|
84
|
+
const origR = (color >>> 24) & 0xff;
|
|
85
|
+
const repR = (repacked >>> 24) & 0xff;
|
|
86
|
+
expect(Math.abs(repR - origR)).toBeLessThanOrEqual(1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('packOpaqueColor', () => {
|
|
91
|
+
it('widens 24-bit RGB to 32-bit RGBA with full opacity', () => {
|
|
92
|
+
expect(packOpaqueColor(0x336699)).toBe(0x336699ff);
|
|
93
|
+
expect(packOpaqueColor(0x000000)).toBe(0x000000ff);
|
|
94
|
+
expect(packOpaqueColor(0xffffff)).toBe(0xffffffff);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('masks off bits above 24 (ignores any alpha in the input)', () => {
|
|
98
|
+
expect(packOpaqueColor(0xaa336699)).toBe(0x336699ff);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns an unsigned 32-bit integer', () => {
|
|
102
|
+
expect(packOpaqueColor(0xffffff)).toBeGreaterThan(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('setColorAlpha', () => {
|
|
107
|
+
it('replaces the alpha byte from a float in [0, 1], leaving RGB intact', () => {
|
|
108
|
+
expect(setColorAlpha(0xff8800ff, 0)).toBe(0xff880000);
|
|
109
|
+
expect(setColorAlpha(0xff880000, 1)).toBe(0xff8800ff);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('clamps and rounds to 8 bits', () => {
|
|
113
|
+
expect(setColorAlpha(0x112233ff, 2)).toBe(0x112233ff);
|
|
114
|
+
expect(getColorAlpha(setColorAlpha(0x112233ff, 0.5))).toBeCloseTo(0.5, 2);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('unpackColorRgba', () => {
|
|
119
|
+
it('extracts white as all-ones', () => {
|
|
120
|
+
const out: [number, number, number, number] = [0, 0, 0, 0];
|
|
121
|
+
unpackColorRgba(out, 0xffffffff);
|
|
122
|
+
expect(out[0]).toBeCloseTo(1, 5);
|
|
123
|
+
expect(out[1]).toBeCloseTo(1, 5);
|
|
124
|
+
expect(out[2]).toBeCloseTo(1, 5);
|
|
125
|
+
expect(out[3]).toBeCloseTo(1, 5);
|
|
126
|
+
});
|
|
127
|
+
it('does NOT gamma-decode — mid sRGB stays above the linear midpoint', () => {
|
|
128
|
+
const out: [number, number, number, number] = [0, 0, 0, 0];
|
|
129
|
+
unpackColorRgba(out, 0x808080ff);
|
|
130
|
+
expect(out[0]).toBeCloseTo(0x80 / 0xff, 4);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('unpackColorToLinear', () => {
|
|
135
|
+
it('decodes white and black exactly at the endpoints', () => {
|
|
136
|
+
const out = createLinearColor();
|
|
137
|
+
expect(unpackColorToLinear(out, 0xffffffff)).toEqual([1, 1, 1, 1]);
|
|
138
|
+
expect(unpackColorToLinear(out, 0x000000ff)).toEqual([0, 0, 0, 1]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('passes alpha through linearly without gamma decode', () => {
|
|
142
|
+
const out = createLinearColor();
|
|
143
|
+
unpackColorToLinear(out, 0x00000080);
|
|
144
|
+
expect(out[3]).toBeCloseTo(0x80 / 0xff, 6);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('gamma-decodes RGB so mid sRgb is below the linear midpoint', () => {
|
|
148
|
+
const out = createLinearColor();
|
|
149
|
+
unpackColorToLinear(out, 0x808080ff);
|
|
150
|
+
expect(out[0]).toBeCloseTo(0.21586, 4);
|
|
151
|
+
expect(out[0]).toBe(out[1]);
|
|
152
|
+
expect(out[1]).toBe(out[2]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns the same out instance it was given', () => {
|
|
156
|
+
const out = createLinearColor();
|
|
157
|
+
expect(unpackColorToLinear(out, 0xff0000ff)).toBe(out);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { premultiplyColorAlpha, unpremultiplyColorAlpha } from './premultiplyColorAlpha';
|
|
2
|
+
|
|
3
|
+
describe('premultiplyColorAlpha', () => {
|
|
4
|
+
it('returns fully-opaque color unchanged (alpha=1)', () => {
|
|
5
|
+
expect(premultiplyColorAlpha(0xff0000ff)).toBe(0xff0000ff);
|
|
6
|
+
});
|
|
7
|
+
it('multiplies RGB by alpha and preserves the alpha channel', () => {
|
|
8
|
+
const result = premultiplyColorAlpha(0xff00007f);
|
|
9
|
+
expect((result >>> 24) & 0xff).toBeCloseTo(0x7f, -1);
|
|
10
|
+
expect(result & 0xff).toBe(0x7f);
|
|
11
|
+
});
|
|
12
|
+
it('returns black-with-alpha-0 for fully transparent color', () => {
|
|
13
|
+
const result = premultiplyColorAlpha(0xff000000);
|
|
14
|
+
expect(result & 0xff).toBe(0);
|
|
15
|
+
expect((result >>> 24) & 0xff).toBe(0);
|
|
16
|
+
});
|
|
17
|
+
it('round-trips with unpremultiplyColorAlpha for fully-opaque', () => {
|
|
18
|
+
const color = 0xab3456ff;
|
|
19
|
+
expect(unpremultiplyColorAlpha(premultiplyColorAlpha(color))).toBe(color);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('unpremultiplyColorAlpha', () => {
|
|
24
|
+
it('returns fully-opaque color unchanged', () => {
|
|
25
|
+
expect(unpremultiplyColorAlpha(0xff0000ff)).toBe(0xff0000ff);
|
|
26
|
+
});
|
|
27
|
+
it('divides RGB by alpha and preserves the alpha channel', () => {
|
|
28
|
+
const premul = premultiplyColorAlpha((0xff0000ff & (0xffffff80 >>> 0)) | 0x80);
|
|
29
|
+
const result = unpremultiplyColorAlpha(premul);
|
|
30
|
+
expect(result & 0xff).toBe(0x80);
|
|
31
|
+
});
|
|
32
|
+
it('returns the input unchanged for alpha=0', () => {
|
|
33
|
+
const color = 0xff000000;
|
|
34
|
+
expect(unpremultiplyColorAlpha(color)).toBe(color);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { linearChannelToSrgb, srgbChannelToLinear } from './srgbTransfer';
|
|
2
|
+
|
|
3
|
+
describe('linearChannelToSrgb', () => {
|
|
4
|
+
it('maps 0 → 0', () => {
|
|
5
|
+
expect(linearChannelToSrgb(0)).toBe(0);
|
|
6
|
+
});
|
|
7
|
+
it('maps 1 → 1', () => {
|
|
8
|
+
expect(linearChannelToSrgb(1)).toBeCloseTo(1, 8);
|
|
9
|
+
});
|
|
10
|
+
it('is the inverse of the sRGB decode for a round-trip', () => {
|
|
11
|
+
const linear = 0.5;
|
|
12
|
+
const srgb = linearChannelToSrgb(linear);
|
|
13
|
+
expect(srgbChannelToLinear(srgb)).toBeCloseTo(linear, 8);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('srgbChannelToLinear', () => {
|
|
18
|
+
it('maps 0 → 0 and 1 → 1', () => {
|
|
19
|
+
expect(srgbChannelToLinear(0)).toBe(0);
|
|
20
|
+
expect(srgbChannelToLinear(1)).toBeCloseTo(1, 8);
|
|
21
|
+
});
|
|
22
|
+
it('decodes mid sRGB below the linear midpoint', () => {
|
|
23
|
+
// 0x80/0xff ≈ 0.502 sRGB → linear ≈ 0.21586.
|
|
24
|
+
expect(srgbChannelToLinear(0x80 / 0xff)).toBeCloseTo(0.21586, 5);
|
|
25
|
+
});
|
|
26
|
+
it('round-trips with linearChannelToSrgb', () => {
|
|
27
|
+
const srgb = 0.25;
|
|
28
|
+
expect(linearChannelToSrgb(srgbChannelToLinear(srgb))).toBeCloseTo(srgb, 8);
|
|
29
|
+
});
|
|
30
|
+
});
|