-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathoklab.go
139 lines (115 loc) · 3.35 KB
/
oklab.go
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Package oklab implements the Oklab color space, as described at https://bottosson.github.io/posts/oklab/
package oklab
// L: 0.000000–1.000000
// A: -0.233888–0.276216
// B: -0.311528–0.198570
// L: 0.000000–1.000000
// C: 0.000000–0.322491
// H: -3.141592–3.141592
import (
"image/color"
"math"
)
type Oklab struct {
L float64 // Perceived lightness
A float64 // How green/red the color is
B float64 // How blue/yellow the color is
}
type Oklch struct {
L float64 // Perceived lightness
C float64 // Chroma
H float64 // Hue
}
var OklabModel = color.ModelFunc(oklabModel)
var OklchModel = color.ModelFunc(oklchModel)
// See image.Color.
func (c Oklab) RGBA() (uint32, uint32, uint32, uint32) {
r, g, b := c.SRGB()
r, g, b = clampf(r), clampf(g), clampf(b)
return (uint32(0x1fffe*r) + 1) >> 1, (uint32(0x1fffe*g) + 1) >> 1, (uint32(0x1fffe*b) + 1) >> 1, 0xffff
}
// Convert to linear sRGB.
// See https://bottosson.github.io/posts/oklab/
func (c Oklab) LinearSRGB() (float64, float64, float64) {
l_ := c.L + 0.3963377774*c.A + 0.2158037573*c.B
m_ := c.L - 0.1055613458*c.A - 0.0638541728*c.B
s_ := c.L - 0.0894841775*c.A - 1.2914855480*c.B
l := l_ * l_ * l_
m := m_ * m_ * m_
s := s_ * s_ * s_
r := 4.0767416621*l - 3.3077115913*m + 0.2309699292*s
g := -1.2684380046*l + 2.6097574011*m - 0.3413193965*s
b := -0.0041960863*l - 0.7034186147*m + 1.7076147010*s
return r, g, b
}
// Convert to sRGB.
// See https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
func (c Oklab) SRGB() (float64, float64, float64) {
r, g, b := c.LinearSRGB()
return linearSrgbToSrgb(r), linearSrgbToSrgb(g), linearSrgbToSrgb(b)
}
// Convert to LCh, which is Oklab in polar.
func (c Oklab) Oklch() Oklch {
return Oklch{
L: c.L,
C: math.Sqrt(c.A*c.A + c.B*c.B),
H: math.Atan2(c.B, c.A),
}
}
// See image.Color.
func (c Oklch) RGBA() (uint32, uint32, uint32, uint32) {
return c.Oklab().RGBA()
}
// Convert to Oklab.
func (c Oklch) Oklab() Oklab {
return Oklab{
L: c.L,
A: c.C * math.Cos(c.H),
B: c.C * math.Sin(c.H),
}
}
func oklabModel(c color.Color) color.Color {
r8, g8, b8, a8 := c.RGBA()
r := float64(r8) / float64(a8)
g := float64(g8) / float64(a8)
b := float64(b8) / float64(a8)
r, g, b = srgbToLinearSrgb(r), srgbToLinearSrgb(g), srgbToLinearSrgb(b)
l := 0.4122214708*r + 0.5363325363*g + 0.0514459929*b
m := 0.2119034982*r + 0.6806995451*g + 0.1073969566*b
s := 0.0883024619*r + 0.2817188376*g + 0.6299787005*b
l_ := math.Cbrt(l)
m_ := math.Cbrt(m)
s_ := math.Cbrt(s)
return Oklab{
L: 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_,
A: 1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_,
B: 0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_,
}
}
func oklchModel(c color.Color) color.Color {
return oklabModel(c).(Oklab).Oklch()
}
// Convert a linear sRGB color component to sRGB.
// See https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
func linearSrgbToSrgb(x float64) float64 {
if x >= 0.0031308 {
return 1.055*math.Pow(x, 1.0/2.4) - 0.055
}
return 12.92 * x
}
// Convert an sRGB color component to linear sRGB.
// See https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
func srgbToLinearSrgb(x float64) float64 {
if x >= 0.04045 {
return math.Pow((x+0.055)/(1+0.055), 2.4)
}
return x / 12.92
}
func clampf(x float64) float64 {
if x < 0 {
return 0
} else if x > 1 {
return 1
}
return x
}