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