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.

428 lines
8.0 KiB

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