You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

369 lines
7.2 KiB

  1. package color
  2. import (
  3. "fmt"
  4. "git.aiterp.net/lucifer/new-server/internal/lerrors"
  5. "github.com/lucasb-eyer/go-colorful"
  6. "strconv"
  7. "strings"
  8. )
  9. type Color struct {
  10. RGB *RGB `json:"rgb,omitempty"`
  11. HS *HueSat `json:"hs,omitempty"`
  12. K *int `json:"k,omitempty"`
  13. XY *XY `json:"xy,omitempty"`
  14. }
  15. func (col *Color) IsHueSat() bool {
  16. return col.HS != nil
  17. }
  18. func (col *Color) IsHueSatKelvin() bool {
  19. return col.HS != nil && col.K != nil
  20. }
  21. func (col *Color) IsKelvin() bool {
  22. return col.K != nil
  23. }
  24. func (col *Color) IsEmpty() bool {
  25. return *col == Color{}
  26. }
  27. func (col *Color) SetK(k int) {
  28. *col = Color{K: &k}
  29. }
  30. func (col *Color) SetXY(xy XY) {
  31. *col = Color{XY: &xy}
  32. }
  33. // ToRGB tries to copy the color to an RGB color. If it's already RGB, it will be plainly copied, but HS
  34. // will be returned. If ok is false, then no copying has occurred and cv2 will be blank.
  35. func (col *Color) ToRGB() (col2 Color, ok bool) {
  36. if col.RGB != nil {
  37. rgb := *col.RGB
  38. col2 = Color{RGB: &rgb}
  39. ok = true
  40. } else if col.HS != nil {
  41. rgb := col.HS.ToRGB()
  42. col2 = Color{RGB: &rgb}
  43. ok = true
  44. } else if col.XY != nil {
  45. rgb := col.XY.ToRGB()
  46. col2 = Color{RGB: &rgb}
  47. ok = true
  48. }
  49. return
  50. }
  51. func (col *Color) ToHS() (col2 Color, ok bool) {
  52. if col.HS != nil {
  53. hs := *col.HS
  54. col2 = Color{HS: &hs}
  55. ok = true
  56. } else if col.RGB != nil {
  57. hs := col.RGB.ToHS()
  58. col2 = Color{HS: &hs}
  59. ok = true
  60. } else if col.XY != nil {
  61. hs := col.XY.ToHS()
  62. col2 = Color{HS: &hs}
  63. ok = true
  64. }
  65. return
  66. }
  67. func (col *Color) ToHSK() (col2 Color, ok bool) {
  68. k := 4000
  69. if col.HS != nil {
  70. hs := *col.HS
  71. col2 = Color{HS: &hs}
  72. if col.K != nil {
  73. k = *col.K
  74. }
  75. col2.K = &k
  76. ok = true
  77. } else if col.RGB != nil {
  78. hs := col.RGB.ToHS()
  79. col2 = Color{HS: &hs}
  80. col2.K = &k
  81. ok = true
  82. } else if col.XY != nil {
  83. hs := col.XY.ToHS()
  84. col2 = Color{HS: &hs}
  85. col2.K = &k
  86. ok = true
  87. } else if col.K != nil {
  88. k = *col.K
  89. col2.HS = &HueSat{Hue: 0, Sat: 0}
  90. col2.K = &k
  91. ok = true
  92. }
  93. return
  94. }
  95. // ToXY tries to copy the color to an XY color.
  96. func (col *Color) ToXY() (col2 Color, ok bool) {
  97. if col.XY != nil {
  98. xy := *col.XY
  99. col2 = Color{XY: &xy}
  100. ok = true
  101. } else if col.HS != nil {
  102. xy := col.HS.ToXY()
  103. col2 = Color{XY: &xy}
  104. ok = true
  105. } else if col.RGB != nil {
  106. xy := col.RGB.ToXY()
  107. col2 = Color{XY: &xy}
  108. ok = true
  109. }
  110. return
  111. }
  112. func (col *Color) Interpolate(other Color, fac float64) Color {
  113. // Special case for kelvin values.
  114. if col.IsKelvin() && other.IsKelvin() {
  115. k1 := *col.K
  116. k2 := *col.K
  117. k3 := k1 + int(float64(k2-k1)*fac)
  118. return Color{K: &k3}
  119. }
  120. // Get the colorful values.
  121. cvCF := col.colorful()
  122. otherCF := other.colorful()
  123. // Blend and normalize
  124. blended := cvCF.BlendLuv(otherCF, fac)
  125. blendedHue, blendedSat, _ := blended.Hsv()
  126. blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat}
  127. // Convert to the first's type
  128. switch col.Kind() {
  129. case "rgb":
  130. rgb := blendedHs.ToRGB()
  131. return Color{RGB: &rgb}
  132. case "xy":
  133. xy := blendedHs.ToXY()
  134. return Color{XY: &xy}
  135. default:
  136. return Color{HS: &blendedHs}
  137. }
  138. }
  139. func (col *Color) Kind() string {
  140. switch {
  141. case col.RGB != nil:
  142. return "rgb"
  143. case col.XY != nil:
  144. return "xy"
  145. case col.HS != nil && col.K != nil:
  146. return "hsk"
  147. case col.HS != nil:
  148. return "hs"
  149. case col.K != nil:
  150. return "k"
  151. default:
  152. return ""
  153. }
  154. }
  155. func (col *Color) String() string {
  156. switch {
  157. case col.RGB != nil:
  158. return fmt.Sprintf("rgb:%.3f,%.3f,%.3f", col.RGB.Red, col.RGB.Green, col.RGB.Blue)
  159. case col.XY != nil:
  160. return fmt.Sprintf("xy:%.4f,%.4f", col.XY.X, col.XY.Y)
  161. case col.HS != nil && col.K != nil:
  162. return fmt.Sprintf("hsk:%.4f,%.3f,%d", col.HS.Hue, col.HS.Sat, *col.K)
  163. case col.HS != nil:
  164. return fmt.Sprintf("hs:%.4f,%.3f", col.HS.Hue, col.HS.Sat)
  165. case col.K != nil:
  166. return fmt.Sprintf("k:%d", *col.K)
  167. default:
  168. return ""
  169. }
  170. }
  171. func (col *Color) colorful() colorful.Color {
  172. switch {
  173. case col.HS != nil:
  174. return colorful.Hsv(col.HS.Hue, col.HS.Sat, 1)
  175. case col.RGB != nil:
  176. return colorful.Color{R: col.RGB.Red, G: col.RGB.Green, B: col.RGB.Blue}
  177. case col.XY != nil:
  178. return colorful.Xyy(col.XY.X, col.XY.Y, 0.5)
  179. default:
  180. return colorful.Color{R: 255, B: 255, G: 255}
  181. }
  182. }
  183. func Parse(raw string) (col Color, err error) {
  184. if raw == "" {
  185. return
  186. }
  187. tokens := strings.SplitN(raw, ":", 2)
  188. if len(tokens) != 2 {
  189. err = lerrors.ErrBadInput
  190. return
  191. }
  192. switch tokens[0] {
  193. case "kelvin", "k":
  194. {
  195. parsedPart, err := strconv.Atoi(tokens[1])
  196. if err != nil {
  197. err = lerrors.ErrBadInput
  198. break
  199. }
  200. col.K = &parsedPart
  201. }
  202. case "xy":
  203. {
  204. parts := strings.Split(tokens[1], ",")
  205. if len(parts) < 2 {
  206. err = lerrors.ErrUnknownColorFormat
  207. return
  208. }
  209. x, err1 := strconv.ParseFloat(parts[0], 64)
  210. y, err2 := strconv.ParseFloat(parts[1], 64)
  211. if err1 != nil || err2 != nil {
  212. err = lerrors.ErrBadInput
  213. break
  214. }
  215. col.XY = &XY{x, y}
  216. }
  217. case "hs":
  218. {
  219. parts := strings.Split(tokens[1], ",")
  220. if len(parts) < 2 {
  221. err = lerrors.ErrUnknownColorFormat
  222. return
  223. }
  224. part1, err1 := strconv.ParseFloat(parts[0], 64)
  225. part2, err2 := strconv.ParseFloat(parts[1], 64)
  226. if err1 != nil || err2 != nil {
  227. err = lerrors.ErrBadInput
  228. break
  229. }
  230. col.HS = &HueSat{Hue: part1, Sat: part2}
  231. }
  232. case "hsk":
  233. {
  234. parts := strings.Split(tokens[1], ",")
  235. if len(parts) < 3 {
  236. err = lerrors.ErrUnknownColorFormat
  237. return
  238. }
  239. part1, err1 := strconv.ParseFloat(parts[0], 64)
  240. part2, err2 := strconv.ParseFloat(parts[1], 64)
  241. part3, err3 := strconv.Atoi(parts[2])
  242. if err1 != nil || err2 != nil || err3 != nil {
  243. err = lerrors.ErrBadInput
  244. break
  245. }
  246. col.HS = &HueSat{Hue: part1, Sat: part2}
  247. col.K = &part3
  248. }
  249. case "rgb":
  250. {
  251. if strings.HasPrefix(tokens[1], "#") {
  252. hex := tokens[1][1:]
  253. if !validHex(hex) {
  254. err = lerrors.ErrBadInput
  255. break
  256. }
  257. if len(hex) == 6 {
  258. col.RGB = &RGB{
  259. Red: float64(hex2num(hex[0:2])) / 255.0,
  260. Green: float64(hex2num(hex[2:4])) / 255.0,
  261. Blue: float64(hex2num(hex[4:6])) / 255.0,
  262. }
  263. } else if len(hex) == 3 {
  264. col.RGB = &RGB{
  265. Red: float64(hex2digit(hex[0])) / 15.0,
  266. Green: float64(hex2digit(hex[1])) / 15.0,
  267. Blue: float64(hex2digit(hex[2])) / 15.0,
  268. }
  269. } else {
  270. err = lerrors.ErrUnknownColorFormat
  271. return
  272. }
  273. } else {
  274. parts := strings.Split(tokens[1], ",")
  275. if len(parts) < 3 {
  276. err = lerrors.ErrUnknownColorFormat
  277. return
  278. }
  279. part1, err1 := strconv.ParseFloat(parts[0], 64)
  280. part2, err2 := strconv.ParseFloat(parts[1], 64)
  281. part3, err3 := strconv.ParseFloat(parts[2], 64)
  282. if err1 != nil || err2 != nil || err3 != nil {
  283. err = lerrors.ErrBadInput
  284. break
  285. }
  286. col.RGB = &RGB{Red: part1, Green: part2, Blue: part3}
  287. }
  288. normalizedRGB := col.RGB.ToHS().ToRGB()
  289. col.RGB = &normalizedRGB
  290. }
  291. default:
  292. err = lerrors.ErrUnknownColorFormat
  293. }
  294. return
  295. }
  296. func validHex(h string) bool {
  297. for _, ch := range h {
  298. if !((ch >= 'a' && ch <= 'f') || (ch >= '0' || ch <= '9')) {
  299. return false
  300. }
  301. }
  302. return true
  303. }
  304. func hex2num(s string) int {
  305. v := 0
  306. for _, h := range s {
  307. v *= 16
  308. v += hex2digit(byte(h))
  309. }
  310. return v
  311. }
  312. func hex2digit(h byte) int {
  313. if h >= 'a' && h <= 'f' {
  314. return 10 + int(h-'a')
  315. } else {
  316. return int(h - '0')
  317. }
  318. }