pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/color/color.go (about)

     1  // Package color provides methods for working with colors
     2  package color
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"fmt"
    13  	"math"
    14  	"strconv"
    15  
    16  	"pkg.re/essentialkaos/ek.v12/mathutil"
    17  )
    18  
    19  // ////////////////////////////////////////////////////////////////////////////////// //
    20  
    21  type Hex uint32 // Hex color 0x00000000 - 0xFFFFFFFF
    22  
    23  type RGB struct {
    24  	R uint8 // Red
    25  	G uint8 // Green
    26  	B uint8 // Blue
    27  }
    28  
    29  type RGBA struct {
    30  	R uint8 // Red
    31  	G uint8 // Green
    32  	B uint8 // Blue
    33  	A uint8 // Alpha
    34  }
    35  
    36  type CMYK struct {
    37  	C float64 // Cyan
    38  	M float64 // Magenta
    39  	Y float64 // Yellow
    40  	K float64 // Key (black)
    41  }
    42  
    43  type HSV struct {
    44  	H float64 // Hue
    45  	S float64 // Saturation
    46  	V float64 // Lightness
    47  }
    48  
    49  type HSL struct {
    50  	H float64 // Hue
    51  	S float64 // Saturation
    52  	L float64 // Value
    53  }
    54  
    55  // ////////////////////////////////////////////////////////////////////////////////// //
    56  
    57  // colors is colors keywords
    58  var colors = map[string]Hex{
    59  	"aliceblue":            0xf0f8ff,
    60  	"antiquewhite":         0xfaebd7,
    61  	"aqua":                 0x00ffff,
    62  	"aquamarine":           0x7fffd4,
    63  	"azure":                0xf0ffff,
    64  	"beige":                0xf5f5dc,
    65  	"bisque":               0xffe4c4,
    66  	"black":                0x000000,
    67  	"blanchedalmond":       0xffebcd,
    68  	"blue":                 0x0000ff,
    69  	"blueviolet":           0x8a2be2,
    70  	"brown":                0xa52a2a,
    71  	"burlywood":            0xdeb887,
    72  	"cadetblue":            0x5f9ea0,
    73  	"chartreuse":           0x7fff00,
    74  	"chocolate":            0xd2691e,
    75  	"coral":                0xff7f50,
    76  	"cornflowerblue":       0x6495ed,
    77  	"cornsilk":             0xfff8dc,
    78  	"crimson":              0xdc143c,
    79  	"cyan":                 0x00ffff,
    80  	"darkblue":             0x00008b,
    81  	"darkcyan":             0x008b8b,
    82  	"darkgoldenrod":        0xb8860b,
    83  	"darkgray":             0xa9a9a9,
    84  	"darkgreen":            0x006400,
    85  	"darkgrey":             0xa9a9a9,
    86  	"darkkhaki":            0xbdb76b,
    87  	"darkmagenta":          0x8b008b,
    88  	"darkolivegreen":       0x556b2f,
    89  	"darkorange":           0xff8c00,
    90  	"darkorchid":           0x9932cc,
    91  	"darkred":              0x8b0000,
    92  	"darksalmon":           0xe9967a,
    93  	"darkseagreen":         0x8fbc8f,
    94  	"darkslateblue":        0x483d8b,
    95  	"darkslategray":        0x2f4f4f,
    96  	"darkslategrey":        0x2f4f4f,
    97  	"darkturquoise":        0x00ced1,
    98  	"darkviolet":           0x9400d3,
    99  	"deeppink":             0xff1493,
   100  	"deepskyblue":          0x00bfff,
   101  	"dimgray":              0x696969,
   102  	"dimgrey":              0x696969,
   103  	"dodgerblue":           0x1e90ff,
   104  	"firebrick":            0xb22222,
   105  	"floralwhite":          0xfffaf0,
   106  	"forestgreen":          0x228b22,
   107  	"fuchsia":              0xff00ff,
   108  	"gainsboro":            0xdcdcdc,
   109  	"ghostwhite":           0xf8f8ff,
   110  	"gold":                 0xffd700,
   111  	"goldenrod":            0xdaa520,
   112  	"gray":                 0x808080,
   113  	"green":                0x008000,
   114  	"greenyellow":          0xadff2f,
   115  	"grey":                 0x808080,
   116  	"honeydew":             0xf0fff0,
   117  	"hotpink":              0xff69b4,
   118  	"indianred":            0xcd5c5c,
   119  	"indigo":               0x4b0082,
   120  	"ivory":                0xfffff0,
   121  	"khaki":                0xf0e68c,
   122  	"lavender":             0xe6e6fa,
   123  	"lavenderblush":        0xfff0f5,
   124  	"lawngreen":            0x7cfc00,
   125  	"lemonchiffon":         0xfffacd,
   126  	"lightblue":            0xadd8e6,
   127  	"lightcoral":           0xf08080,
   128  	"lightcyan":            0xe0ffff,
   129  	"lightgoldenrodyellow": 0xfafad2,
   130  	"lightgray":            0xd3d3d3,
   131  	"lightgreen":           0x90ee90,
   132  	"lightgrey":            0xd3d3d3,
   133  	"lightpink":            0xffb6c1,
   134  	"lightsalmon":          0xffa07a,
   135  	"lightseagreen":        0x20b2aa,
   136  	"lightskyblue":         0x87cefa,
   137  	"lightslategray":       0x778899,
   138  	"lightslategrey":       0x778899,
   139  	"lightsteelblue":       0xb0c4de,
   140  	"lightyellow":          0xffffe0,
   141  	"lime":                 0x00ff00,
   142  	"limegreen":            0x32cd32,
   143  	"linen":                0xfaf0e6,
   144  	"magenta":              0xff00ff,
   145  	"maroon":               0x800000,
   146  	"mediumaquamarine":     0x66cdaa,
   147  	"mediumblue":           0x0000cd,
   148  	"mediumorchid":         0xba55d3,
   149  	"mediumpurple":         0x9370db,
   150  	"mediumseagreen":       0x3cb371,
   151  	"mediumslateblue":      0x7b68ee,
   152  	"mediumspringgreen":    0x00fa9a,
   153  	"mediumturquoise":      0x48d1cc,
   154  	"mediumvioletred":      0xc71585,
   155  	"midnightblue":         0x191970,
   156  	"mintcream":            0xf5fffa,
   157  	"mistyrose":            0xffe4e1,
   158  	"moccasin":             0xffe4b5,
   159  	"navajowhite":          0xffdead,
   160  	"navy":                 0x000080,
   161  	"oldlace":              0xfdf5e6,
   162  	"olive":                0x808000,
   163  	"olivedrab":            0x6b8e23,
   164  	"orange":               0xffa500,
   165  	"orangered":            0xff4500,
   166  	"orchid":               0xda70d6,
   167  	"palegoldenrod":        0xeee8aa,
   168  	"palegreen":            0x98fb98,
   169  	"paleturquoise":        0xafeeee,
   170  	"palevioletred":        0xdb7093,
   171  	"papayawhip":           0xffefd5,
   172  	"peachpuff":            0xffdab9,
   173  	"peru":                 0xcd853f,
   174  	"pink":                 0xffc0cb,
   175  	"plum":                 0xdda0dd,
   176  	"powderblue":           0xb0e0e6,
   177  	"purple":               0x800080,
   178  	"rebeccapurple":        0x663399,
   179  	"red":                  0xff0000,
   180  	"rosybrown":            0xbc8f8f,
   181  	"royalblue":            0x4169e1,
   182  	"saddlebrown":          0x8b4513,
   183  	"salmon":               0xfa8072,
   184  	"sandybrown":           0xf4a460,
   185  	"seagreen":             0x2e8b57,
   186  	"seashell":             0xfff5ee,
   187  	"sienna":               0xa0522d,
   188  	"silver":               0xc0c0c0,
   189  	"skyblue":              0x87ceeb,
   190  	"slateblue":            0x6a5acd,
   191  	"slategray":            0x708090,
   192  	"slategrey":            0x708090,
   193  	"snow":                 0xfffafa,
   194  	"springgreen":          0x00ff7f,
   195  	"steelblue":            0x4682b4,
   196  	"tan":                  0xd2b48c,
   197  	"teal":                 0x008080,
   198  	"thistle":              0xd8bfd8,
   199  	"tomato":               0xff6347,
   200  	"turquoise":            0x40e0d0,
   201  	"violet":               0xee82ee,
   202  	"wheat":                0xf5deb3,
   203  	"white":                0xffffff,
   204  	"whitesmoke":           0xf5f5f5,
   205  	"yellow":               0xffff00,
   206  	"yellowgreen":          0x9acd32,
   207  }
   208  
   209  // ////////////////////////////////////////////////////////////////////////////////// //
   210  
   211  // IsRGBA returns true if color contains info about alpha channel
   212  func (c Hex) IsRGBA() bool {
   213  	return c > 0xFFFFFF
   214  }
   215  
   216  // ToHex converts RGB color to hex
   217  func (c RGB) ToHex() Hex {
   218  	return RGB2Hex(c)
   219  }
   220  
   221  // ToCMYK converts RGB color to CMYK
   222  func (c RGB) ToCMYK() CMYK {
   223  	return RGB2CMYK(c)
   224  }
   225  
   226  // ToHSV converts RGB color to HSV
   227  func (c RGB) ToHSV() HSV {
   228  	return RGB2HSV(c)
   229  }
   230  
   231  // ToHSL converts RGB color to HSL
   232  func (c RGB) ToHSL() HSL {
   233  	return RGB2HSL(c)
   234  }
   235  
   236  // ToTerm converts RGB color to terminal color code
   237  func (c RGB) ToTerm() int {
   238  	return RGB2Term(c)
   239  }
   240  
   241  // ToHex converts RGBA color to hex
   242  func (c RGBA) ToHex() Hex {
   243  	return RGBA2Hex(c)
   244  }
   245  
   246  // ToRGB converts CMYK color to RGB
   247  func (c CMYK) ToRGB() RGB {
   248  	return CMYK2RGB(c)
   249  }
   250  
   251  // ToRGB converts HSV color to RGB
   252  func (c HSV) ToRGB() RGB {
   253  	return HSV2RGB(c)
   254  }
   255  
   256  // ToRGB converts HSL color to RGB
   257  func (c HSL) ToRGB() RGB {
   258  	return HSL2RGB(c)
   259  }
   260  
   261  // ToRGB converts hex color to RGB
   262  func (c Hex) ToRGB() RGB {
   263  	return Hex2RGB(c)
   264  }
   265  
   266  // ToRGB converts hex color to RGBA
   267  func (c Hex) ToRGBA() RGBA {
   268  	return Hex2RGBA(c)
   269  }
   270  
   271  // ToWeb converts hex color notation used in web (#RGB / #RRGGBB/#RRGGBBAA)
   272  func (c Hex) ToWeb(caps bool) string {
   273  	var k string
   274  
   275  	if caps {
   276  		k = fmt.Sprintf("%06X", uint32(c))
   277  	} else {
   278  		k = fmt.Sprintf("%06x", uint32(c))
   279  	}
   280  
   281  	// Generate shorthand color only for RGB
   282  	if c < 0xFFFFFF && k[0] == k[1] && k[2] == k[3] && k[4] == k[5] {
   283  		k = k[0:1] + k[2:3] + k[4:5]
   284  	}
   285  
   286  	return "#" + k
   287  }
   288  
   289  // String returns string representation of RGB color
   290  func (c RGB) String() string {
   291  	return fmt.Sprintf(
   292  		"RGB{R:%d G:%d B:%d}",
   293  		c.R, c.G, c.B,
   294  	)
   295  }
   296  
   297  // String returns string representation of RGBA color
   298  func (c RGBA) String() string {
   299  	return fmt.Sprintf(
   300  		"RGBA{R:%d G:%d B:%d A:%.2f}",
   301  		c.R, c.G, c.B, float64(c.A)/255.0,
   302  	)
   303  }
   304  
   305  // String returns string representation of hex color
   306  func (c Hex) String() string {
   307  	return fmt.Sprintf("Hex{#%X}", uint32(c))
   308  }
   309  
   310  // String returns string representation of CMYK color
   311  func (c CMYK) String() string {
   312  	return fmt.Sprintf(
   313  		"CMYK{C:%.0f%% M:%.0f%% Y:%.0f%% K:%.0f%%}",
   314  		c.C*100.0, c.M*100.0, c.Y*100.0, c.K*100.0,
   315  	)
   316  }
   317  
   318  // String returns string representation of HSV color
   319  func (c HSV) String() string {
   320  	return fmt.Sprintf(
   321  		"HSV{H:%.0f° S:%.0f%% V:%.0f%%}",
   322  		c.H*360, c.S*100, c.V*100,
   323  	)
   324  }
   325  
   326  // String returns string representation of HSL color
   327  func (c HSL) String() string {
   328  	return fmt.Sprintf(
   329  		"HSL{H:%.0f° S:%.0f%% L:%.0f%%}",
   330  		c.H*360, c.S*100, c.L*100,
   331  	)
   332  }
   333  
   334  // ////////////////////////////////////////////////////////////////////////////////// //
   335  
   336  // Parse parses color
   337  func Parse(c string) (Hex, error) {
   338  	if colors[c] != 0 {
   339  		return colors[c], nil
   340  	}
   341  
   342  	if c != "" && c[0] == '#' {
   343  		c = c[1:]
   344  	}
   345  
   346  	switch len(c) {
   347  	case 0:
   348  		return 0x0, fmt.Errorf("Color is empty")
   349  
   350  	// Shorthand #RGB
   351  	case 3:
   352  		c = c[0:1] + c[0:1] + c[1:2] + c[1:2] + c[2:3] + c[2:3]
   353  
   354  	// Shorthand #RGBA
   355  	case 4:
   356  		c = c[0:1] + c[0:1] + c[1:2] + c[1:2] + c[2:3] + c[2:3] + c[3:4] + c[3:4]
   357  	}
   358  
   359  	k, err := strconv.ParseUint(c, 16, 32)
   360  
   361  	return Hex(k), err
   362  }
   363  
   364  // RGB2Hex converts RGB color to Hex
   365  func RGB2Hex(c RGB) Hex {
   366  	return Hex(int(c.R)<<16 | int(c.G)<<8 | int(c.B))
   367  }
   368  
   369  // Hex2RGB converts Hex color to RGB
   370  func Hex2RGB(h Hex) RGB {
   371  	return RGB{uint8(h >> 16 & 0xFF), uint8(h >> 8 & 0xFF), uint8(h & 0xFF)}
   372  }
   373  
   374  // RGBA2Hex converts RGBA color to Hex
   375  func RGBA2Hex(c RGBA) Hex {
   376  	return Hex(int64(c.R)<<24 | int64(c.G)<<16 | int64(c.B)<<8 | int64(c.A))
   377  }
   378  
   379  // Hex2RGBA converts Hex color to RGBA
   380  func Hex2RGBA(h Hex) RGBA {
   381  	if h >= 0xFFFFFF {
   382  		return RGBA{uint8(h>>24) & 0xFF, uint8(h>>16) & 0xFF, uint8(h>>8) & 0xFF, uint8(h) & 0xFF}
   383  	}
   384  
   385  	return RGBA{uint8(h>>16) & 0xFF, uint8(h>>8) & 0xFF, uint8(h) & 0xFF, 0}
   386  }
   387  
   388  // RGB2Term convert rgb color to terminal color code
   389  // https://misc.flogisoft.com/bash/tip_colors_and_formatting#colors1
   390  func RGB2Term(c RGB) int {
   391  	R, G, B := int(c.R), int(c.G), int(c.B)
   392  
   393  	// grayscale
   394  	if R == G && G == B {
   395  		if R == 175 {
   396  			return 145
   397  		}
   398  
   399  		return (R / 10) + 232
   400  	}
   401  
   402  	return 36*(R/51) + 6*(G/51) + (B / 51) + 16
   403  }
   404  
   405  // RGB2CMYK converts RGB color to CMYK
   406  func RGB2CMYK(c RGB) CMYK {
   407  	R, G, B := float64(c.R)/255.0, float64(c.G)/255.0, float64(c.B)/255.0
   408  	K := 1.0 - math.Max(math.Max(R, G), B)
   409  
   410  	return CMYK{
   411  		calcCMYKColor(R, K),
   412  		calcCMYKColor(G, K),
   413  		calcCMYKColor(B, K),
   414  		K,
   415  	}
   416  }
   417  
   418  // CMYK2RGB converts CMYK color to RGB
   419  func CMYK2RGB(c CMYK) RGB {
   420  	C := mathutil.BetweenF(c.C, 0.0, 1.0)
   421  	M := mathutil.BetweenF(c.M, 0.0, 1.0)
   422  	Y := mathutil.BetweenF(c.Y, 0.0, 1.0)
   423  	K := mathutil.BetweenF(c.K, 0.0, 1.0)
   424  
   425  	return RGB{
   426  		uint8(255 * (1 - C) * (1 - K)),
   427  		uint8(255 * (1 - M) * (1 - K)),
   428  		uint8(255 * (1 - Y) * (1 - K)),
   429  	}
   430  }
   431  
   432  // RGB2HSV converts RGB color to HSV (HSB)
   433  func RGB2HSV(c RGB) HSV {
   434  	R, G, B := float64(c.R)/255.0, float64(c.G)/255.0, float64(c.B)/255.0
   435  
   436  	max := math.Max(math.Max(R, G), B)
   437  	min := math.Min(math.Min(R, G), B)
   438  
   439  	h, s, v := 0.0, 0.0, max
   440  
   441  	if max != min {
   442  		d := max - min
   443  		s = d / max
   444  		h = calcHUE(max, R, G, B, d)
   445  	}
   446  
   447  	return HSV{h, s, v}
   448  }
   449  
   450  // HSV2RGB converts HSV (HSB) color to RGB
   451  func HSV2RGB(c HSV) RGB {
   452  	i := (c.H * 360.0) / 60.0
   453  	f := i - math.Floor(i)
   454  
   455  	p := c.V * (1 - c.S)
   456  	q := c.V * (1 - f*c.S)
   457  	t := c.V * (1 - (1-f)*c.S)
   458  
   459  	var R, G, B float64
   460  
   461  	switch int(c.H*6) % 6 {
   462  	case 0:
   463  		R, G, B = c.V, t, p
   464  	case 1:
   465  		R, G, B = q, c.V, p
   466  	case 2:
   467  		R, G, B = p, c.V, t
   468  	case 3:
   469  		R, G, B = p, q, c.V
   470  	case 4:
   471  		R, G, B = t, p, c.V
   472  	case 5:
   473  		R, G, B = c.V, p, q
   474  	}
   475  
   476  	return RGB{uint8(R * 0xFF), uint8(G * 0xFF), uint8(B * 0xFF)}
   477  }
   478  
   479  // RGB2HSL converts RGB color to HSL
   480  func RGB2HSL(c RGB) HSL {
   481  	R, G, B := float64(c.R)/255.0, float64(c.G)/255.0, float64(c.B)/255.0
   482  
   483  	max := math.Max(math.Max(R, G), B)
   484  	min := math.Min(math.Min(R, G), B)
   485  
   486  	h, s, l := 0.0, 0.0, (min+max)/2.0
   487  
   488  	if max != min {
   489  		d := max - min
   490  
   491  		if l > 0.5 {
   492  			s = d / (2.0 - max - min)
   493  		} else {
   494  			s = d / (max + min)
   495  		}
   496  
   497  		h = calcHUE(max, R, G, B, d)
   498  	}
   499  
   500  	return HSL{h, s, l}
   501  }
   502  
   503  // HSL2RGB converts HSL color to RGB
   504  func HSL2RGB(c HSL) RGB {
   505  	R, G, B := c.L, c.L, c.L
   506  
   507  	if c.S != 0 {
   508  		var q float64
   509  
   510  		if c.L > 0.5 {
   511  			q = c.L + c.S - (c.L * c.S)
   512  		} else {
   513  			q = c.L * (1.0 + c.S)
   514  		}
   515  
   516  		p := (2.0 * c.L) - q
   517  
   518  		R = HUE2RGB(p, q, c.H+1.0/3.0)
   519  		G = HUE2RGB(p, q, c.H)
   520  		B = HUE2RGB(p, q, c.H-1.0/3.0)
   521  	}
   522  
   523  	return RGB{uint8(R * 255), uint8(G * 255), uint8(B * 255)}
   524  }
   525  
   526  // HUE2RGB calculates HUE value for given RGB color
   527  func HUE2RGB(p, q, t float64) float64 {
   528  	if t < 0 {
   529  		t += 1
   530  	}
   531  
   532  	if t > 1 {
   533  		t -= 1
   534  	}
   535  
   536  	switch {
   537  	case t < 1.0/6.0:
   538  		return p + (q-p)*6.0*t
   539  	case t < 1.0/2.0:
   540  		return q
   541  	case t < 2.0/3.0:
   542  		return p + (q-p)*(2.0/3.0-t)*6.0
   543  	}
   544  
   545  	return p
   546  }
   547  
   548  // Luminance returns relative luminance for RGB color
   549  func Luminance(c RGB) float64 {
   550  	R := calcLumColor(float64(c.R) / 255)
   551  	G := calcLumColor(float64(c.G) / 255)
   552  	B := calcLumColor(float64(c.B) / 255)
   553  
   554  	return 0.2126*R + 0.7152*G + 0.0722*B
   555  }
   556  
   557  // Contrast calculates contrast ratio of foreground and background colors
   558  func Contrast(fg, bg Hex) float64 {
   559  	L1 := Luminance(fg.ToRGB()) + 0.05
   560  	L2 := Luminance(bg.ToRGB()) + 0.05
   561  
   562  	if L1 > L2 {
   563  		return L1 / L2
   564  	}
   565  
   566  	return L2 / L1
   567  }
   568  
   569  // ////////////////////////////////////////////////////////////////////////////////// //
   570  
   571  func calcCMYKColor(c, k float64) float64 {
   572  	if c == 0 && k == 1 {
   573  		return 0
   574  	}
   575  
   576  	return (1 - c - k) / (1 - k)
   577  }
   578  
   579  func calcLumColor(c float64) float64 {
   580  	if c <= 0.03928 {
   581  		return c / 12.92
   582  	}
   583  
   584  	return math.Pow(((c + 0.055) / 1.055), 2.4)
   585  }
   586  
   587  func calcHUE(max, r, g, b, d float64) float64 {
   588  	var h float64
   589  
   590  	switch max {
   591  	case r:
   592  		if g < b {
   593  			h = (g-b)/d + 6.0
   594  		} else {
   595  			h = (g - b) / d
   596  		}
   597  	case g:
   598  		h = (b-r)/d + 2.0
   599  	case b:
   600  		h = (r-g)/d + 4.0
   601  	}
   602  
   603  	return h / 6
   604  }