1package chroma
2
3import (
4	"fmt"
5	"math"
6	"strconv"
7	"strings"
8)
9
10// ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values.
11var ANSI2RGB = map[string]string{
12	"#ansiblack":     "000000",
13	"#ansidarkred":   "7f0000",
14	"#ansidarkgreen": "007f00",
15	"#ansibrown":     "7f7fe0",
16	"#ansidarkblue":  "00007f",
17	"#ansipurple":    "7f007f",
18	"#ansiteal":      "007f7f",
19	"#ansilightgray": "e5e5e5",
20	// Normal
21	"#ansidarkgray":  "555555",
22	"#ansired":       "ff0000",
23	"#ansigreen":     "00ff00",
24	"#ansiyellow":    "ffff00",
25	"#ansiblue":      "0000ff",
26	"#ansifuchsia":   "ff00ff",
27	"#ansiturquoise": "00ffff",
28	"#ansiwhite":     "ffffff",
29
30	// Aliases without the "ansi" prefix, because...why?
31	"#black":     "000000",
32	"#darkred":   "7f0000",
33	"#darkgreen": "007f00",
34	"#brown":     "7f7fe0",
35	"#darkblue":  "00007f",
36	"#purple":    "7f007f",
37	"#teal":      "007f7f",
38	"#lightgray": "e5e5e5",
39	// Normal
40	"#darkgray":  "555555",
41	"#red":       "ff0000",
42	"#green":     "00ff00",
43	"#yellow":    "ffff00",
44	"#blue":      "0000ff",
45	"#fuchsia":   "ff00ff",
46	"#turquoise": "00ffff",
47	"#white":     "ffffff",
48}
49
50// Colour represents an RGB colour.
51type Colour int32
52
53// NewColour creates a Colour directly from RGB values.
54func NewColour(r, g, b uint8) Colour {
55	return ParseColour(fmt.Sprintf("%02x%02x%02x", r, g, b))
56}
57
58// Distance between this colour and another.
59//
60// This uses the approach described here (https://www.compuphase.com/cmetric.htm).
61// This is not as accurate as LAB, et. al. but is *vastly* simpler and sufficient for our needs.
62func (c Colour) Distance(e2 Colour) float64 {
63	ar, ag, ab := int64(c.Red()), int64(c.Green()), int64(c.Blue())
64	br, bg, bb := int64(e2.Red()), int64(e2.Green()), int64(e2.Blue())
65	rmean := (ar + br) / 2
66	r := ar - br
67	g := ag - bg
68	b := ab - bb
69	return math.Sqrt(float64((((512 + rmean) * r * r) >> 8) + 4*g*g + (((767 - rmean) * b * b) >> 8)))
70}
71
72// Brighten returns a copy of this colour with its brightness adjusted.
73//
74// If factor is negative, the colour is darkened.
75//
76// Uses approach described here (http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html).
77func (c Colour) Brighten(factor float64) Colour {
78	r := float64(c.Red())
79	g := float64(c.Green())
80	b := float64(c.Blue())
81
82	if factor < 0 {
83		factor++
84		r *= factor
85		g *= factor
86		b *= factor
87	} else {
88		r = (255-r)*factor + r
89		g = (255-g)*factor + g
90		b = (255-b)*factor + b
91	}
92	return NewColour(uint8(r), uint8(g), uint8(b))
93}
94
95// BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
96func (c Colour) BrightenOrDarken(factor float64) Colour {
97	if c.Brightness() < 0.5 {
98		return c.Brighten(factor)
99	}
100	return c.Brighten(-factor)
101}
102
103// Brightness of the colour (roughly) in the range 0.0 to 1.0
104func (c Colour) Brightness() float64 {
105	return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
106}
107
108// ParseColour in the forms #rgb, #rrggbb, #ansi<colour>, or #<colour>.
109// Will return an "unset" colour if invalid.
110func ParseColour(colour string) Colour {
111	colour = normaliseColour(colour)
112	n, err := strconv.ParseUint(colour, 16, 32)
113	if err != nil {
114		return 0
115	}
116	return Colour(n + 1)
117}
118
119// MustParseColour is like ParseColour except it panics if the colour is invalid.
120//
121// Will panic if colour is in an invalid format.
122func MustParseColour(colour string) Colour {
123	parsed := ParseColour(colour)
124	if !parsed.IsSet() {
125		panic(fmt.Errorf("invalid colour %q", colour))
126	}
127	return parsed
128}
129
130// IsSet returns true if the colour is set.
131func (c Colour) IsSet() bool { return c != 0 }
132
133func (c Colour) String() string   { return fmt.Sprintf("#%06x", int(c-1)) }
134func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
135
136// Red component of colour.
137func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) }
138
139// Green component of colour.
140func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) }
141
142// Blue component of colour.
143func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) }
144
145// Colours is an orderable set of colours.
146type Colours []Colour
147
148func (c Colours) Len() int           { return len(c) }
149func (c Colours) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
150func (c Colours) Less(i, j int) bool { return c[i] < c[j] }
151
152// Convert colours to #rrggbb.
153func normaliseColour(colour string) string {
154	if ansi, ok := ANSI2RGB[colour]; ok {
155		return ansi
156	}
157	if strings.HasPrefix(colour, "#") {
158		colour = colour[1:]
159		if len(colour) == 3 {
160			return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3]
161		}
162	}
163	return colour
164}
165