-
Notifications
You must be signed in to change notification settings - Fork 1
/
okhsv.js
125 lines (98 loc) · 2.7 KB
/
okhsv.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { toLinear } from "./linear.js";
import { fromOklab } from "./oklab.js";
import {
oklabToLinear,
linearToOklab,
toe,
toeInv,
getStMax,
TAU,
} from "./utils.js";
/**
* @typedef {number[]} okhsv
*
* All components in the range 0 <= x <= 1
* @see {@link https://bottosson.github.io/posts/colorpicker/#hsv-2}
*/
const S0 = 0.5;
/**
* Updates a color based on Okhsv values and alpha.
* @alias module:pex-color.fromOkhsv
* @param {import("./color.js").color} color
* @param {number} h
* @param {number} s
* @param {number} v
* @param {number} [α]
* @returns {import("./color.js").color}
*/
export function fromOkhsv(color, h, s, v, α) {
let L = toeInv(v);
let a = 0; // null
let b = 0; // null
// Avoid processing gray or colors with undefined hues
if (L !== 0 && s !== 0) {
const a_ = Math.cos(TAU * h);
const b_ = Math.sin(TAU * h);
const [S, T] = getStMax(a_, b_);
const k = 1 - S0 / S;
const Lv = 1 - (s * S0) / (S0 + T - T * k * s);
const Cv = (s * T * S0) / (S0 + T - T * k * s);
L = v * Lv;
let C = v * Cv;
const Lvt = toeInv(Lv);
const Cvt = (Cv * Lvt) / Lv;
const Lnew = toeInv(L);
C = (C * Lnew) / L;
L = Lnew;
oklabToLinear(Lvt, a_ * Cvt, b_ * Cvt, color);
const scaleL = Math.cbrt(1 / Math.max(color[0], color[1], color[2], 0));
L = L * scaleL;
C = C * scaleL;
a = C * a_;
b = C * b_;
}
return fromOklab(color, L, a, b, α);
}
/**
* Returns an Okhsv representation of a given color.
* @alias module:pex-color.toOkhsv
* @param {import("./color.js").color} color
* @param {Array} out
* @returns {okhsv}
*/
export function toOkhsv(color, out = []) {
toLinear(color, out);
linearToOklab(out[0], out[1], out[2], out);
const H = 0.5 + (0.5 * Math.atan2(-out[2], -out[1])) / Math.PI;
let L = out[0];
let C = Math.sqrt(out[1] * out[1] + out[2] * out[2]);
if (L !== 0 && L !== 1 && C !== 0) {
const a_ = out[1] / C;
const b_ = out[2] / C;
const [S, T] = getStMax(a_, b_);
const t = T / (C + L * T);
const Lv = t * L;
const Cv = t * C;
const Lvt = toeInv(Lv);
const Cvt = (Cv * Lvt) / Lv;
oklabToLinear(Lvt, a_ * Cvt, b_ * Cvt, out);
const scaleL = Math.cbrt(1 / Math.max(out[0], out[1], out[2], 0));
L = L / scaleL;
C = C / scaleL;
const toeL = toe(L);
C = (C * toeL) / L;
out[1] = ((S0 + T) * Cv) / (T * S0 + T * (1 - S0 / S) * Cv);
out[2] = toeL / Lv;
} else {
out[1] = 0;
out[2] = toe(L);
}
// Epsilon for saturation just needs to be sufficiently close when denoting achromatic
const ε = 1e-4;
if (Math.abs(out[1]) < ε || out[2] === 0) {
out[0] = 0; // null
} else {
out[0] = H;
}
return out;
}