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.

641 lines
15 KiB

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