rgba.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package f32color
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 // SRGBA 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 return RGBA{
56 R: sRGBToLinear(float32(col.R)/0xff) * af,
57 G: sRGBToLinear(float32(col.G)/0xff) * af,
58 B: sRGBToLinear(float32(col.B)/0xff) * af,
59 A: af,
60 }
61 }
62
63 // NRGBAToRGBA converts from non-premultiplied sRGB color to premultiplied sRGB color.
64 //
65 // Each component in the result is `sRGBToLinear(c * alpha)`, where `c`
66 // is the linear color.
67 func NRGBAToRGBA(col color.NRGBA) color.RGBA {
68 if col.A == 0xFF {
69 return color.RGBA(col)
70 }
71 c := LinearFromSRGB(col)
72 return color.RGBA{
73 R: uint8(linearTosRGB(c.R)*255 + .5),
74 G: uint8(linearTosRGB(c.G)*255 + .5),
75 B: uint8(linearTosRGB(c.B)*255 + .5),
76 A: col.A,
77 }
78 }
79
80 // NRGBAToLinearRGBA converts from non-premultiplied sRGB color to premultiplied linear RGBA color.
81 //
82 // Each component in the result is `c * alpha`, where `c` is the linear color.
83 func NRGBAToLinearRGBA(col color.NRGBA) color.RGBA {
84 if col.A == 0xFF {
85 return color.RGBA(col)
86 }
87 c := LinearFromSRGB(col)
88 return color.RGBA{
89 R: uint8(c.R*255 + .5),
90 G: uint8(c.G*255 + .5),
91 B: uint8(c.B*255 + .5),
92 A: col.A,
93 }
94 }
95
96 // NRGBAToRGBA_PostAlpha converts from non-premultiplied sRGB color to premultiplied sRGB color.
97 //
98 // Each component in the result is `sRGBToLinear(c) * alpha`, where `c`
99 // is the linear color.
100 func NRGBAToRGBA_PostAlpha(col color.NRGBA) color.RGBA {
101 if col.A == 0xFF {
102 return color.RGBA(col)
103 } else if col.A == 0x00 {
104 return color.RGBA{}
105 }
106 return color.RGBA{
107 R: uint8(uint32(col.R) * uint32(col.A) / 0xFF),
108 G: uint8(uint32(col.G) * uint32(col.A) / 0xFF),
109 B: uint8(uint32(col.B) * uint32(col.A) / 0xFF),
110 A: col.A,
111 }
112 }
113
114 // RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color.
115 func RGBAToNRGBA(col color.RGBA) color.NRGBA {
116 if col.A == 0xFF {
117 return color.NRGBA(col)
118 }
119
120 linear := RGBA{
121 R: sRGBToLinear(float32(col.R) / 0xff),
122 G: sRGBToLinear(float32(col.G) / 0xff),
123 B: sRGBToLinear(float32(col.B) / 0xff),
124 A: float32(col.A) / 0xff,
125 }
126
127 return linear.SRGB()
128 }
129
130 // linearTosRGB transforms color value from linear to sRGB.
131 func linearTosRGB(c float32) float32 {
132 // Formula from EXT_sRGB.
133 switch {
134 case c <= 0:
135 return 0
136 case 0 < c && c < 0.0031308:
137 return 12.92 * c
138 case 0.0031308 <= c && c < 1:
139 return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055
140 }
141
142 return 1
143 }
144
145 // sRGBToLinear transforms color value from sRGB to linear.
146 func sRGBToLinear(c float32) float32 {
147 // Formula from EXT_sRGB.
148 if c <= 0.04045 {
149 return c / 12.92
150 } else {
151 return float32(math.Pow(float64((c+0.055)/1.055), 2.4))
152 }
153 }
154
155 // MulAlpha applies the alpha to the color.
156 func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
157 c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
158 return c
159 }
160
161 // Disabled blends color towards the luminance and multiplies alpha.
162 // Blending towards luminance will desaturate the color.
163 // Multiplying alpha blends the color together more with the background.
164 func Disabled(c color.NRGBA) (d color.NRGBA) {
165 const r = 80 // blend ratio
166 lum := approxLuminance(c)
167 return color.NRGBA{
168 R: byte((int(c.R)*r + int(lum)*(256-r)) / 256),
169 G: byte((int(c.G)*r + int(lum)*(256-r)) / 256),
170 B: byte((int(c.B)*r + int(lum)*(256-r)) / 256),
171 A: byte(int(c.A) * (128 + 32) / 256),
172 }
173 }
174
175 // Hovered blends color towards a brighter color.
176 func Hovered(c color.NRGBA) (d color.NRGBA) {
177 const r = 0x20 // lighten ratio
178 return color.NRGBA{
179 R: byte(255 - int(255-c.R)*(255-r)/256),
180 G: byte(255 - int(255-c.G)*(255-r)/256),
181 B: byte(255 - int(255-c.B)*(255-r)/256),
182 A: c.A,
183 }
184 }
185
186 // approxLuminance is a fast approximate version of RGBA.Luminance.
187 func approxLuminance(c color.NRGBA) byte {
188 const (
189 r = 13933 // 0.2126 * 256 * 256
190 g = 46871 // 0.7152 * 256 * 256
191 b = 4732 // 0.0722 * 256 * 256
192 t = r + g + b
193 )
194 return byte((r*int(c.R) + g*int(c.G) + b*int(c.B)) / t)
195 }
196