@texel/color 1.0.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/LICENSE.md +21 -0
- package/README.md +376 -0
- package/package.json +65 -0
- package/src/conversion_matrices.js +268 -0
- package/src/core.js +186 -0
- package/src/gamut.js +336 -0
- package/src/index.js +6 -0
- package/src/okhsl.js +345 -0
- package/src/spaces/a98-rgb.js +50 -0
- package/src/spaces/display-p3.js +28 -0
- package/src/spaces/oklab.js +70 -0
- package/src/spaces/prophoto-rgb.js +72 -0
- package/src/spaces/rec2020.js +47 -0
- package/src/spaces/srgb.js +29 -0
- package/src/spaces/util.js +44 -0
- package/src/spaces/xyz.js +44 -0
- package/src/spaces.js +44 -0
- package/src/util.js +119 -0
- package/test/almost-equal.js +15 -0
- package/test/banner.png +0 -0
- package/test/bench-colorjs.js +138 -0
- package/test/bench-node.js +51 -0
- package/test/bench-size.js +3 -0
- package/test/canvas-graph.js +210 -0
- package/test/logo.js +112 -0
- package/test/logo.png +0 -0
- package/test/profiles/DisplayP3.icc +0 -0
- package/test/test-colorjs.js +87 -0
- package/test/test.js +321 -0
- package/tools/__pycache__/calc_oklab_matrices.cpython-311.pyc +0 -0
- package/tools/calc_oklab_matrices.py +233 -0
- package/tools/print_matrices.py +509 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { vec3 } from "../util.js";
|
|
2
|
+
import {
|
|
3
|
+
linear_A98RGB_to_XYZ_M,
|
|
4
|
+
XYZ_to_linear_A98RGB_M,
|
|
5
|
+
linear_A98RGB_to_LMS_M,
|
|
6
|
+
LMS_to_linear_A98RGB_M,
|
|
7
|
+
OKLab_to_linear_A98RGB_coefficients,
|
|
8
|
+
} from "../conversion_matrices.js";
|
|
9
|
+
|
|
10
|
+
const A98RGBToLinear = (val) => {
|
|
11
|
+
let sign = val < 0 ? -1 : 1;
|
|
12
|
+
let abs = Math.abs(val);
|
|
13
|
+
return sign * Math.pow(abs, 563 / 256);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const A98RGBToGamma = (val) => {
|
|
17
|
+
let sign = val < 0 ? -1 : 1;
|
|
18
|
+
let abs = Math.abs(val);
|
|
19
|
+
return sign * Math.pow(abs, 256 / 563);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const A98RGBLinear = {
|
|
23
|
+
id: "a98-rgb-linear",
|
|
24
|
+
toXYZ_M: linear_A98RGB_to_XYZ_M,
|
|
25
|
+
fromXYZ_M: XYZ_to_linear_A98RGB_M,
|
|
26
|
+
toLMS_M: linear_A98RGB_to_LMS_M,
|
|
27
|
+
fromLMS_M: LMS_to_linear_A98RGB_M,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const A98RGB = {
|
|
31
|
+
id: "a98-rgb",
|
|
32
|
+
base: A98RGBLinear,
|
|
33
|
+
toBase: (vec, out = vec3()) => {
|
|
34
|
+
out[0] = A98RGBToLinear(vec[0]);
|
|
35
|
+
out[1] = A98RGBToLinear(vec[1]);
|
|
36
|
+
out[2] = A98RGBToLinear(vec[2]);
|
|
37
|
+
return out;
|
|
38
|
+
},
|
|
39
|
+
fromBase: (vec, out = vec3()) => {
|
|
40
|
+
out[0] = A98RGBToGamma(vec[0]);
|
|
41
|
+
out[1] = A98RGBToGamma(vec[1]);
|
|
42
|
+
out[2] = A98RGBToGamma(vec[2]);
|
|
43
|
+
return out;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const A98RGBGamut = {
|
|
48
|
+
space: A98RGB,
|
|
49
|
+
coefficients: OKLab_to_linear_A98RGB_coefficients,
|
|
50
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
linear_DisplayP3_to_LMS_M,
|
|
3
|
+
linear_DisplayP3_to_XYZ_M,
|
|
4
|
+
LMS_to_linear_DisplayP3_M,
|
|
5
|
+
XYZ_to_linear_DisplayP3_M,
|
|
6
|
+
OKLab_to_linear_DisplayP3_coefficients,
|
|
7
|
+
} from "../conversion_matrices.js";
|
|
8
|
+
import { sRGBGammaToLinearVec3, sRGBLinearToGammaVec3 } from "./util.js";
|
|
9
|
+
|
|
10
|
+
export const DisplayP3Linear = {
|
|
11
|
+
id: "display-p3-linear",
|
|
12
|
+
toXYZ_M: linear_DisplayP3_to_XYZ_M,
|
|
13
|
+
fromXYZ_M: XYZ_to_linear_DisplayP3_M,
|
|
14
|
+
toLMS_M: linear_DisplayP3_to_LMS_M,
|
|
15
|
+
fromLMS_M: LMS_to_linear_DisplayP3_M,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const DisplayP3 = {
|
|
19
|
+
id: "display-p3",
|
|
20
|
+
base: DisplayP3Linear,
|
|
21
|
+
toBase: sRGBGammaToLinearVec3,
|
|
22
|
+
fromBase: sRGBLinearToGammaVec3,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const DisplayP3Gamut = {
|
|
26
|
+
space: DisplayP3,
|
|
27
|
+
coefficients: OKLab_to_linear_DisplayP3_coefficients,
|
|
28
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Oklab and related spaces: OKLCH, OKHSV(sRGB), OKHSL(sRGB)
|
|
2
|
+
|
|
3
|
+
import { constrainAngle, vec3 } from "../util.js";
|
|
4
|
+
import {
|
|
5
|
+
OKHSLToOKLab,
|
|
6
|
+
OKHSVToOKLab,
|
|
7
|
+
OKLabToOKHSL,
|
|
8
|
+
OKLabToOKHSV,
|
|
9
|
+
} from "../okhsl.js";
|
|
10
|
+
|
|
11
|
+
import { sRGBGamut } from "./srgb.js";
|
|
12
|
+
|
|
13
|
+
// based on colorjs.io, could perhaps use a more specific number than this
|
|
14
|
+
const ACHROMATIC_EPSILON = (0.4 - 0.0) / 100000;
|
|
15
|
+
|
|
16
|
+
export const OKLab = {
|
|
17
|
+
id: "oklab",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const OKLCH = {
|
|
21
|
+
id: "oklch",
|
|
22
|
+
base: OKLab,
|
|
23
|
+
toBase: (oklch, out = vec3()) => {
|
|
24
|
+
// Note: newer version of Colorjs.io clamps oklch chroma
|
|
25
|
+
// However, this means that oklch(0.5, -0.36, 90) -> srgb will result in an in-gamut rgb
|
|
26
|
+
// which seems a bit odd; you'd expect it to be out of gamut. So we will leave
|
|
27
|
+
// chroma unclamped for this conversion.
|
|
28
|
+
// const C = Math.max(0, oklch[1]);
|
|
29
|
+
|
|
30
|
+
const C = oklch[1];
|
|
31
|
+
const H = oklch[2];
|
|
32
|
+
out[0] = oklch[0]; // L remains the same
|
|
33
|
+
out[1] = C * Math.cos((H * Math.PI) / 180); // a
|
|
34
|
+
out[2] = C * Math.sin((H * Math.PI) / 180); // b
|
|
35
|
+
return out;
|
|
36
|
+
},
|
|
37
|
+
fromBase: (oklab, out = vec3()) => {
|
|
38
|
+
// These methods are used for other polar forms as well, so we can't hardcode the ε
|
|
39
|
+
const a = oklab[1];
|
|
40
|
+
const b = oklab[2];
|
|
41
|
+
let isAchromatic =
|
|
42
|
+
Math.abs(a) < ACHROMATIC_EPSILON && Math.abs(b) < ACHROMATIC_EPSILON;
|
|
43
|
+
let hue = isAchromatic
|
|
44
|
+
? 0
|
|
45
|
+
: constrainAngle((Math.atan2(b, a) * 180) / Math.PI);
|
|
46
|
+
let C = isAchromatic ? 0 : Math.sqrt(a * a + b * b);
|
|
47
|
+
out[0] = oklab[0]; // L remains the same
|
|
48
|
+
out[1] = C; // Chroma
|
|
49
|
+
out[2] = hue; // Hue, in degrees [0 to 360)
|
|
50
|
+
return out;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const OKHSL = {
|
|
55
|
+
// Note: sRGB gamut only
|
|
56
|
+
// For other gamuts, use okhsl method directly
|
|
57
|
+
id: "okhsl",
|
|
58
|
+
base: OKLab,
|
|
59
|
+
toBase: (okhsl, out = vec3()) => OKHSLToOKLab(okhsl, sRGBGamut, out),
|
|
60
|
+
fromBase: (oklab, out = vec3()) => OKLabToOKHSL(oklab, sRGBGamut, out),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const OKHSV = {
|
|
64
|
+
// Note: sRGB gamut only
|
|
65
|
+
// For other gamuts, use okhsv method directly
|
|
66
|
+
id: "okhsv",
|
|
67
|
+
base: OKLab,
|
|
68
|
+
toBase: (okhsl, out = vec3()) => OKHSVToOKLab(okhsl, sRGBGamut, out),
|
|
69
|
+
fromBase: (oklab, out = vec3()) => OKLabToOKHSV(oklab, sRGBGamut, out),
|
|
70
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { vec3 } from "../util.js";
|
|
2
|
+
import {
|
|
3
|
+
linear_ProPhotoRGB_to_XYZ_M,
|
|
4
|
+
XYZ_to_linear_ProPhotoRGB_M,
|
|
5
|
+
} from "../conversion_matrices.js";
|
|
6
|
+
import { D50_to_D65_M, D65_to_D50_M } from "./xyz.js";
|
|
7
|
+
|
|
8
|
+
const Et = 1 / 512;
|
|
9
|
+
const Et2 = 16 / 512;
|
|
10
|
+
|
|
11
|
+
// Transfer curve is gamma 1.8 with a small linear portion
|
|
12
|
+
const ProPhotoRGBToLinear = (v) => (v < Et2 ? v / 16 : v ** 1.8);
|
|
13
|
+
const ProPhotoRGBToGamma = (v) => (v >= Et ? v ** (1 / 1.8) : 16 * v);
|
|
14
|
+
|
|
15
|
+
// Note: below is a possibly improved transfer function proposed by CSS Module 4 spec
|
|
16
|
+
// It is currently not matching the outputs of Colorjs.io when dealing with particular
|
|
17
|
+
// negative values, such as convert([1,1,1], OKlab, ProPhotoRGB)
|
|
18
|
+
|
|
19
|
+
// const ProPhotoRGBToLinear = (val) => {
|
|
20
|
+
// // convert an array of prophoto-rgb values
|
|
21
|
+
// // where in-gamut colors are in the range [0.0 - 1.0]
|
|
22
|
+
// // to linear light (un-companded) form.
|
|
23
|
+
// // Transfer curve is gamma 1.8 with a small linear portion
|
|
24
|
+
// let sign = val < 0 ? -1 : 1;
|
|
25
|
+
// let abs = Math.abs(val);
|
|
26
|
+
// return abs <= Et2 ? val / 16 : sign * Math.pow(abs, 1.8);
|
|
27
|
+
// };
|
|
28
|
+
|
|
29
|
+
// const ProPhotoRGBToGamma = (val) => {
|
|
30
|
+
// // convert an array of linear-light prophoto-rgb in the range 0.0-1.0
|
|
31
|
+
// // to gamma corrected form
|
|
32
|
+
// // Transfer curve is gamma 1.8 with a small linear portion
|
|
33
|
+
// let sign = val < 0 ? -1 : 1;
|
|
34
|
+
// let abs = Math.abs(val);
|
|
35
|
+
// return abs >= Et ? sign * Math.pow(abs, 1 / 1.8) : 16 * val;
|
|
36
|
+
// };
|
|
37
|
+
|
|
38
|
+
export const ProPhotoRGBLinear = {
|
|
39
|
+
id: "prophoto-rgb-linear",
|
|
40
|
+
adapt: {
|
|
41
|
+
// chromatic adaptation to and from D65
|
|
42
|
+
to: D50_to_D65_M,
|
|
43
|
+
from: D65_to_D50_M,
|
|
44
|
+
},
|
|
45
|
+
// Note these are in D50
|
|
46
|
+
toXYZ_M: linear_ProPhotoRGB_to_XYZ_M,
|
|
47
|
+
fromXYZ_M: XYZ_to_linear_ProPhotoRGB_M,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const ProPhotoRGB = {
|
|
51
|
+
id: "prophoto-rgb",
|
|
52
|
+
base: ProPhotoRGBLinear,
|
|
53
|
+
toBase: (vec, out = vec3()) => {
|
|
54
|
+
out[0] = ProPhotoRGBToLinear(vec[0]);
|
|
55
|
+
out[1] = ProPhotoRGBToLinear(vec[1]);
|
|
56
|
+
out[2] = ProPhotoRGBToLinear(vec[2]);
|
|
57
|
+
return out;
|
|
58
|
+
},
|
|
59
|
+
fromBase: (vec, out = vec3()) => {
|
|
60
|
+
out[0] = ProPhotoRGBToGamma(vec[0]);
|
|
61
|
+
out[1] = ProPhotoRGBToGamma(vec[1]);
|
|
62
|
+
out[2] = ProPhotoRGBToGamma(vec[2]);
|
|
63
|
+
return out;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Note: this is currently not supported, some overflows are occurring
|
|
68
|
+
// in the python script. Please file an issue or PR if you think you can help.
|
|
69
|
+
// export const ProPhotoRGBGamut = {
|
|
70
|
+
// space: ProPhotoRGB,
|
|
71
|
+
// coefficients: OKLab_to_linear_ProPhotoRGB_coefficients,
|
|
72
|
+
// };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { vec3 } from "../util.js";
|
|
2
|
+
import {
|
|
3
|
+
linear_Rec2020_to_LMS_M,
|
|
4
|
+
linear_Rec2020_to_XYZ_M,
|
|
5
|
+
LMS_to_linear_Rec2020_M,
|
|
6
|
+
XYZ_to_linear_Rec2020_M,
|
|
7
|
+
OKLab_to_linear_Rec2020_coefficients,
|
|
8
|
+
} from "../conversion_matrices.js";
|
|
9
|
+
|
|
10
|
+
const ALPHA = 1.09929682680944;
|
|
11
|
+
const BETA = 0.018053968510807;
|
|
12
|
+
|
|
13
|
+
const Rec2020ToLinear = (val) =>
|
|
14
|
+
val < BETA * 4.5 ? val / 4.5 : Math.pow((val + ALPHA - 1) / ALPHA, 1 / 0.45);
|
|
15
|
+
|
|
16
|
+
const Rec2020ToGamma = (val) =>
|
|
17
|
+
val >= BETA ? ALPHA * Math.pow(val, 0.45) - (ALPHA - 1) : 4.5 * val;
|
|
18
|
+
|
|
19
|
+
export const Rec2020Linear = {
|
|
20
|
+
id: "rec2020-linear",
|
|
21
|
+
toXYZ_M: linear_Rec2020_to_XYZ_M,
|
|
22
|
+
fromXYZ_M: XYZ_to_linear_Rec2020_M,
|
|
23
|
+
toLMS_M: linear_Rec2020_to_LMS_M,
|
|
24
|
+
fromLMS_M: LMS_to_linear_Rec2020_M,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Rec2020 = {
|
|
28
|
+
id: "rec2020",
|
|
29
|
+
base: Rec2020Linear,
|
|
30
|
+
toBase: (vec, out = vec3()) => {
|
|
31
|
+
out[0] = Rec2020ToLinear(vec[0]);
|
|
32
|
+
out[1] = Rec2020ToLinear(vec[1]);
|
|
33
|
+
out[2] = Rec2020ToLinear(vec[2]);
|
|
34
|
+
return out;
|
|
35
|
+
},
|
|
36
|
+
fromBase: (vec, out = vec3()) => {
|
|
37
|
+
out[0] = Rec2020ToGamma(vec[0]);
|
|
38
|
+
out[1] = Rec2020ToGamma(vec[1]);
|
|
39
|
+
out[2] = Rec2020ToGamma(vec[2]);
|
|
40
|
+
return out;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Rec2020Gamut = {
|
|
45
|
+
space: Rec2020,
|
|
46
|
+
coefficients: OKLab_to_linear_Rec2020_coefficients,
|
|
47
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
linear_sRGB_to_LMS_M,
|
|
3
|
+
linear_sRGB_to_XYZ_M,
|
|
4
|
+
LMS_to_linear_sRGB_M,
|
|
5
|
+
XYZ_to_linear_sRGB_M,
|
|
6
|
+
OKLab_to_linear_sRGB_coefficients,
|
|
7
|
+
} from "../conversion_matrices.js";
|
|
8
|
+
|
|
9
|
+
import { sRGBGammaToLinearVec3, sRGBLinearToGammaVec3 } from "./util.js";
|
|
10
|
+
|
|
11
|
+
export const sRGBLinear = {
|
|
12
|
+
id: "srgb-linear",
|
|
13
|
+
toXYZ_M: linear_sRGB_to_XYZ_M,
|
|
14
|
+
fromXYZ_M: XYZ_to_linear_sRGB_M,
|
|
15
|
+
toLMS_M: linear_sRGB_to_LMS_M,
|
|
16
|
+
fromLMS_M: LMS_to_linear_sRGB_M,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const sRGB = {
|
|
20
|
+
id: "srgb",
|
|
21
|
+
base: sRGBLinear,
|
|
22
|
+
toBase: sRGBGammaToLinearVec3,
|
|
23
|
+
fromBase: sRGBLinearToGammaVec3,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const sRGBGamut = {
|
|
27
|
+
space: sRGB,
|
|
28
|
+
coefficients: OKLab_to_linear_sRGB_coefficients,
|
|
29
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { vec3 } from "../util.js";
|
|
2
|
+
|
|
3
|
+
export const sRGBGammaToLinear = (val) => {
|
|
4
|
+
// convert a single channel value
|
|
5
|
+
// where in-gamut values are in the range [0 - 1]
|
|
6
|
+
// to linear light (un-companded) form.
|
|
7
|
+
// https://en.wikipedia.org/wiki/SRGB
|
|
8
|
+
// Extended transfer function:
|
|
9
|
+
// for negative values, linear portion is extended on reflection of axis,
|
|
10
|
+
// then reflected power function is used.
|
|
11
|
+
let sign = val < 0 ? -1 : 1;
|
|
12
|
+
let abs = Math.abs(val);
|
|
13
|
+
return abs <= 0.04045
|
|
14
|
+
? val / 12.92
|
|
15
|
+
: sign * Math.pow((abs + 0.055) / 1.055, 2.4);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const sRGBLinearToGamma = (val) => {
|
|
19
|
+
// convert a single channel linear-light value in range 0-1
|
|
20
|
+
// to gamma corrected form
|
|
21
|
+
// https://en.wikipedia.org/wiki/SRGB
|
|
22
|
+
// Extended transfer function:
|
|
23
|
+
// For negative values, linear portion extends on reflection
|
|
24
|
+
// of axis, then uses reflected pow below that
|
|
25
|
+
let sign = val < 0 ? -1 : 1;
|
|
26
|
+
let abs = Math.abs(val);
|
|
27
|
+
return abs > 0.0031308
|
|
28
|
+
? sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055)
|
|
29
|
+
: 12.92 * val;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const sRGBLinearToGammaVec3 = (vec, out = vec3()) => {
|
|
33
|
+
out[0] = sRGBLinearToGamma(vec[0]);
|
|
34
|
+
out[1] = sRGBLinearToGamma(vec[1]);
|
|
35
|
+
out[2] = sRGBLinearToGamma(vec[2]);
|
|
36
|
+
return out;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const sRGBGammaToLinearVec3 = (vec, out = vec3()) => {
|
|
40
|
+
out[0] = sRGBGammaToLinear(vec[0]);
|
|
41
|
+
out[1] = sRGBGammaToLinear(vec[1]);
|
|
42
|
+
out[2] = sRGBGammaToLinear(vec[2]);
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { vec3 } from "../util.js";
|
|
2
|
+
import { transform } from "../core.js";
|
|
3
|
+
import { LMS_to_XYZ_M, XYZ_to_LMS_M } from "../conversion_matrices.js";
|
|
4
|
+
|
|
5
|
+
// Bradford chromatic adaptation from D65 to D50
|
|
6
|
+
// The matrix below is the result of three operations:
|
|
7
|
+
// - convert from XYZ to retinal cone domain
|
|
8
|
+
// - scale components from one reference white to another
|
|
9
|
+
// - convert back to XYZ
|
|
10
|
+
// see https://github.com/LeaVerou/color.js/pull/354/files
|
|
11
|
+
export const D65_to_D50_M = [
|
|
12
|
+
[1.0479297925449969, 0.022946870601609652, -0.05019226628920524],
|
|
13
|
+
[0.02962780877005599, 0.9904344267538799, -0.017073799063418826],
|
|
14
|
+
[-0.009243040646204504, 0.015055191490298152, 0.7518742814281371],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// Bradford chromatic adaptation from D50 to D65
|
|
18
|
+
// See https://github.com/LeaVerou/color.js/pull/360/files
|
|
19
|
+
export const D50_to_D65_M = [
|
|
20
|
+
[0.955473421488075, -0.02309845494876471, 0.06325924320057072],
|
|
21
|
+
[-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
|
|
22
|
+
[0.012314014864481998, -0.020507649298898964, 1.330365926242124],
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const XYZD65ToD50 = (XYZ, out = vec3()) =>
|
|
26
|
+
transform(XYZ, D65_to_D50_M, out);
|
|
27
|
+
|
|
28
|
+
export const XYZD50ToD65 = (XYZ, out = vec3()) =>
|
|
29
|
+
transform(XYZ, D50_to_D65_M, out);
|
|
30
|
+
|
|
31
|
+
// XYZ using D65 whitepoint
|
|
32
|
+
export const XYZ = {
|
|
33
|
+
id: "xyz", // xyz-d65
|
|
34
|
+
toLMS_M: XYZ_to_LMS_M,
|
|
35
|
+
fromLMS_M: LMS_to_XYZ_M,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// XYZ using D50 whitepoint
|
|
39
|
+
export const XYZD50 = {
|
|
40
|
+
id: "xyz-d50",
|
|
41
|
+
base: XYZ,
|
|
42
|
+
toBase: XYZD50ToD65,
|
|
43
|
+
fromBase: XYZD65ToD50,
|
|
44
|
+
};
|
package/src/spaces.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { XYZ, XYZD50 } from "./spaces/xyz.js";
|
|
2
|
+
import { OKLab, OKLCH, OKHSV, OKHSL } from "./spaces/oklab.js";
|
|
3
|
+
import { sRGB, sRGBLinear, sRGBGamut } from "./spaces/srgb.js";
|
|
4
|
+
import {
|
|
5
|
+
DisplayP3,
|
|
6
|
+
DisplayP3Linear,
|
|
7
|
+
DisplayP3Gamut,
|
|
8
|
+
} from "./spaces/display-p3.js";
|
|
9
|
+
import { Rec2020, Rec2020Linear, Rec2020Gamut } from "./spaces/rec2020.js";
|
|
10
|
+
import { A98RGB, A98RGBLinear, A98RGBGamut } from "./spaces/a98-rgb.js";
|
|
11
|
+
import { ProPhotoRGB, ProPhotoRGBLinear } from "./spaces/prophoto-rgb.js";
|
|
12
|
+
|
|
13
|
+
export * from "./spaces/xyz.js";
|
|
14
|
+
export * from "./spaces/oklab.js";
|
|
15
|
+
export * from "./spaces/srgb.js";
|
|
16
|
+
export * from "./spaces/display-p3.js";
|
|
17
|
+
export * from "./spaces/rec2020.js";
|
|
18
|
+
export * from "./spaces/a98-rgb.js";
|
|
19
|
+
export * from "./spaces/prophoto-rgb.js";
|
|
20
|
+
|
|
21
|
+
export const listColorSpaces = () => {
|
|
22
|
+
return [
|
|
23
|
+
XYZ, // D65
|
|
24
|
+
XYZD50,
|
|
25
|
+
OKLab,
|
|
26
|
+
OKLCH,
|
|
27
|
+
OKHSV,
|
|
28
|
+
OKHSL,
|
|
29
|
+
sRGB,
|
|
30
|
+
sRGBLinear,
|
|
31
|
+
DisplayP3,
|
|
32
|
+
DisplayP3Linear,
|
|
33
|
+
Rec2020,
|
|
34
|
+
Rec2020Linear,
|
|
35
|
+
A98RGB,
|
|
36
|
+
A98RGBLinear,
|
|
37
|
+
ProPhotoRGB,
|
|
38
|
+
ProPhotoRGBLinear,
|
|
39
|
+
];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const listColorGamuts = () => {
|
|
43
|
+
return [sRGBGamut, DisplayP3Gamut, Rec2020Gamut, A98RGBGamut];
|
|
44
|
+
};
|
package/src/util.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const GAMUT_EPSILON = 0.000075;
|
|
2
|
+
|
|
3
|
+
export const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
|
|
4
|
+
|
|
5
|
+
export const lerp = (min, max, t) => min * (1 - t) + max * t;
|
|
6
|
+
|
|
7
|
+
export const degToRad = (n) => (n * Math.PI) / 180;
|
|
8
|
+
|
|
9
|
+
export const radToDeg = (n) => (n * 180) / Math.PI;
|
|
10
|
+
|
|
11
|
+
export const constrainAngle = (angle) => ((angle % 360) + 360) % 360;
|
|
12
|
+
|
|
13
|
+
export const hexToRGB = (str, out = vec3()) => {
|
|
14
|
+
let hex = str.replace(/#/, "");
|
|
15
|
+
if (hex.length === 3) {
|
|
16
|
+
// expand shorthand
|
|
17
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
18
|
+
} else if (hex.length > 6) {
|
|
19
|
+
// discard alpha
|
|
20
|
+
hex = hex.slice(0, 6);
|
|
21
|
+
}
|
|
22
|
+
const rgb = parseInt(hex, 16);
|
|
23
|
+
out[0] = ((rgb >> 16) & 0xff) / 0xff;
|
|
24
|
+
out[1] = ((rgb >> 8) & 0xff) / 0xff;
|
|
25
|
+
out[2] = (rgb & 0xff) / 0xff;
|
|
26
|
+
return out;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const RGBtoHex = (rgb) =>
|
|
30
|
+
`#${rgb.map((n) => floatToByte(n).toString(16).padStart(2, "0")).join("")}`;
|
|
31
|
+
|
|
32
|
+
export const isRGBInGamut = (lrgb, ep = GAMUT_EPSILON) => {
|
|
33
|
+
const r = lrgb[0];
|
|
34
|
+
const g = lrgb[1];
|
|
35
|
+
const b = lrgb[2];
|
|
36
|
+
return (
|
|
37
|
+
r >= -ep &&
|
|
38
|
+
r <= 1 + ep &&
|
|
39
|
+
g >= -ep &&
|
|
40
|
+
g <= 1 + ep &&
|
|
41
|
+
b >= -ep &&
|
|
42
|
+
b <= 1 + ep
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const clampedRGB = (rgb, out = vec3()) => {
|
|
47
|
+
out[0] = clamp(rgb[0], 0, 1);
|
|
48
|
+
out[1] = clamp(rgb[1], 0, 1);
|
|
49
|
+
out[2] = clamp(rgb[2], 0, 1);
|
|
50
|
+
return out;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const xyY_to_XYZ = (arg, out = vec3()) => {
|
|
54
|
+
let X, Y, Z, x, y;
|
|
55
|
+
x = arg[0];
|
|
56
|
+
y = arg[1];
|
|
57
|
+
Y = arg[2];
|
|
58
|
+
if (y === 0) {
|
|
59
|
+
out[0] = out[1] = out[2] = 0;
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
X = (x * Y) / y;
|
|
63
|
+
Z = ((1 - x - y) * Y) / y;
|
|
64
|
+
out[0] = X;
|
|
65
|
+
out[1] = Y;
|
|
66
|
+
out[2] = Z;
|
|
67
|
+
return out;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const XYZ_to_xyY = (arg, out = vec3()) => {
|
|
71
|
+
let sum, X, Y, Z;
|
|
72
|
+
X = arg[0];
|
|
73
|
+
Y = arg[1];
|
|
74
|
+
Z = arg[2];
|
|
75
|
+
sum = X + Y + Z;
|
|
76
|
+
if (sum === 0) {
|
|
77
|
+
out[0] = out[1] = 0;
|
|
78
|
+
out[2] = Y;
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
out[0] = X / sum;
|
|
82
|
+
out[1] = Y / sum;
|
|
83
|
+
out[2] = Y;
|
|
84
|
+
return out;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const floatToByte = (n) => clamp(Math.round(255 * n), 0, 255);
|
|
88
|
+
|
|
89
|
+
// Undocumented
|
|
90
|
+
|
|
91
|
+
export const vec3 = () => [0, 0, 0];
|
|
92
|
+
|
|
93
|
+
// export const normalizeHue = (hue) => ((hue = hue % 360) < 0 ? hue + 360 : hue);
|
|
94
|
+
|
|
95
|
+
// in degrees
|
|
96
|
+
// export const angle_delta = (angle1, angle2) => {
|
|
97
|
+
// const diff = ((angle2 - angle1 + 180) % 360) - 180;
|
|
98
|
+
// return diff < -180 ? diff + 360 : diff;
|
|
99
|
+
// };
|
|
100
|
+
|
|
101
|
+
// function shortAngleDistRad(a0, a1) {
|
|
102
|
+
// var max = Math.PI * 2;
|
|
103
|
+
// var da = (a1 - a0) % max;
|
|
104
|
+
// return ((2 * da) % max) - da;
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
// function shortAngleDistDeg(a0, a1) {
|
|
108
|
+
// var max = 360;
|
|
109
|
+
// var da = (a1 - a0) % max;
|
|
110
|
+
// return ((2 * da) % max) - da;
|
|
111
|
+
// }
|
|
112
|
+
|
|
113
|
+
// function lerpAngleRad(a0, a1, t) {
|
|
114
|
+
// return a0 + shortAngleDistRad(a0, a1) * t;
|
|
115
|
+
// }
|
|
116
|
+
|
|
117
|
+
// function lerpAngleDeg(a0, a1, t) {
|
|
118
|
+
// return radToDeg(lerpAngleRad(degToRad(a0), degToRad(a1), t));
|
|
119
|
+
// }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const EPSILON = Math.pow(2, -33); // ~= 0.0000000001
|
|
2
|
+
|
|
3
|
+
export default function arrayAlmostEqual(a, b, tolerance = EPSILON) {
|
|
4
|
+
if (!a || !b) return false;
|
|
5
|
+
if (a.length !== b.length) return false;
|
|
6
|
+
for (let i = 0; i < a.length; i++) {
|
|
7
|
+
const p0 = a[i];
|
|
8
|
+
const p1 = b[i];
|
|
9
|
+
if (p0 !== p1) {
|
|
10
|
+
const diff = Math.abs(p1 - p0);
|
|
11
|
+
if (diff > tolerance) return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
package/test/banner.png
ADDED
|
Binary file
|