color.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package input
  2. import (
  3. "fmt"
  4. "image/color"
  5. "math"
  6. )
  7. // ForegroundColorEvent represents a foreground color event. This event is
  8. // emitted when the terminal requests the terminal foreground color using
  9. // [ansi.RequestForegroundColor].
  10. type ForegroundColorEvent struct{ color.Color }
  11. // String returns the hex representation of the color.
  12. func (e ForegroundColorEvent) String() string {
  13. return colorToHex(e.Color)
  14. }
  15. // IsDark returns whether the color is dark.
  16. func (e ForegroundColorEvent) IsDark() bool {
  17. return isDarkColor(e.Color)
  18. }
  19. // BackgroundColorEvent represents a background color event. This event is
  20. // emitted when the terminal requests the terminal background color using
  21. // [ansi.RequestBackgroundColor].
  22. type BackgroundColorEvent struct{ color.Color }
  23. // String returns the hex representation of the color.
  24. func (e BackgroundColorEvent) String() string {
  25. return colorToHex(e)
  26. }
  27. // IsDark returns whether the color is dark.
  28. func (e BackgroundColorEvent) IsDark() bool {
  29. return isDarkColor(e.Color)
  30. }
  31. // CursorColorEvent represents a cursor color change event. This event is
  32. // emitted when the program requests the terminal cursor color using
  33. // [ansi.RequestCursorColor].
  34. type CursorColorEvent struct{ color.Color }
  35. // String returns the hex representation of the color.
  36. func (e CursorColorEvent) String() string {
  37. return colorToHex(e)
  38. }
  39. // IsDark returns whether the color is dark.
  40. func (e CursorColorEvent) IsDark() bool {
  41. return isDarkColor(e)
  42. }
  43. type shiftable interface {
  44. ~uint | ~uint16 | ~uint32 | ~uint64
  45. }
  46. func shift[T shiftable](x T) T {
  47. if x > 0xff {
  48. x >>= 8
  49. }
  50. return x
  51. }
  52. func colorToHex(c color.Color) string {
  53. if c == nil {
  54. return ""
  55. }
  56. r, g, b, _ := c.RGBA()
  57. return fmt.Sprintf("#%02x%02x%02x", shift(r), shift(g), shift(b))
  58. }
  59. func getMaxMin(a, b, c float64) (ma, mi float64) {
  60. if a > b {
  61. ma = a
  62. mi = b
  63. } else {
  64. ma = b
  65. mi = a
  66. }
  67. if c > ma {
  68. ma = c
  69. } else if c < mi {
  70. mi = c
  71. }
  72. return ma, mi
  73. }
  74. func round(x float64) float64 {
  75. return math.Round(x*1000) / 1000
  76. }
  77. // rgbToHSL converts an RGB triple to an HSL triple.
  78. func rgbToHSL(r, g, b uint8) (h, s, l float64) {
  79. // convert uint32 pre-multiplied value to uint8
  80. // The r,g,b values are divided by 255 to change the range from 0..255 to 0..1:
  81. Rnot := float64(r) / 255
  82. Gnot := float64(g) / 255
  83. Bnot := float64(b) / 255
  84. Cmax, Cmin := getMaxMin(Rnot, Gnot, Bnot)
  85. Δ := Cmax - Cmin
  86. // Lightness calculation:
  87. l = (Cmax + Cmin) / 2
  88. // Hue and Saturation Calculation:
  89. if Δ == 0 {
  90. h = 0
  91. s = 0
  92. } else {
  93. switch Cmax {
  94. case Rnot:
  95. h = 60 * (math.Mod((Gnot-Bnot)/Δ, 6))
  96. case Gnot:
  97. h = 60 * (((Bnot - Rnot) / Δ) + 2)
  98. case Bnot:
  99. h = 60 * (((Rnot - Gnot) / Δ) + 4)
  100. }
  101. if h < 0 {
  102. h += 360
  103. }
  104. s = Δ / (1 - math.Abs((2*l)-1))
  105. }
  106. return h, round(s), round(l)
  107. }
  108. // isDarkColor returns whether the given color is dark.
  109. func isDarkColor(c color.Color) bool {
  110. if c == nil {
  111. return true
  112. }
  113. r, g, b, _ := c.RGBA()
  114. _, _, l := rgbToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8)) //nolint:gosec
  115. return l < 0.5
  116. }