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  // 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  }
 178