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 }