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  }