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.

377 lines
7.2 KiB

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