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  }