github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/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 // 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 // RGBAToNRGBA converts from premultiplied sRGB color to non-premultiplied sRGB color. 97 func RGBAToNRGBA(col color.RGBA) color.NRGBA { 98 if col.A == 0xFF { 99 return color.NRGBA(col) 100 } 101 102 linear := RGBA{ 103 R: sRGBToLinear(float32(col.R) / 0xff), 104 G: sRGBToLinear(float32(col.G) / 0xff), 105 B: sRGBToLinear(float32(col.B) / 0xff), 106 A: float32(col.A) / 0xff, 107 } 108 109 return linear.SRGB() 110 } 111 112 // linearTosRGB transforms color value from linear to sRGB. 113 func linearTosRGB(c float32) float32 { 114 // Formula from EXT_sRGB. 115 switch { 116 case c <= 0: 117 return 0 118 case 0 < c && c < 0.0031308: 119 return 12.92 * c 120 case 0.0031308 <= c && c < 1: 121 return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055 122 } 123 124 return 1 125 } 126 127 // sRGBToLinear transforms color value from sRGB to linear. 128 func sRGBToLinear(c float32) float32 { 129 // Formula from EXT_sRGB. 130 if c <= 0.04045 { 131 return c / 12.92 132 } else { 133 return float32(math.Pow(float64((c+0.055)/1.055), 2.4)) 134 } 135 } 136 137 // MulAlpha applies the alpha to the color. 138 func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA { 139 c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF) 140 return c 141 } 142 143 // Disabled blends color towards the luminance and multiplies alpha. 144 // Blending towards luminance will desaturate the color. 145 // Multiplying alpha blends the color together more with the background. 146 func Disabled(c color.NRGBA) (d color.NRGBA) { 147 const r = 80 // blend ratio 148 lum := approxLuminance(c) 149 return color.NRGBA{ 150 R: byte((int(c.R)*r + int(lum)*(256-r)) / 256), 151 G: byte((int(c.G)*r + int(lum)*(256-r)) / 256), 152 B: byte((int(c.B)*r + int(lum)*(256-r)) / 256), 153 A: byte(int(c.A) * (128 + 32) / 256), 154 } 155 } 156 157 // Hovered blends color towards a brighter color. 158 func Hovered(c color.NRGBA) (d color.NRGBA) { 159 const r = 0x20 // lighten ratio 160 return color.NRGBA{ 161 R: byte(255 - int(255-c.R)*(255-r)/256), 162 G: byte(255 - int(255-c.G)*(255-r)/256), 163 B: byte(255 - int(255-c.B)*(255-r)/256), 164 A: c.A, 165 } 166 } 167 168 // approxLuminance is a fast approximate version of RGBA.Luminance. 169 func approxLuminance(c color.NRGBA) byte { 170 const ( 171 r = 13933 // 0.2126 * 256 * 256 172 g = 46871 // 0.7152 * 256 * 256 173 b = 4732 // 0.0722 * 256 * 256 174 t = r + g + b 175 ) 176 return byte((r*int(c.R) + g*int(c.G) + b*int(c.B)) / t) 177 }