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.

644 lines
16 KiB

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