github.com/utopiagio/gio@v0.0.8/internal/f32color/rgba.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package f32color 4 5 import ( 6 "image/color" 7 "math" 8 ) 9 10 //go:generate go run ./f32colorgen -out tables.go 11 12 // RGBA is a 32 bit floating point linear premultiplied color space. 13 type RGBA struct { 14 R, G, B, A float32 15 } 16 17 // Array returns rgba values in a [4]float32 array. 18 func (rgba RGBA) Array() [4]float32 { 19 return [4]float32{rgba.R, rgba.G, rgba.B, rgba.A} 20 } 21 22 // Float32 returns r, g, b, a values. 23 func (col RGBA) Float32() (r, g, b, a float32) { 24 return col.R, col.G, col.B, col.A 25 } 26 27 // SRGBA converts from linear to sRGB color space. 28 func (col RGBA) SRGB() color.NRGBA { 29 if col.A == 0 { 30 return color.NRGBA{} 31 } 32 return color.NRGBA{ 33 R: uint8(linearTosRGB(col.R/col.A)*255 + .5), 34 G: uint8(linearTosRGB(col.G/col.A)*255 + .5), 35 B: uint8(linearTosRGB(col.B/col.A)*255 + .5), 36 A: uint8(col.A*255 + .5), 37 } 38 } 39 40 // Luminance calculates the relative luminance of a linear RGBA color. 41 // Normalized to 0 for black and 1 for white. 42 // 43 // See https://www.w3.org/TR/WCAG20/#relativeluminancedef for more details 44 func (col RGBA) Luminance() float32 { 45 return 0.2126*col.R + 0.7152*col.G + 0.0722*col.B 46 } 47 48 // Opaque returns the color without alpha component. 49 func (col RGBA) Opaque() RGBA { 50 col.A = 1.0 51 return col 52 } 53 54 // LinearFromSRGB converts from col in the sRGB colorspace to RGBA. 55 func LinearFromSRGB(col color.NRGBA) RGBA { 56 af := float32(col.A) / 0xFF 57 return RGBA{ 58 R: srgb8ToLinear[col.R] * af, // sRGBToLinear(float32(col.R)/0xff) * af, 59 G: srgb8ToLinear[col.G] * af, // sRGBToLinear(float32(col.G)/0xff) * af, 60 B: srgb8ToLinear[col.B] * af, // sRGBToLinear(float32(col.B)/0xff) * af, 61 A: af, 62 } 63 } 64 65 // NRGBAToRGBA converts from non-premultiplied sRGB color to premultiplied sRGB color. 66 // 67 // Each component in the result is `sRGBToLinear(c * alpha)`, where `c` 68 // is the linear color. 69 func NRGBAToRGBA(col color.NRGBA) color.RGBA { 70 if col.A == 0xFF { 71 return color.RGBA(col) 72 } 73 c := LinearFromSRGB(col) 74 return color.RGBA{ 75 R: uint8(linearTosRGB(c.R)*255 + .5), 76 G: uint8(linearTosRGB(c.G)*255 + .5), 77 B: uint8(linearTosRGB(c.B)*255 + .5), 78 A: col.A, 79 } 80 } 81 82 // NRGBAToLinearRGBA converts from non-premultiplied sRGB color to premultiplied linear RGBA color. 83 // 84 // Each component in the result is `c * alpha`, where `c` is the linear color. 85 func NRGBAToLinearRGBA(col color.NRGBA) color.RGBA { 86 if col.A == 0xFF { 87 return color.RGBA(col) 88 } 89 c := LinearFromSRGB(col) 90 return color.RGBA{ 91 R: uint8(c.R*255 + .5), 92 G: uint8(c.G*255 + .5), 93 B: uint8(c.B*255 + .5), 94 A: col.A, 95 } 96 } 97 98 // RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color. 99 func RGBAToNRGBA(col color.RGBA) color.NRGBA { 100 if col.A == 0xFF { 101 return color.NRGBA(col) 102 } 103 104 linear := RGBA{ 105 R: sRGBToLinear(float32(col.R) / 0xff), 106 G: sRGBToLinear(float32(col.G) / 0xff), 107 B: sRGBToLinear(float32(col.B) / 0xff), 108 A: float32(col.A) / 0xff, 109 } 110 111 return linear.SRGB() 112 } 113 114 // linearTosRGB transforms color value from linear to sRGB. 115 func linearTosRGB(c float32) float32 { 116 // Formula from EXT_sRGB. 117 switch { 118 case c <= 0: 119 return 0 120 case 0 < c && c < 0.0031308: 121 return 12.92 * c 122 case 0.0031308 <= c && c < 1: 123 return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055 124 } 125 126 return 1 127 } 128 129 // sRGBToLinear transforms color value from sRGB to linear. 130 func sRGBToLinear(c float32) float32 { 131 // Formula from EXT_sRGB. 132 if c <= 0.04045 { 133 return c / 12.92 134 } else { 135 return float32(math.Pow(float64((c+0.055)/1.055), 2.4)) 136 } 137 } 138 139 // MulAlpha applies the alpha to the color. 140 func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA { 141 c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF) 142 return c 143 } 144 145 // Disabled blends color towards the luminance and multiplies alpha. 146 // Blending towards luminance will desaturate the color. 147 // Multiplying alpha blends the color together more with the background. 148 func Disabled(c color.NRGBA) (d color.NRGBA) { 149 const r = 80 // blend ratio 150 lum := approxLuminance(c) 151 d = mix(c, color.NRGBA{A: c.A, R: lum, G: lum, B: lum}, r) 152 d = MulAlpha(d, 128+32) 153 return 154 } 155 156 // Hovered blends dark colors towards white, and light colors towards 157 // black. It is approximate because it operates in non-linear sRGB space. 158 func Hovered(c color.NRGBA) (h color.NRGBA) { 159 if c.A == 0 { 160 // Provide a reasonable default for transparent widgets. 161 return color.NRGBA{A: 0x44, R: 0x88, G: 0x88, B: 0x88} 162 } 163 const ratio = 0x20 164 m := color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: c.A} 165 if approxLuminance(c) > 128 { 166 m = color.NRGBA{A: c.A} 167 } 168 return mix(m, c, ratio) 169 } 170 171 // mix mixes c1 and c2 weighted by (1 - a/256) and a/256 respectively. 172 func mix(c1, c2 color.NRGBA, a uint8) color.NRGBA { 173 ai := int(a) 174 return color.NRGBA{ 175 R: byte((int(c1.R)*ai + int(c2.R)*(256-ai)) / 256), 176 G: byte((int(c1.G)*ai + int(c2.G)*(256-ai)) / 256), 177 B: byte((int(c1.B)*ai + int(c2.B)*(256-ai)) / 256), 178 A: byte((int(c1.A)*ai + int(c2.A)*(256-ai)) / 256), 179 } 180 } 181 182 // approxLuminance is a fast approximate version of RGBA.Luminance. 183 func approxLuminance(c color.NRGBA) byte { 184 const ( 185 r = 13933 // 0.2126 * 256 * 256 186 g = 46871 // 0.7152 * 256 * 256 187 b = 4732 // 0.0722 * 256 * 256 188 t = r + g + b 189 ) 190 return byte((r*int(c.R) + g*int(c.G) + b*int(c.B)) / t) 191 }