f32color.mx raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package gio
4
5 import (
6 "image/color"
7 "math"
8 )
9
10 // RGBA is a 32 bit floating point linear premultiplied color space.
11 type RGBA struct {
12 R, G, B, A float32
13 }
14
15 // Array returns rgba values in a [4]float32 array.
16 func (rgba RGBA) Array() [4]float32 {
17 return [4]float32{rgba.R, rgba.G, rgba.B, rgba.A}
18 }
19
20 // Float32 returns r, g, b, a values.
21 func (col RGBA) Float32() (r, g, b, a float32) {
22 return col.R, col.G, col.B, col.A
23 }
24
25 // SRGB converts from linear to sRGB color space.
26 func (col RGBA) SRGB() color.NRGBA {
27 if col.A == 0 {
28 return color.NRGBA{}
29 }
30 return color.NRGBA{
31 R: uint8(linearTosRGB(col.R/col.A)*255 + .5),
32 G: uint8(linearTosRGB(col.G/col.A)*255 + .5),
33 B: uint8(linearTosRGB(col.B/col.A)*255 + .5),
34 A: uint8(col.A*255 + .5),
35 }
36 }
37
38 // Luminance calculates the relative luminance of a linear RGBA color.
39 // Normalized to 0 for black and 1 for white.
40 //
41 // See https://www.w3.org/TR/WCAG20/#relativeluminancedef for more details
42 func (col RGBA) Luminance() float32 {
43 return 0.2126*col.R + 0.7152*col.G + 0.0722*col.B
44 }
45
46 // Opaque returns the color without alpha component.
47 func (col RGBA) Opaque() RGBA {
48 col.A = 1.0
49 return col
50 }
51
52 // LinearFromSRGB converts from col in the sRGB colorspace to RGBA.
53 func LinearFromSRGB(col color.NRGBA) RGBA {
54 af := float32(col.A) / 0xFF
55 t := srgb8ToLinear()
56 return RGBA{
57 R: t[col.R] * af,
58 G: t[col.G] * af,
59 B: t[col.B] * af,
60 A: af,
61 }
62 }
63
64 // NRGBAToRGBA converts from non-premultiplied sRGB color to premultiplied sRGB color.
65 //
66 // Each component in the result is sRGBToLinear(c * alpha), where c
67 // is the linear color.
68 func NRGBAToRGBA(col color.NRGBA) color.RGBA {
69 if col.A == 0xFF {
70 return color.RGBA(col)
71 }
72 c := LinearFromSRGB(col)
73 return color.RGBA{
74 R: uint8(linearTosRGB(c.R)*255 + .5),
75 G: uint8(linearTosRGB(c.G)*255 + .5),
76 B: uint8(linearTosRGB(c.B)*255 + .5),
77 A: col.A,
78 }
79 }
80
81 // NRGBAToLinearRGBA converts from non-premultiplied sRGB color to premultiplied linear RGBA color.
82 //
83 // Each component in the result is c * alpha, where c is the linear color.
84 func NRGBAToLinearRGBA(col color.NRGBA) color.RGBA {
85 if col.A == 0xFF {
86 return color.RGBA(col)
87 }
88 c := LinearFromSRGB(col)
89 return color.RGBA{
90 R: uint8(c.R*255 + .5),
91 G: uint8(c.G*255 + .5),
92 B: uint8(c.B*255 + .5),
93 A: col.A,
94 }
95 }
96
97 // RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color.
98 func RGBAToNRGBA(col color.RGBA) color.NRGBA {
99 if col.A == 0xFF {
100 return color.NRGBA(col)
101 }
102
103 linear := RGBA{
104 R: sRGBToLinear(float32(col.R) / 0xff),
105 G: sRGBToLinear(float32(col.G) / 0xff),
106 B: sRGBToLinear(float32(col.B) / 0xff),
107 A: float32(col.A) / 0xff,
108 }
109
110 return linear.SRGB()
111 }
112
113 // linearTosRGB transforms color value from linear to sRGB.
114 func linearTosRGB(c float32) float32 {
115 // Formula from EXT_sRGB.
116 switch {
117 case c <= 0:
118 return 0
119 case 0 < c && c < 0.0031308:
120 return 12.92 * c
121 case 0.0031308 <= c && c < 1:
122 return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055
123 }
124
125 return 1
126 }
127
128 // sRGBToLinear transforms color value from sRGB to linear.
129 func sRGBToLinear(c float32) float32 {
130 // Formula from EXT_sRGB.
131 if c <= 0.04045 {
132 return c / 12.92
133 } else {
134 return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
135 }
136 }
137
138 // MulAlpha applies the alpha to the color.
139 func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
140 c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
141 return c
142 }
143
144 // Disabled blends color towards the luminance and multiplies alpha.
145 // Blending towards luminance will desaturate the color.
146 // Multiplying alpha blends the color together more with the background.
147 func Disabled(c color.NRGBA) (d color.NRGBA) {
148 const r = 80 // blend ratio
149 lum := approxLuminance(c)
150 d = mix(c, color.NRGBA{A: c.A, R: lum, G: lum, B: lum}, r)
151 d = MulAlpha(d, 128+32)
152 return
153 }
154
155 // Hovered blends dark colors towards white, and light colors towards
156 // black. It is approximate because it operates in non-linear sRGB space.
157 func Hovered(c color.NRGBA) (h color.NRGBA) {
158 if c.A == 0 {
159 // Provide a reasonable default for transparent widgets.
160 return color.NRGBA{A: 0x44, R: 0x88, G: 0x88, B: 0x88}
161 }
162 const ratio = 0x20
163 m := color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: c.A}
164 if approxLuminance(c) > 128 {
165 m = color.NRGBA{A: c.A}
166 }
167 return mix(m, c, ratio)
168 }
169
170 // mix mixes c1 and c2 weighted by (1 - a/256) and a/256 respectively.
171 func mix(c1, c2 color.NRGBA, a uint8) color.NRGBA {
172 ai := int32(a)
173 return color.NRGBA{
174 R: byte((int32(c1.R)*ai + int32(c2.R)*(256-ai)) / 256),
175 G: byte((int32(c1.G)*ai + int32(c2.G)*(256-ai)) / 256),
176 B: byte((int32(c1.B)*ai + int32(c2.B)*(256-ai)) / 256),
177 A: byte((int32(c1.A)*ai + int32(c2.A)*(256-ai)) / 256),
178 }
179 }
180
181 // approxLuminance is a fast approximate version of RGBA.Luminance.
182 func approxLuminance(c color.NRGBA) byte {
183 const (
184 r = 13933 // 0.2126 * 256 * 256
185 g = 46871 // 0.7152 * 256 * 256
186 b = 4732 // 0.0722 * 256 * 256
187 t = r + g + b
188 )
189 return byte((r*int32(c.R) + g*int32(c.G) + b*int32(c.B)) / t)
190 }
191
192 // srgb8ToLinear returns the lookup table: sRGBToLinear(float32(index)/0xff)
193 func srgb8ToLinear() [256]float32 {
194 return [256]float32{
195 0, 0.000303527, 0.000607054, 0.000910581, 0.001214108, 0.001517635, 0.001821162, 0.0021246888, 0.002428216, 0.002731743, 0.00303527, 0.0033465363, 0.0036765079, 0.004024718, 0.004391443, 0.004776954,
196 0.005181518, 0.0056053926, 0.006048834, 0.0065120924, 0.0069954116, 0.007499033, 0.008023194, 0.008568126, 0.009134059, 0.00972122, 0.010329825, 0.010960096, 0.011612247, 0.012286489, 0.0129830325, 0.013702083,
197 0.014443846, 0.015208517, 0.015996296, 0.016807377, 0.017641956, 0.01850022, 0.019382365, 0.020288566, 0.021219013, 0.022173887, 0.023153368, 0.024157634, 0.025186861, 0.026241226, 0.027320895, 0.028426042,
198 0.029556837, 0.030713446, 0.031896036, 0.033104766, 0.03433981, 0.035601318, 0.03688945, 0.03820437, 0.039546244, 0.040915202, 0.042311415, 0.043735035, 0.04518621, 0.04666509, 0.048171826, 0.049706567,
199 0.05126947, 0.05286066, 0.05448029, 0.056128502, 0.05780544, 0.059511248, 0.06124608, 0.06301004, 0.06480329, 0.06662596, 0.06847819, 0.07036012, 0.07227187, 0.07421359, 0.0761854, 0.078187436,
200 0.080219835, 0.08228272, 0.08437622, 0.08650047, 0.08865561, 0.09084174, 0.09305899, 0.09530749, 0.09758737, 0.09989875, 0.102241755, 0.10461651, 0.10702312, 0.10946173, 0.11193245, 0.11443539,
201 0.11697068, 0.11953844, 0.122138806, 0.12477185, 0.12743771, 0.1301365, 0.13286835, 0.13563335, 0.13843164, 0.14126332, 0.14412849, 0.14702728, 0.1499598, 0.15292618, 0.15592648, 0.15896088,
202 0.16202942, 0.16513222, 0.16826941, 0.17144111, 0.1746474, 0.17788842, 0.18116425, 0.18447499, 0.18782078, 0.19120169, 0.19461782, 0.19806932, 0.20155625, 0.20507872, 0.20863685, 0.21223074,
203 0.21586055, 0.21952623, 0.223228, 0.2269659, 0.23074009, 0.23455067, 0.23839766, 0.24228121, 0.24620141, 0.25015837, 0.25415218, 0.25818294, 0.26225075, 0.2663557, 0.27049786, 0.2746774,
204 0.27889434, 0.28314883, 0.2874409, 0.29177073, 0.29613835, 0.30054384, 0.30498737, 0.30946898, 0.31398878, 0.31854683, 0.32314327, 0.32777816, 0.33245158, 0.33716366, 0.34191447, 0.3467041,
205 0.35153273, 0.35640025, 0.3613069, 0.36625272, 0.37123778, 0.37626222, 0.3813261, 0.38642955, 0.39157256, 0.39675534, 0.40197787, 0.4072403, 0.4125427, 0.41788515, 0.42326775, 0.42869058,
206 0.4341537, 0.43965724, 0.44520128, 0.45078585, 0.4564111, 0.46207705, 0.46778387, 0.47353154, 0.47932023, 0.48515, 0.4910209, 0.49693304, 0.5028866, 0.50888145, 0.5149178, 0.5209957,
207 0.5271153, 0.53327656, 0.5394796, 0.5457246, 0.55201155, 0.5583405, 0.56471163, 0.5711249, 0.5775806, 0.58407855, 0.59061897, 0.5972019, 0.6038274, 0.6104957, 0.61720663, 0.6239605,
208 0.6307572, 0.63759696, 0.64447975, 0.6514057, 0.6583749, 0.66538733, 0.6724432, 0.67954254, 0.6866855, 0.6938719, 0.7011021, 0.70837593, 0.71569365, 0.7230553, 0.7304609, 0.73791057,
209 0.74540436, 0.7529423, 0.76052463, 0.7681513, 0.77582234, 0.7835379, 0.79129803, 0.79910284, 0.80695236, 0.8148467, 0.82278585, 0.83076996, 0.8387991, 0.84687334, 0.8549927, 0.8631573,
210 0.8713672, 0.87962234, 0.8879232, 0.89626944, 0.90466136, 0.9130987, 0.92158204, 0.9301109, 0.9386859, 0.9473066, 0.9559735, 0.9646863, 0.9734455, 0.9822506, 0.9911022, 1,
211 }
212 }
213