-
Notifications
You must be signed in to change notification settings - Fork 1
/
colors.go
151 lines (129 loc) · 9.37 KB
/
colors.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
140
141
142
143
144
145
146
147
148
149
150
151
package pix
import (
"fmt"
"math"
"os"
"sort"
)
// Note: We assume colors are given to us in nonlinear srgb, which
// we convert to linear RGB and then OkLab.
// Color represents a color in linear RGB or OkLab
type Color struct{ x, y, z uint8 }
// quantize a float in [0,1] to a uint8
func quantize(x float64) uint8 { return uint8(255*x + 0.5) }
// remap a uint8 to [0, 1] float
func invQuantize(x uint8) float64 { return float64(x) / 255 }
// remap from [0, 1] to [lo, hi]
func remap(x, lo, hi float64) float64 { return (x - lo) / (hi - lo) }
// remap from [lo, hi] to [0, 1]
func invRemap(x, lo, hi float64) float64 { return lo + x*(hi-lo) }
func clamp(x, lo, hi float64) float64 {
if x < lo {
return lo
}
if x > hi {
return hi
}
return x
}
// Minimum and maximum values for OkLab's a and b parameters
// found by enumerating all 8-bit RGB colors and taking the extrema.
// The L parameter extent is [0, 1], and doesn't need to be remapped.
const aLo, aHi = -0.23388757418790818, 0.2762167534925238
const bLo, bHi = -0.3115281476783751, 0.19856975465179516
func rgbToOkLab(rgb Color) Color {
L, a, b := linear_srgb_to_oklab(
toLinearRGB(rgb.x),
toLinearRGB(rgb.y),
toLinearRGB(rgb.z))
return Color{
quantize(L),
// Rescaling these by translation leaves a lot of dynamic range on the table.
// Could compute a tighter bounding cube by finding the largest scale factor
// by which to linearly rescale all dimensions s. t. the smallest and largest
// values across any channel are 0 and 1.
quantize(a - aLo),
quantize(b - bLo)}
}
func okLabCodeToRgb(code MortonCode) (uint8, uint8, uint8) {
r, g, b := oklab_to_linear_srgb(
invQuantize(mortonX(code)),
invQuantize(mortonY(code))+aLo,
invQuantize(mortonZ(code))+bLo)
// floating-point imprecision can cause values to exceed 1
r, g, b = clamp(r, 0, 1), clamp(g, 0, 1), clamp(b, 0, 1)
return toNonlinearRGBLUT(r), toNonlinearRGBLUT(g), toNonlinearRGBLUT(b)
}
// note: can also be optimize w/ a lookup table if needed.
func toLinearRGB(x uint8) float64 {
// remap to [0, 1]
y := invQuantize(x)
// apply the inverse srgb nonlinearity
if y >= 0.04045 {
y = math.Pow((y+0.055)/(1+0.055), 2.4)
} else {
y = y / 12.92
}
return y
}
// this function takes a long time for larger images because math.Pow is slow.
// can we make a size-256 lookup table somehow?
var toNonlinearRGBTable = [256]float64{0, 0.00016000160001600016, 0.0004600046000460005, 0.0007600076000760008, 0.0010700107001070012, 0.0013700137001370013, 0.0016700167001670017, 0.001980019800198002, 0.0022800228002280024, 0.0025800258002580028, 0.002890028900289003, 0.0031900319003190032, 0.0035100351003510034, 0.003850038500385004, 0.004210042100421005, 0.004590045900459004, 0.004980049800498005, 0.005400054000540005, 0.0058300583005830055, 0.006280062800628006, 0.006760067600676007, 0.007250072500725007, 0.007760077600776008, 0.008300083000830008, 0.00885008850088501, 0.00943009430094301, 0.01003010030100301, 0.01065010650106501, 0.011290112901129011, 0.011950119501195011, 0.012640126401264013, 0.013340133401334013, 0.014070140701407014, 0.014830148301483015, 0.015600156001560016, 0.016400164001640016, 0.017230172301723017, 0.018070180701807017, 0.018940189401894018, 0.01984019840198402, 0.02076020760207602, 0.021700217002170022, 0.022670226702267024, 0.023660236602366023, 0.024670246702467025, 0.025720257202572025, 0.026780267802678028, 0.02787027870278703, 0.02899028990289903, 0.03014030140301403, 0.03131031310313103, 0.032500325003250036, 0.03372033720337203, 0.034970349703497036, 0.03625036250362504, 0.037550375503755035, 0.03888038880388804, 0.04023040230402304, 0.04161041610416104, 0.043020430204302046, 0.044460444604446044, 0.045930459304593045, 0.04742047420474205, 0.048940489404894046, 0.05049050490504905, 0.052070520705207055, 0.053670536705367054, 0.055310553105531055, 0.056970569705697055, 0.05866058660586606, 0.06038060380603806, 0.06213062130621306, 0.06391063910639107, 0.06572065720657207, 0.06755067550675507, 0.06942069420694207, 0.07132071320713207, 0.07324073240732408, 0.07520075200752008, 0.07719077190771907, 0.07920079200792007, 0.08125081250812508, 0.08333083330833309, 0.08544085440854408, 0.08758087580875809, 0.0897508975089751, 0.0919509195091951, 0.09418094180941809, 0.09645096450964509, 0.0987409874098741, 0.1010710107101071, 0.1034310343103431, 0.1058210582105821, 0.10824108241082411, 0.11070110701107011, 0.11318113181131811, 0.11570115701157012, 0.11825118251182512, 0.12084120841208412, 0.12345123451234512, 0.12610126101261013, 0.12879128791287914, 0.13150131501315013, 0.13425134251342513, 0.13703137031370313, 0.13985139851398515, 0.14270142701427013, 0.14558145581455814, 0.14849148491484915, 0.15144151441514414, 0.15443154431544315, 0.15744157441574416, 0.16049160491604916, 0.16358163581635815, 0.16670166701667016, 0.16985169851698517, 0.17304173041730417, 0.17627176271762718, 0.17953179531795319, 0.1828218282182822, 0.18615186151861518, 0.1895118951189512, 0.1929119291192912, 0.1963419634196342, 0.1998119981199812, 0.2033220332203322, 0.2068620686206862, 0.2104321043210432, 0.2140421404214042, 0.21769217692176923, 0.2213822138221382, 0.22510225102251022, 0.22885228852288522, 0.23264232642326424, 0.23647236472364724, 0.24034240342403423, 0.24424244242442425, 0.24818248182481825, 0.2521525215252153, 0.25617256172561725, 0.26021260212602126, 0.26430264302643025, 0.2684226842268423, 0.27259272592725925, 0.2767827678276783, 0.28102281022810227, 0.2852928529285293, 0.2896028960289603, 0.2939529395293953, 0.2983429834298343, 0.3027630276302763, 0.30723307233072333, 0.3117331173311733, 0.3162631626316263, 0.3208432084320843, 0.32546325463254633, 0.33011330113301135, 0.33480334803348033, 0.33954339543395434, 0.34431344313443135, 0.34911349113491136, 0.35396353963539634, 0.35885358853588534, 0.36378363783637835, 0.36874368743687436, 0.3737537375373754, 0.3787937879378794, 0.3838738387383874, 0.3890038900389004, 0.39416394163941637, 0.3993639936399364, 0.4046040460404604, 0.40989409894098944, 0.4152141521415214, 0.4205742057420574, 0.42597425974259745, 0.4314243142431424, 0.43690436904369045, 0.44242442424424244, 0.44799447994479946, 0.4535945359453595, 0.45924459244592447, 0.4649346493464935, 0.4706547065470655, 0.47642476424764246, 0.48223482234822346, 0.48808488084880847, 0.4939749397493975, 0.4999049990499905, 0.5058850588505885, 0.5118951189511896, 0.5179551795517955, 0.5240552405524055, 0.5301953019530196, 0.5363753637536376, 0.5426054260542605, 0.5488654886548866, 0.5551755517555176, 0.5615256152561525, 0.5679156791567915, 0.5743557435574356, 0.5808258082580826, 0.5873458734587346, 0.5939059390593906, 0.6005160051600515, 0.6071660716607166, 0.6138461384613846, 0.6205862058620586, 0.6273562735627356, 0.6341763417634176, 0.6410364103641036, 0.6479464794647947, 0.6548865488654887, 0.6618766187661876, 0.6689166891668916, 0.6759967599675997, 0.6831168311683117, 0.6902769027690276, 0.6974869748697486, 0.7047370473704737, 0.7120371203712037, 0.7193771937719378, 0.7267572675726758, 0.7341873418734187, 0.7416574165741657, 0.7491774917749178, 0.7567375673756738, 0.7643376433764337, 0.7719877198771987, 0.7796777967779678, 0.7874178741787418, 0.7951979519795198, 0.8030280302803028, 0.8108981089810898, 0.8188181881818818, 0.8267782677826778, 0.8347883478834789, 0.8428384283842838, 0.8509285092850929, 0.8590785907859079, 0.8672586725867258, 0.8754987549875499, 0.8837688376883769, 0.8920989209892098, 0.9004690046900469, 0.9088790887908879, 0.9173391733917339, 0.9258492584925849, 0.9343993439934399, 0.9429994299943, 0.951639516395164, 0.960329603296033, 0.969069690696907, 0.977849778497785, 0.986679866798668, 0.995549955499555}
func toNonlinearRGBLUT(x float64) uint8 {
// return toNonlinearRGB(x)
return uint8(sort.Search(256, func(i int) bool { return toNonlinearRGBTable[i] > x }) - 1)
}
func verifyNonlinearRGBLookupTable() {
n := 100000
for i := 0; i < n; i++ {
x := float64(i) / float64(n-1)
want := toNonlinearRGB(x)
got := toNonlinearRGBLUT(x)
if want != got {
fmt.Println("Inconsistent results for", x, ": want", want, "got", got)
os.Exit(1)
}
}
}
func generateNonlinearRGBLookupTable() {
prev := uint8(255)
n := 100000
fmt.Print("var toNonlinearRGBTable = [256]float64{")
for i := 0; i < n; i++ {
x := float64(i) / float64(n-1)
cur := toNonlinearRGB(x)
if cur != prev {
fmt.Print(x, ", ")
prev = cur
}
}
fmt.Print("}")
}
func toNonlinearRGB(x float64) uint8 {
// apply the srgb nonlinearity
if x >= 0.0031308 {
x = 1.055*math.Pow(x, 1/2.4) - 0.055
} else {
x = 12.92 * x
}
return quantize(x)
}
// The original code uses float32, but Go's built-in math.Cbrt is float64-only
func linear_srgb_to_oklab(r, g, b float64) (float64, float64, float64) {
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_, m_, s_ := math.Cbrt(l), math.Cbrt(m), math.Cbrt(s)
return 0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_,
1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_,
0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_
}
func oklab_to_linear_srgb(L, a, b float64) (float64, float64, float64) {
l_ := L + 0.3963377774*a + 0.2158037573*b
m_ := L - 0.1055613458*a - 0.0638541728*b
s_ := L - 0.0894841775*a - 1.2914855480*b
l, m, s := l_*l_*l_, m_*m_*m_, s_*s_*s_
return +4.0767416621*l - 3.3077115913*m + 0.2309699292*s,
-1.2684380046*l + 2.6097574011*m - 0.3413193965*s,
-0.0041960863*l - 0.7034186147*m + 1.7076147010*s
}