1. package chroma
    
  2. 
    
  3. import (
    
  4. 	"fmt"
    
  5. 	"math"
    
  6. 	"strconv"
    
  7. 	"strings"
    
  8. )
    
  9. 
    
  10. // ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values.
    
  11. var 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.
    
  51. type Colour int32
    
  52. 
    
  53. // NewColour creates a Colour directly from RGB values.
    
  54. func 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.
    
  62. func (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).
    
  77. func (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 brightness or darkens if > 0.5 brightness.
    
  96. func (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. // ClampBrightness returns a copy of this colour with its brightness adjusted such that
    
  104. // it falls within the range [min, max] (or very close to it due to rounding errors).
    
  105. // The supplied values use the same [0.0, 1.0] range as Brightness.
    
  106. func (c Colour) ClampBrightness(min, max float64) Colour {
    
  107. 	if !c.IsSet() {
    
  108. 		return c
    
  109. 	}
    
  110. 
    
  111. 	min = math.Max(min, 0)
    
  112. 	max = math.Min(max, 1)
    
  113. 	current := c.Brightness()
    
  114. 	target := math.Min(math.Max(current, min), max)
    
  115. 	if current == target {
    
  116. 		return c
    
  117. 	}
    
  118. 
    
  119. 	r := float64(c.Red())
    
  120. 	g := float64(c.Green())
    
  121. 	b := float64(c.Blue())
    
  122. 	rgb := r + g + b
    
  123. 	if target > current {
    
  124. 		// Solve for x: target == ((255-r)*x + r + (255-g)*x + g + (255-b)*x + b) / 255 / 3
    
  125. 		return c.Brighten((target*255*3 - rgb) / (255*3 - rgb))
    
  126. 	}
    
  127. 	// Solve for x: target == (r*(x+1) + g*(x+1) + b*(x+1)) / 255 / 3
    
  128. 	return c.Brighten((target*255*3)/rgb - 1)
    
  129. }
    
  130. 
    
  131. // Brightness of the colour (roughly) in the range 0.0 to 1.0.
    
  132. func (c Colour) Brightness() float64 {
    
  133. 	return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
    
  134. }
    
  135. 
    
  136. // ParseColour in the forms #rgb, #rrggbb, #ansi<colour>, or #<colour>.
    
  137. // Will return an "unset" colour if invalid.
    
  138. func ParseColour(colour string) Colour {
    
  139. 	colour = normaliseColour(colour)
    
  140. 	n, err := strconv.ParseUint(colour, 16, 32)
    
  141. 	if err != nil {
    
  142. 		return 0
    
  143. 	}
    
  144. 	return Colour(n + 1)
    
  145. }
    
  146. 
    
  147. // MustParseColour is like ParseColour except it panics if the colour is invalid.
    
  148. //
    
  149. // Will panic if colour is in an invalid format.
    
  150. func MustParseColour(colour string) Colour {
    
  151. 	parsed := ParseColour(colour)
    
  152. 	if !parsed.IsSet() {
    
  153. 		panic(fmt.Errorf("invalid colour %q", colour))
    
  154. 	}
    
  155. 	return parsed
    
  156. }
    
  157. 
    
  158. // IsSet returns true if the colour is set.
    
  159. func (c Colour) IsSet() bool { return c != 0 }
    
  160. 
    
  161. func (c Colour) String() string   { return fmt.Sprintf("#%06x", int(c-1)) }
    
  162. func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
    
  163. 
    
  164. // Red component of colour.
    
  165. func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) }
    
  166. 
    
  167. // Green component of colour.
    
  168. func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) }
    
  169. 
    
  170. // Blue component of colour.
    
  171. func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) }
    
  172. 
    
  173. // Colours is an orderable set of colours.
    
  174. type Colours []Colour
    
  175. 
    
  176. func (c Colours) Len() int           { return len(c) }
    
  177. func (c Colours) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
    
  178. func (c Colours) Less(i, j int) bool { return c[i] < c[j] }
    
  179. 
    
  180. // Convert colours to #rrggbb.
    
  181. func normaliseColour(colour string) string {
    
  182. 	if ansi, ok := ANSI2RGB[colour]; ok {
    
  183. 		return ansi
    
  184. 	}
    
  185. 	if strings.HasPrefix(colour, "#") {
    
  186. 		colour = colour[1:]
    
  187. 		if len(colour) == 3 {
    
  188. 			return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3]
    
  189. 		}
    
  190. 	}
    
  191. 	return colour
    
  192. }