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.

438 lines
8.2 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. if col.AlmostEquals(other) {
  155. return *col
  156. }
  157. // Special case for kelvin values.
  158. if col.IsKelvin() && other.IsKelvin() {
  159. k1 := *col.K
  160. k2 := *col.K
  161. k3 := k1 + int(float64(k2-k1)*fac)
  162. return Color{K: &k3}
  163. }
  164. if fac < 0.000001 {
  165. return *col
  166. } else if fac > 0.999999 {
  167. return other
  168. }
  169. // Get the colorful values.
  170. cvCF := col.colorful()
  171. otherCF := other.colorful()
  172. // Blend and normalize (clamping is hax to avoid issues with some colors)
  173. blended := cvCF.BlendLuv(otherCF, fac)
  174. blendedHue, blendedSat, _ := blended.Hsv()
  175. blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat}
  176. // Convert to the first's type
  177. switch col.Kind() {
  178. case "rgb":
  179. rgb := blendedHs.ToRGB()
  180. return Color{RGB: &rgb}
  181. case "xy":
  182. xy := blendedHs.ToXY()
  183. return Color{XY: &xy}
  184. default:
  185. return Color{HS: &blendedHs}
  186. }
  187. }
  188. func (col *Color) Kind() string {
  189. switch {
  190. case col.RGB != nil:
  191. return "rgb"
  192. case col.XY != nil:
  193. return "xy"
  194. case col.HS != nil && col.K != nil:
  195. return "hsk"
  196. case col.HS != nil:
  197. return "hs"
  198. case col.K != nil:
  199. return "k"
  200. default:
  201. return ""
  202. }
  203. }
  204. func (col *Color) String() string {
  205. switch {
  206. case col.RGB != nil:
  207. return fmt.Sprintf("rgb:%.3f,%.3f,%.3f", col.RGB.Red, col.RGB.Green, col.RGB.Blue)
  208. case col.XY != nil:
  209. return fmt.Sprintf("xy:%.4f,%.4f", col.XY.X, col.XY.Y)
  210. case col.HS != nil && col.K != nil:
  211. return fmt.Sprintf("hsk:%.4f,%.3f,%d", col.HS.Hue, col.HS.Sat, *col.K)
  212. case col.HS != nil:
  213. return fmt.Sprintf("hs:%.4f,%.3f", col.HS.Hue, col.HS.Sat)
  214. case col.K != nil:
  215. return fmt.Sprintf("k:%d", *col.K)
  216. default:
  217. return ""
  218. }
  219. }
  220. func (col *Color) colorful() colorful.Color {
  221. switch {
  222. case col.HS != nil:
  223. return colorful.Hsv(col.HS.Hue, col.HS.Sat, 1)
  224. case col.RGB != nil:
  225. return colorful.Color{R: col.RGB.Red, G: col.RGB.Green, B: col.RGB.Blue}
  226. case col.XY != nil:
  227. return colorful.Xyy(col.XY.X, col.XY.Y, 0.5)
  228. default:
  229. return colorful.Color{R: 1, B: 1, G: 1}
  230. }
  231. }
  232. func MustParse(raw string) Color {
  233. col, err := Parse(raw)
  234. if err != nil {
  235. panic(err)
  236. }
  237. return col
  238. }
  239. func Parse(raw string) (col Color, err error) {
  240. if raw == "" {
  241. return
  242. }
  243. tokens := strings.SplitN(raw, ":", 2)
  244. if len(tokens) != 2 {
  245. err = ErrBadColorInput
  246. return
  247. }
  248. switch tokens[0] {
  249. case "kelvin", "k":
  250. {
  251. parsedPart, err := strconv.Atoi(tokens[1])
  252. if err != nil {
  253. err = ErrBadColorInput
  254. break
  255. }
  256. col.K = &parsedPart
  257. }
  258. case "xy":
  259. {
  260. parts := strings.Split(tokens[1], ",")
  261. if len(parts) < 2 {
  262. err = ErrUnknownColorFormat
  263. return
  264. }
  265. x, err1 := strconv.ParseFloat(parts[0], 64)
  266. y, err2 := strconv.ParseFloat(parts[1], 64)
  267. if err1 != nil || err2 != nil {
  268. err = ErrBadColorInput
  269. break
  270. }
  271. col.XY = &XY{x, y}
  272. }
  273. case "hs":
  274. {
  275. parts := strings.Split(tokens[1], ",")
  276. if len(parts) < 2 {
  277. err = ErrUnknownColorFormat
  278. return
  279. }
  280. part1, err1 := strconv.ParseFloat(parts[0], 64)
  281. part2, err2 := strconv.ParseFloat(parts[1], 64)
  282. if err1 != nil || err2 != nil {
  283. err = ErrBadColorInput
  284. break
  285. }
  286. col.HS = &HueSat{Hue: part1, Sat: part2}
  287. }
  288. case "hsk":
  289. {
  290. parts := strings.Split(tokens[1], ",")
  291. if len(parts) < 3 {
  292. err = ErrUnknownColorFormat
  293. return
  294. }
  295. part1, err1 := strconv.ParseFloat(parts[0], 64)
  296. part2, err2 := strconv.ParseFloat(parts[1], 64)
  297. part3, err3 := strconv.Atoi(parts[2])
  298. if err1 != nil || err2 != nil || err3 != nil {
  299. err = ErrBadColorInput
  300. break
  301. }
  302. col.HS = &HueSat{Hue: part1, Sat: part2}
  303. col.K = &part3
  304. }
  305. case "rgb":
  306. {
  307. if strings.HasPrefix(tokens[1], "#") {
  308. hex := tokens[1][1:]
  309. if !validHex(hex) {
  310. err = ErrBadColorInput
  311. break
  312. }
  313. if len(hex) == 6 {
  314. col.RGB = &RGB{
  315. Red: float64(hex2num(hex[0:2])) / 255.0,
  316. Green: float64(hex2num(hex[2:4])) / 255.0,
  317. Blue: float64(hex2num(hex[4:6])) / 255.0,
  318. }
  319. } else if len(hex) == 3 {
  320. col.RGB = &RGB{
  321. Red: float64(hex2digit(hex[0])) / 15.0,
  322. Green: float64(hex2digit(hex[1])) / 15.0,
  323. Blue: float64(hex2digit(hex[2])) / 15.0,
  324. }
  325. } else {
  326. err = ErrUnknownColorFormat
  327. return
  328. }
  329. } else {
  330. parts := strings.Split(tokens[1], ",")
  331. if len(parts) < 3 {
  332. err = ErrUnknownColorFormat
  333. return
  334. }
  335. part1, err1 := strconv.ParseFloat(parts[0], 64)
  336. part2, err2 := strconv.ParseFloat(parts[1], 64)
  337. part3, err3 := strconv.ParseFloat(parts[2], 64)
  338. if err1 != nil || err2 != nil || err3 != nil {
  339. err = ErrBadColorInput
  340. break
  341. }
  342. col.RGB = &RGB{Red: part1, Green: part2, Blue: part3}
  343. }
  344. normalizedRGB := col.RGB.ToHS().ToRGB()
  345. col.RGB = &normalizedRGB
  346. }
  347. default:
  348. err = ErrUnknownColorFormat
  349. }
  350. return
  351. }
  352. func validHex(h string) bool {
  353. for _, ch := range h {
  354. if !((ch >= 'a' && ch <= 'f') || (ch >= '0' || ch <= '9')) {
  355. return false
  356. }
  357. }
  358. return true
  359. }
  360. func hex2num(s string) int {
  361. v := 0
  362. for _, h := range s {
  363. v *= 16
  364. v += hex2digit(byte(h))
  365. }
  366. return v
  367. }
  368. func hex2digit(h byte) int {
  369. if h >= 'a' && h <= 'f' {
  370. return 10 + int(h-'a')
  371. } else {
  372. return int(h - '0')
  373. }
  374. }