effects.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package rgb
  2. import (
  3. "fmt"
  4. "math"
  5. "strconv"
  6. )
  7. // Effect defines an RGB lighting effect.
  8. type Effect uint8
  9. const (
  10. Static Effect = 0
  11. Breathing Effect = 1
  12. EffectAlmond Effect = 2
  13. BandSat Effect = 3
  14. BandVal Effect = 4
  15. BandHue Effect = 5
  16. Chroma Effect = 6
  17. Rainbow Effect = 7
  18. RainbowWave Effect = 8
  19. Dance Effect = 9
  20. RainbowDance Effect = 10
  21. ChromaDance Effect = 11
  22. Snake Effect = 12
  23. Tail Effect = 13
  24. Split Effect = 14
  25. Heartbeat Effect = 15
  26. Blink Effect = 16
  27. BlinkStatic Effect = 17
  28. Gradient Effect = 18
  29. Testing Effect = 19
  30. )
  31. // EffectName returns the human-readable name of an effect.
  32. func (e Effect) String() string {
  33. names := map[Effect]string{
  34. Static: "static",
  35. Breathing: "breathing",
  36. EffectAlmond: "almond",
  37. BandSat: "band_sat",
  38. BandVal: "band_val",
  39. BandHue: "band_hue",
  40. Chroma: "chroma",
  41. Rainbow: "rainbow",
  42. RainbowWave: "rainbow_wave",
  43. Dance: "dance",
  44. RainbowDance: "rainbow_dance",
  45. ChromaDance: "chroma_dance",
  46. Snake: "snake",
  47. Tail: "tail",
  48. Split: "split",
  49. Heartbeat: "heartbeat",
  50. Blink: "blink",
  51. BlinkStatic: "blink_static",
  52. Gradient: "gradient",
  53. Testing: "testing",
  54. }
  55. if name, ok := names[e]; ok {
  56. return name
  57. }
  58. return "unknown"
  59. }
  60. // ParseEffect converts a string name to an Effect.
  61. func ParseEffect(name string) (Effect, error) {
  62. switch name {
  63. case "static":
  64. return Static, nil
  65. case "breathing":
  66. return Breathing, nil
  67. case "almond":
  68. return EffectAlmond, nil
  69. case "band_sat":
  70. return BandSat, nil
  71. case "band_val":
  72. return BandVal, nil
  73. case "band_hue":
  74. return BandHue, nil
  75. case "chroma":
  76. return Chroma, nil
  77. case "rainbow":
  78. return Rainbow, nil
  79. case "rainbow_wave":
  80. return RainbowWave, nil
  81. case "dance":
  82. return Dance, nil
  83. case "rainbow_dance":
  84. return RainbowDance, nil
  85. case "chroma_dance":
  86. return ChromaDance, nil
  87. case "snake":
  88. return Snake, nil
  89. case "tail":
  90. return Tail, nil
  91. case "split":
  92. return Split, nil
  93. case "heartbeat":
  94. return Heartbeat, nil
  95. case "blink":
  96. return Blink, nil
  97. case "blink_static":
  98. return BlinkStatic, nil
  99. case "gradient":
  100. return Gradient, nil
  101. case "testing":
  102. return Testing, nil
  103. default:
  104. return 0, fmt.Errorf("unknown effect: %s", name)
  105. }
  106. }
  107. func Impact80EffectName(value uint8) string {
  108. switch value {
  109. case 0:
  110. return "none"
  111. case 1:
  112. return "wave"
  113. case 2:
  114. return "fixed_wave"
  115. case 3:
  116. return "spectrum"
  117. case 4:
  118. return "breathing"
  119. case 5:
  120. return "light"
  121. case 6:
  122. return "shutdown"
  123. default:
  124. return "unknown"
  125. }
  126. }
  127. func ParseImpact80Effect(name string) (uint8, error) {
  128. switch name {
  129. case "none", "off":
  130. return 0, nil
  131. case "wave", "rainbow_wave":
  132. return 1, nil
  133. case "fixed_wave":
  134. return 2, nil
  135. case "spectrum", "rainbow":
  136. return 3, nil
  137. case "breathing", "breathe":
  138. return 4, nil
  139. case "light", "static", "solid":
  140. return 5, nil
  141. case "shutdown":
  142. return 6, nil
  143. default:
  144. return 0, fmt.Errorf("unknown Impact 80 effect: %s", name)
  145. }
  146. }
  147. // LEDParam defines QMK rgblight parameter indices.
  148. type LEDParam uint8
  149. const (
  150. Brightness LEDParam = 1
  151. EffectID LEDParam = 2
  152. Speed LEDParam = 3
  153. ColorValue LEDParam = 4
  154. Mode LEDParam = EffectID
  155. Enable LEDParam = EffectID
  156. )
  157. // Color represents an RGB color.
  158. type Color struct {
  159. R uint8
  160. G uint8
  161. B uint8
  162. }
  163. // ParseHexColor parses a 6-digit hex color string (e.g. "ff0000").
  164. func ParseHexColor(s string) (Color, error) {
  165. if len(s) != 6 {
  166. return Color{}, fmt.Errorf("invalid hex color: %s", s)
  167. }
  168. r, err := strconv.ParseUint(s[0:2], 16, 8)
  169. if err != nil {
  170. return Color{}, fmt.Errorf("invalid hex color: %s", s)
  171. }
  172. g, err := strconv.ParseUint(s[2:4], 16, 8)
  173. if err != nil {
  174. return Color{}, fmt.Errorf("invalid hex color: %s", s)
  175. }
  176. b, err := strconv.ParseUint(s[4:6], 16, 8)
  177. if err != nil {
  178. return Color{}, fmt.Errorf("invalid hex color: %s", s)
  179. }
  180. return Color{R: uint8(r), G: uint8(g), B: uint8(b)}, nil
  181. }
  182. // State represents the current RGB state from the keyboard.
  183. type State struct {
  184. Enabled bool
  185. Mode Effect
  186. Brightness uint8
  187. Speed uint8
  188. Color Color
  189. }
  190. // HSV converts the color to HSV (QMK rgblight uses 0-255 ranges for H, S, V).
  191. func (c Color) HSV() (h, s, v uint8) {
  192. r, g, b := float64(c.R), float64(c.G), float64(c.B)
  193. max := math.Max(r, math.Max(g, b))
  194. min := math.Min(r, math.Min(g, b))
  195. v = uint8(max)
  196. d := max - min
  197. if d == 0 {
  198. return 0, 0, v
  199. }
  200. if max > 0 {
  201. s = uint8((d / max) * 255)
  202. } else {
  203. return 0, 0, 0
  204. }
  205. var hFloat float64
  206. switch {
  207. case max == r:
  208. hFloat = ((g - b) / d) * 60.0
  209. case max == g:
  210. hFloat = ((b-r)/d)*60.0 + 120.0
  211. case max == b:
  212. hFloat = ((r-g)/d)*60.0 + 240.0
  213. }
  214. if hFloat < 0 {
  215. hFloat += 360.0
  216. }
  217. h = uint8((hFloat / 360.0) * 255.0)
  218. if h >= 255 {
  219. h = 254
  220. }
  221. return h, s, v
  222. }