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.

368 lines
7.1 KiB

  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 Parse(raw string) (col Color, err error) {
  183. if raw == "" {
  184. return
  185. }
  186. tokens := strings.SplitN(raw, ":", 2)
  187. if len(tokens) != 2 {
  188. err = ErrBadColorInput
  189. return
  190. }
  191. switch tokens[0] {
  192. case "kelvin", "k":
  193. {
  194. parsedPart, err := strconv.Atoi(tokens[1])
  195. if err != nil {
  196. err = ErrBadColorInput
  197. break
  198. }
  199. col.K = &parsedPart
  200. }
  201. case "xy":
  202. {
  203. parts := strings.Split(tokens[1], ",")
  204. if len(parts) < 2 {
  205. err = ErrUnknownColorFormat
  206. return
  207. }
  208. x, err1 := strconv.ParseFloat(parts[0], 64)
  209. y, err2 := strconv.ParseFloat(parts[1], 64)
  210. if err1 != nil || err2 != nil {
  211. err = ErrBadColorInput
  212. break
  213. }
  214. col.XY = &XY{x, y}
  215. }
  216. case "hs":
  217. {
  218. parts := strings.Split(tokens[1], ",")
  219. if len(parts) < 2 {
  220. err = ErrUnknownColorFormat
  221. return
  222. }
  223. part1, err1 := strconv.ParseFloat(parts[0], 64)
  224. part2, err2 := strconv.ParseFloat(parts[1], 64)
  225. if err1 != nil || err2 != nil {
  226. err = ErrBadColorInput
  227. break
  228. }
  229. col.HS = &HueSat{Hue: part1, Sat: part2}
  230. }
  231. case "hsk":
  232. {
  233. parts := strings.Split(tokens[1], ",")
  234. if len(parts) < 3 {
  235. err = ErrUnknownColorFormat
  236. return
  237. }
  238. part1, err1 := strconv.ParseFloat(parts[0], 64)
  239. part2, err2 := strconv.ParseFloat(parts[1], 64)
  240. part3, err3 := strconv.Atoi(parts[2])
  241. if err1 != nil || err2 != nil || err3 != nil {
  242. err = ErrBadColorInput
  243. break
  244. }
  245. col.HS = &HueSat{Hue: part1, Sat: part2}
  246. col.K = &part3
  247. }
  248. case "rgb":
  249. {
  250. if strings.HasPrefix(tokens[1], "#") {
  251. hex := tokens[1][1:]
  252. if !validHex(hex) {
  253. err = ErrBadColorInput
  254. break
  255. }
  256. if len(hex) == 6 {
  257. col.RGB = &RGB{
  258. Red: float64(hex2num(hex[0:2])) / 255.0,
  259. Green: float64(hex2num(hex[2:4])) / 255.0,
  260. Blue: float64(hex2num(hex[4:6])) / 255.0,
  261. }
  262. } else if len(hex) == 3 {
  263. col.RGB = &RGB{
  264. Red: float64(hex2digit(hex[0])) / 15.0,
  265. Green: float64(hex2digit(hex[1])) / 15.0,
  266. Blue: float64(hex2digit(hex[2])) / 15.0,
  267. }
  268. } else {
  269. err = ErrUnknownColorFormat
  270. return
  271. }
  272. } else {
  273. parts := strings.Split(tokens[1], ",")
  274. if len(parts) < 3 {
  275. err = ErrUnknownColorFormat
  276. return
  277. }
  278. part1, err1 := strconv.ParseFloat(parts[0], 64)
  279. part2, err2 := strconv.ParseFloat(parts[1], 64)
  280. part3, err3 := strconv.ParseFloat(parts[2], 64)
  281. if err1 != nil || err2 != nil || err3 != nil {
  282. err = ErrBadColorInput
  283. break
  284. }
  285. col.RGB = &RGB{Red: part1, Green: part2, Blue: part3}
  286. }
  287. normalizedRGB := col.RGB.ToHS().ToRGB()
  288. col.RGB = &normalizedRGB
  289. }
  290. default:
  291. err = ErrUnknownColorFormat
  292. }
  293. return
  294. }
  295. func validHex(h string) bool {
  296. for _, ch := range h {
  297. if !((ch >= 'a' && ch <= 'f') || (ch >= '0' || ch <= '9')) {
  298. return false
  299. }
  300. }
  301. return true
  302. }
  303. func hex2num(s string) int {
  304. v := 0
  305. for _, h := range s {
  306. v *= 16
  307. v += hex2digit(byte(h))
  308. }
  309. return v
  310. }
  311. func hex2digit(h byte) int {
  312. if h >= 'a' && h <= 'f' {
  313. return 10 + int(h-'a')
  314. } else {
  315. return int(h - '0')
  316. }
  317. }