1package utils
2
3import (
4	"fmt"
5	"math"
6	"os"
7	"strconv"
8)
9
10var (
11	Black, White *Color
12)
13
14func init() {
15	initColorCube()
16	Black, _ = NewColor("000000")
17	White, _ = NewColor("ffffff")
18}
19
20type Color struct {
21	Red   uint8
22	Green uint8
23	Blue  uint8
24}
25
26func NewColor(hex string) (*Color, error) {
27	red, err := strconv.ParseUint(hex[0:2], 16, 8)
28	if err != nil {
29		return nil, err
30	}
31	green, err := strconv.ParseUint(hex[2:4], 16, 8)
32	if err != nil {
33		return nil, err
34	}
35	blue, err := strconv.ParseUint(hex[4:6], 16, 8)
36	if err != nil {
37		return nil, err
38	}
39
40	return &Color{
41		Red:   uint8(red),
42		Green: uint8(green),
43		Blue:  uint8(blue),
44	}, nil
45}
46
47func (c *Color) Distance(other *Color) float64 {
48	return math.Sqrt(math.Pow(float64(c.Red-other.Red), 2) +
49		math.Pow(float64(c.Green-other.Green), 2) +
50		math.Pow(float64(c.Blue-other.Blue), 2))
51}
52
53func rgbComponentToBoldValue(component uint8) float64 {
54	srgb := float64(component) / 255
55	if srgb <= 0.03928 {
56		return srgb / 12.92
57	} else {
58		return math.Pow(((srgb + 0.055) / 1.055), 2.4)
59	}
60}
61
62func (c *Color) Luminance() float64 {
63	return 0.2126*rgbComponentToBoldValue(c.Red) +
64		0.7152*rgbComponentToBoldValue(c.Green) +
65		0.0722*rgbComponentToBoldValue(c.Blue)
66}
67
68func (c *Color) ContrastRatio(other *Color) float64 {
69	L := c.Luminance()
70	otherL := other.Luminance()
71	var L1, L2 float64
72	if L > otherL {
73		L1, L2 = L, otherL
74	} else {
75		L1, L2 = otherL, L
76	}
77	ratio := (L1 + 0.05) / (L2 + 0.05)
78	return ratio
79}
80
81var x6colorIndexes = [6]uint8{0, 95, 135, 175, 215, 255}
82var x6colorCube [216]Color
83
84func initColorCube() {
85	i := 0
86	for iR := 0; iR < 6; iR++ {
87		for iG := 0; iG < 6; iG++ {
88			for iB := 0; iB < 6; iB++ {
89				x6colorCube[i] = Color{
90					x6colorIndexes[iR],
91					x6colorIndexes[iG],
92					x6colorIndexes[iB],
93				}
94				i++
95			}
96		}
97	}
98}
99
100func ditherTo256ColorCode(color *Color) (code int) {
101	iMatch := -1
102	minDistance := float64(99999)
103	for i := 0; i < 216; i++ {
104		distance := color.Distance(&x6colorCube[i])
105		if distance < minDistance {
106			iMatch = i
107			minDistance = distance
108		}
109	}
110	return iMatch + 16
111}
112
113var non24bitColorTerms = []string{
114	"Apple_Terminal",
115}
116var isTerm24bitColorCapableCache bool
117var isTerm24bitColorCapableCacheIsInit bool = false
118
119func isTerm24bitColorCapable() bool {
120	if !isTerm24bitColorCapableCacheIsInit {
121		isTerm24bitColorCapableCache = true
122		myTermProg := os.Getenv("TERM_PROGRAM")
123		for _, brokenTerm := range non24bitColorTerms {
124			if myTermProg == brokenTerm {
125				isTerm24bitColorCapableCache = false
126				break
127			}
128		}
129		isTerm24bitColorCapableCacheIsInit = true
130	}
131	return isTerm24bitColorCapableCache
132}
133
134func RgbToTermColorCode(color *Color) string {
135	if isTerm24bitColorCapable() {
136		return fmt.Sprintf("2;%d;%d;%d", color.Red, color.Green, color.Blue)
137	} else {
138		intCode := ditherTo256ColorCode(color)
139		return fmt.Sprintf("5;%d", intCode)
140	}
141}
142