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.

587 lines
14 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. } else if col.K != nil {
  90. rgb := kToRGB(*col.K)
  91. col2 = Color{RGB: &rgb}
  92. ok = true
  93. }
  94. return
  95. }
  96. func (col *Color) ToHS() (col2 Color, ok bool) {
  97. if col.HS != nil {
  98. hs := *col.HS
  99. col2 = Color{HS: &hs}
  100. ok = true
  101. } else if col.RGB != nil {
  102. hs := col.RGB.ToHS()
  103. col2 = Color{HS: &hs}
  104. ok = true
  105. } else if col.XY != nil {
  106. hs := col.XY.ToHS()
  107. col2 = Color{HS: &hs}
  108. ok = true
  109. } else if col.K != nil {
  110. hs := kToRGB(*col.K).ToHS()
  111. col2 = Color{HS: &hs}
  112. ok = true
  113. }
  114. return
  115. }
  116. func (col *Color) ToHSK() (col2 Color, ok bool) {
  117. k := 4000
  118. if col.HS != nil {
  119. hs := *col.HS
  120. col2 = Color{HS: &hs}
  121. if col.K != nil {
  122. k = *col.K
  123. }
  124. col2.K = &k
  125. ok = true
  126. } else if col.RGB != nil {
  127. hs := col.RGB.ToHS()
  128. col2 = Color{HS: &hs}
  129. col2.K = &k
  130. ok = true
  131. } else if col.XY != nil {
  132. hs := col.XY.ToHS()
  133. col2 = Color{HS: &hs}
  134. col2.K = &k
  135. ok = true
  136. } else if col.K != nil {
  137. k = *col.K
  138. col2.HS = &HueSat{Hue: 0, Sat: 0}
  139. col2.K = &k
  140. ok = true
  141. }
  142. return
  143. }
  144. // ToXY tries to copy the color to an XY color.
  145. func (col *Color) ToXY() (col2 Color, ok bool) {
  146. if col.XY != nil {
  147. xy := *col.XY
  148. col2 = Color{XY: &xy}
  149. ok = true
  150. } else if col.HS != nil {
  151. xy := col.HS.ToXY()
  152. col2 = Color{XY: &xy}
  153. ok = true
  154. } else if col.RGB != nil {
  155. xy := col.RGB.ToXY()
  156. col2 = Color{XY: &xy}
  157. ok = true
  158. } else if col.K != nil {
  159. xy := kToRGB(*col.K).ToXY()
  160. col2 = Color{XY: &xy}
  161. ok = true
  162. }
  163. return
  164. }
  165. func (col *Color) Interpolate(other Color, fac float64) Color {
  166. if col.AlmostEquals(other) {
  167. return *col
  168. }
  169. // Special case for kelvin values.
  170. if col.IsKelvin() && other.IsKelvin() {
  171. k1 := *col.K
  172. k2 := *col.K
  173. k3 := k1 + int(float64(k2-k1)*fac)
  174. return Color{K: &k3}
  175. }
  176. if fac < 0.000001 {
  177. return *col
  178. } else if fac > 0.999999 {
  179. return other
  180. }
  181. // Get the colorful values.
  182. cvCF := col.colorful()
  183. otherCF := other.colorful()
  184. // Blend and normalize (clamping is hax to avoid issues with some colors)
  185. blended := cvCF.BlendLuv(otherCF, fac)
  186. blendedHue, blendedSat, _ := blended.Hsv()
  187. blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat}
  188. // Convert to the first's type
  189. switch col.Kind() {
  190. case "rgb":
  191. rgb := blendedHs.ToRGB()
  192. return Color{RGB: &rgb}
  193. case "xy":
  194. xy := blendedHs.ToXY()
  195. return Color{XY: &xy}
  196. default:
  197. return Color{HS: &blendedHs}
  198. }
  199. }
  200. func (col *Color) Kind() string {
  201. switch {
  202. case col.RGB != nil:
  203. return "rgb"
  204. case col.XY != nil:
  205. return "xy"
  206. case col.HS != nil && col.K != nil:
  207. return "hsk"
  208. case col.HS != nil:
  209. return "hs"
  210. case col.K != nil:
  211. return "k"
  212. default:
  213. return ""
  214. }
  215. }
  216. func (col *Color) String() string {
  217. switch {
  218. case col.RGB != nil:
  219. return fmt.Sprintf("rgb:%.3f,%.3f,%.3f", col.RGB.Red, col.RGB.Green, col.RGB.Blue)
  220. case col.XY != nil:
  221. return fmt.Sprintf("xy:%.4f,%.4f", col.XY.X, col.XY.Y)
  222. case col.HS != nil && col.K != nil:
  223. return fmt.Sprintf("hsk:%.4f,%.3f,%d", col.HS.Hue, col.HS.Sat, *col.K)
  224. case col.HS != nil:
  225. return fmt.Sprintf("hs:%.4f,%.3f", col.HS.Hue, col.HS.Sat)
  226. case col.K != nil:
  227. return fmt.Sprintf("k:%d", *col.K)
  228. default:
  229. return ""
  230. }
  231. }
  232. func (col *Color) colorful() colorful.Color {
  233. switch {
  234. case col.HS != nil:
  235. return colorful.Hsv(col.HS.Hue, col.HS.Sat, 1)
  236. case col.RGB != nil:
  237. return colorful.Color{R: col.RGB.Red, G: col.RGB.Green, B: col.RGB.Blue}
  238. case col.XY != nil:
  239. return colorful.Xyy(col.XY.X, col.XY.Y, 0.5)
  240. default:
  241. return colorful.Color{R: 1, B: 1, G: 1}
  242. }
  243. }
  244. func MustParse(raw string) Color {
  245. col, err := Parse(raw)
  246. if err != nil {
  247. panic(err)
  248. }
  249. return col
  250. }
  251. func Parse(raw string) (col Color, err error) {
  252. if raw == "" {
  253. return
  254. }
  255. tokens := strings.SplitN(raw, ":", 2)
  256. if len(tokens) != 2 {
  257. err = ErrBadColorInput
  258. return
  259. }
  260. switch tokens[0] {
  261. case "kelvin", "k":
  262. {
  263. parsedPart, err := strconv.Atoi(tokens[1])
  264. if err != nil {
  265. err = ErrBadColorInput
  266. break
  267. }
  268. col.K = &parsedPart
  269. }
  270. case "xy":
  271. {
  272. parts := strings.Split(tokens[1], ",")
  273. if len(parts) < 2 {
  274. err = ErrUnknownColorFormat
  275. return
  276. }
  277. x, err1 := strconv.ParseFloat(parts[0], 64)
  278. y, err2 := strconv.ParseFloat(parts[1], 64)
  279. if err1 != nil || err2 != nil {
  280. err = ErrBadColorInput
  281. break
  282. }
  283. col.XY = &XY{x, y}
  284. }
  285. case "hs":
  286. {
  287. parts := strings.Split(tokens[1], ",")
  288. if len(parts) < 2 {
  289. err = ErrUnknownColorFormat
  290. return
  291. }
  292. part1, err1 := strconv.ParseFloat(parts[0], 64)
  293. part2, err2 := strconv.ParseFloat(parts[1], 64)
  294. if err1 != nil || err2 != nil {
  295. err = ErrBadColorInput
  296. break
  297. }
  298. col.HS = &HueSat{Hue: part1, Sat: part2}
  299. }
  300. case "hsk":
  301. {
  302. parts := strings.Split(tokens[1], ",")
  303. if len(parts) < 3 {
  304. err = ErrUnknownColorFormat
  305. return
  306. }
  307. part1, err1 := strconv.ParseFloat(parts[0], 64)
  308. part2, err2 := strconv.ParseFloat(parts[1], 64)
  309. part3, err3 := strconv.Atoi(parts[2])
  310. if err1 != nil || err2 != nil || err3 != nil {
  311. err = ErrBadColorInput
  312. break
  313. }
  314. col.HS = &HueSat{Hue: part1, Sat: part2}
  315. col.K = &part3
  316. }
  317. case "rgb":
  318. {
  319. if strings.HasPrefix(tokens[1], "#") {
  320. hex := tokens[1][1:]
  321. if !validHex(hex) {
  322. err = ErrBadColorInput
  323. break
  324. }
  325. if len(hex) == 6 {
  326. col.RGB = &RGB{
  327. Red: float64(hex2num(hex[0:2])) / 255.0,
  328. Green: float64(hex2num(hex[2:4])) / 255.0,
  329. Blue: float64(hex2num(hex[4:6])) / 255.0,
  330. }
  331. } else if len(hex) == 3 {
  332. col.RGB = &RGB{
  333. Red: float64(hex2digit(hex[0])) / 15.0,
  334. Green: float64(hex2digit(hex[1])) / 15.0,
  335. Blue: float64(hex2digit(hex[2])) / 15.0,
  336. }
  337. } else {
  338. err = ErrUnknownColorFormat
  339. return
  340. }
  341. } else {
  342. parts := strings.Split(tokens[1], ",")
  343. if len(parts) < 3 {
  344. err = ErrUnknownColorFormat
  345. return
  346. }
  347. part1, err1 := strconv.ParseFloat(parts[0], 64)
  348. part2, err2 := strconv.ParseFloat(parts[1], 64)
  349. part3, err3 := strconv.ParseFloat(parts[2], 64)
  350. if err1 != nil || err2 != nil || err3 != nil {
  351. err = ErrBadColorInput
  352. break
  353. }
  354. col.RGB = &RGB{Red: part1, Green: part2, Blue: part3}
  355. }
  356. normalizedRGB := col.RGB.ToHS().ToRGB()
  357. col.RGB = &normalizedRGB
  358. }
  359. default:
  360. err = ErrUnknownColorFormat
  361. }
  362. return
  363. }
  364. func validHex(h string) bool {
  365. for _, ch := range h {
  366. if !((ch >= 'a' && ch <= 'f') || (ch >= '0' || ch <= '9')) {
  367. return false
  368. }
  369. }
  370. return true
  371. }
  372. func hex2num(s string) int {
  373. v := 0
  374. for _, h := range s {
  375. v *= 16
  376. v += hex2digit(byte(h))
  377. }
  378. return v
  379. }
  380. func hex2digit(h byte) int {
  381. if h >= 'a' && h <= 'f' {
  382. return 10 + int(h-'a')
  383. } else {
  384. return int(h - '0')
  385. }
  386. }
  387. func kToRGB(kelvin int) RGB {
  388. if kelvin < 1000 {
  389. kelvin = 1000
  390. } else if kelvin > 12000 {
  391. kelvin = 12000
  392. }
  393. if kelvin%100 == 0 {
  394. return kelvinRGBTable[kelvin]
  395. }
  396. fac := float64(kelvin%100) / 100
  397. floor := (kelvin / 100) * 100
  398. ceil := floor + 100
  399. floorRGB := kelvinRGBTable[floor]
  400. ceilRGB := kelvinRGBTable[ceil]
  401. return RGB{
  402. Red: (floorRGB.Red * fac) + (ceilRGB.Red * (1 - fac)),
  403. Green: (floorRGB.Green * fac) + (ceilRGB.Green * (1 - fac)),
  404. Blue: (floorRGB.Blue * fac) + (ceilRGB.Blue * (1 - fac)),
  405. }
  406. }
  407. var kelvinRGBTable = map[int]RGB{
  408. 1000: {Red: 1.000, Green: 0.220, Blue: 0.000},
  409. 1100: {Red: 1.000, Green: 0.278, Blue: 0.000},
  410. 1200: {Red: 1.000, Green: 0.325, Blue: 0.000},
  411. 1300: {Red: 1.000, Green: 0.365, Blue: 0.000},
  412. 1400: {Red: 1.000, Green: 0.396, Blue: 0.000},
  413. 1500: {Red: 1.000, Green: 0.427, Blue: 0.000},
  414. 1600: {Red: 1.000, Green: 0.451, Blue: 0.000},
  415. 1700: {Red: 1.000, Green: 0.475, Blue: 0.000},
  416. 1800: {Red: 1.000, Green: 0.494, Blue: 0.000},
  417. 1900: {Red: 1.000, Green: 0.514, Blue: 0.000},
  418. 2000: {Red: 1.000, Green: 0.541, Blue: 0.071},
  419. 2100: {Red: 1.000, Green: 0.557, Blue: 0.129},
  420. 2200: {Red: 1.000, Green: 0.576, Blue: 0.173},
  421. 2300: {Red: 1.000, Green: 0.596, Blue: 0.212},
  422. 2400: {Red: 1.000, Green: 0.616, Blue: 0.247},
  423. 2500: {Red: 1.000, Green: 0.631, Blue: 0.282},
  424. 2600: {Red: 1.000, Green: 0.647, Blue: 0.310},
  425. 2700: {Red: 1.000, Green: 0.663, Blue: 0.341},
  426. 2800: {Red: 1.000, Green: 0.678, Blue: 0.369},
  427. 2900: {Red: 1.000, Green: 0.694, Blue: 0.396},
  428. 3000: {Red: 1.000, Green: 0.706, Blue: 0.420},
  429. 3100: {Red: 1.000, Green: 0.722, Blue: 0.447},
  430. 3200: {Red: 1.000, Green: 0.733, Blue: 0.471},
  431. 3300: {Red: 1.000, Green: 0.745, Blue: 0.494},
  432. 3400: {Red: 1.000, Green: 0.757, Blue: 0.518},
  433. 3500: {Red: 1.000, Green: 0.769, Blue: 0.537},
  434. 3600: {Red: 1.000, Green: 0.780, Blue: 0.561},
  435. 3700: {Red: 1.000, Green: 0.788, Blue: 0.580},
  436. 3800: {Red: 1.000, Green: 0.800, Blue: 0.600},
  437. 3900: {Red: 1.000, Green: 0.808, Blue: 0.624},
  438. 4000: {Red: 1.000, Green: 0.820, Blue: 0.639},
  439. 4100: {Red: 1.000, Green: 0.827, Blue: 0.659},
  440. 4200: {Red: 1.000, Green: 0.835, Blue: 0.678},
  441. 4300: {Red: 1.000, Green: 0.843, Blue: 0.694},
  442. 4400: {Red: 1.000, Green: 0.851, Blue: 0.714},
  443. 4500: {Red: 1.000, Green: 0.859, Blue: 0.729},
  444. 4600: {Red: 1.000, Green: 0.867, Blue: 0.745},
  445. 4700: {Red: 1.000, Green: 0.875, Blue: 0.761},
  446. 4800: {Red: 1.000, Green: 0.882, Blue: 0.776},
  447. 4900: {Red: 1.000, Green: 0.890, Blue: 0.792},
  448. 5000: {Red: 1.000, Green: 0.894, Blue: 0.808},
  449. 5100: {Red: 1.000, Green: 0.902, Blue: 0.824},
  450. 5200: {Red: 1.000, Green: 0.910, Blue: 0.835},
  451. 5300: {Red: 1.000, Green: 0.914, Blue: 0.851},
  452. 5400: {Red: 1.000, Green: 0.922, Blue: 0.863},
  453. 5500: {Red: 1.000, Green: 0.925, Blue: 0.878},
  454. 5600: {Red: 1.000, Green: 0.933, Blue: 0.890},
  455. 5700: {Red: 1.000, Green: 0.937, Blue: 0.902},
  456. 5800: {Red: 1.000, Green: 0.941, Blue: 0.914},
  457. 5900: {Red: 1.000, Green: 0.949, Blue: 0.925},
  458. 6000: {Red: 1.000, Green: 0.953, Blue: 0.937},
  459. 6100: {Red: 1.000, Green: 0.957, Blue: 0.949},
  460. 6200: {Red: 1.000, Green: 0.961, Blue: 0.961},
  461. 6300: {Red: 1.000, Green: 0.965, Blue: 0.969},
  462. 6400: {Red: 1.000, Green: 0.973, Blue: 0.984},
  463. 6500: {Red: 1.000, Green: 0.976, Blue: 0.992},
  464. 6600: {Red: 0.996, Green: 0.976, Blue: 1.000},
  465. 6700: {Red: 0.988, Green: 0.969, Blue: 1.000},
  466. 6800: {Red: 0.976, Green: 0.965, Blue: 1.000},
  467. 6900: {Red: 0.969, Green: 0.961, Blue: 1.000},
  468. 7000: {Red: 0.961, Green: 0.953, Blue: 1.000},
  469. 7100: {Red: 0.953, Green: 0.949, Blue: 1.000},
  470. 7200: {Red: 0.941, Green: 0.945, Blue: 1.000},
  471. 7300: {Red: 0.937, Green: 0.941, Blue: 1.000},
  472. 7400: {Red: 0.929, Green: 0.937, Blue: 1.000},
  473. 7500: {Red: 0.922, Green: 0.933, Blue: 1.000},
  474. 7600: {Red: 0.914, Green: 0.929, Blue: 1.000},
  475. 7700: {Red: 0.906, Green: 0.925, Blue: 1.000},
  476. 7800: {Red: 0.902, Green: 0.922, Blue: 1.000},
  477. 7900: {Red: 0.894, Green: 0.918, Blue: 1.000},
  478. 8000: {Red: 0.890, Green: 0.914, Blue: 1.000},
  479. 8100: {Red: 0.882, Green: 0.910, Blue: 1.000},
  480. 8200: {Red: 0.878, Green: 0.906, Blue: 1.000},
  481. 8300: {Red: 0.871, Green: 0.902, Blue: 1.000},
  482. 8400: {Red: 0.867, Green: 0.902, Blue: 1.000},
  483. 8500: {Red: 0.863, Green: 0.898, Blue: 1.000},
  484. 8600: {Red: 0.855, Green: 0.898, Blue: 1.000},
  485. 8700: {Red: 0.851, Green: 0.890, Blue: 1.000},
  486. 8800: {Red: 0.847, Green: 0.890, Blue: 1.000},
  487. 8900: {Red: 0.843, Green: 0.886, Blue: 1.000},
  488. 9000: {Red: 0.839, Green: 0.882, Blue: 1.000},
  489. 9100: {Red: 0.831, Green: 0.882, Blue: 1.000},
  490. 9200: {Red: 0.827, Green: 0.878, Blue: 1.000},
  491. 9300: {Red: 0.824, Green: 0.875, Blue: 1.000},
  492. 9400: {Red: 0.820, Green: 0.875, Blue: 1.000},
  493. 9500: {Red: 0.816, Green: 0.871, Blue: 1.000},
  494. 9600: {Red: 0.812, Green: 0.867, Blue: 1.000},
  495. 9700: {Red: 0.812, Green: 0.867, Blue: 1.000},
  496. 9800: {Red: 0.808, Green: 0.863, Blue: 1.000},
  497. 9900: {Red: 0.804, Green: 0.863, Blue: 1.000},
  498. 10000: {Red: 0.812, Green: 0.855, Blue: 1.000},
  499. 10100: {Red: 0.812, Green: 0.855, Blue: 1.000},
  500. 10200: {Red: 0.808, Green: 0.851, Blue: 1.000},
  501. 10300: {Red: 0.804, Green: 0.851, Blue: 1.000},
  502. 10400: {Red: 0.800, Green: 0.847, Blue: 1.000},
  503. 10500: {Red: 0.800, Green: 0.847, Blue: 1.000},
  504. 10600: {Red: 0.796, Green: 0.843, Blue: 1.000},
  505. 10700: {Red: 0.792, Green: 0.843, Blue: 1.000},
  506. 10800: {Red: 0.792, Green: 0.839, Blue: 1.000},
  507. 10900: {Red: 0.788, Green: 0.839, Blue: 1.000},
  508. 11000: {Red: 0.784, Green: 0.835, Blue: 1.000},
  509. 11100: {Red: 0.784, Green: 0.835, Blue: 1.000},
  510. 11200: {Red: 0.780, Green: 0.831, Blue: 1.000},
  511. 11300: {Red: 0.776, Green: 0.831, Blue: 1.000},
  512. 11400: {Red: 0.776, Green: 0.831, Blue: 1.000},
  513. 11500: {Red: 0.773, Green: 0.827, Blue: 1.000},
  514. 11600: {Red: 0.773, Green: 0.827, Blue: 1.000},
  515. 11700: {Red: 0.773, Green: 0.824, Blue: 1.000},
  516. 11800: {Red: 0.769, Green: 0.824, Blue: 1.000},
  517. 11900: {Red: 0.765, Green: 0.824, Blue: 1.000},
  518. 12000: {Red: 0.765, Green: 0.820, Blue: 1.000},
  519. }