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.

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