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.

129 lines
2.8 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. package models
  2. import (
  3. "fmt"
  4. "math"
  5. "strconv"
  6. "strings"
  7. )
  8. type ColorValue struct {
  9. Hue float64 `json:"h,omitempty"` // 0..360
  10. Saturation float64 `json:"s,omitempty"` // 0..=1
  11. Kelvin int `json:"kelvin,omitempty"`
  12. XY *ColorXY `json:"xy,omitempty"`
  13. }
  14. func (c *ColorValue) IsEmpty() bool {
  15. return c.XY == nil && c.Kelvin == 0 && c.Saturation == 0 && c.Hue == 0
  16. }
  17. func (c *ColorValue) IsHueSat() bool {
  18. return !c.IsKelvin()
  19. }
  20. func (c *ColorValue) IsKelvin() bool {
  21. return !c.IsXY() && c.Kelvin > 0
  22. }
  23. func (c *ColorValue) IsXY() bool {
  24. return c.XY != nil
  25. }
  26. // ToXY converts the color to XY if possible. If the color already is XY, it returns
  27. // a copy of its held value. There are no guarantees of conforming to a gamut, however.
  28. func (c *ColorValue) ToXY() (xy ColorXY, ok bool) {
  29. if c.XY != nil {
  30. xy = *c.XY
  31. ok = true
  32. } else if c.Kelvin > 0 && c.Hue < 0.001 && c.Saturation <= 0.001 {
  33. ok = false
  34. } else {
  35. xy = hsToXY(c.Hue, c.Saturation)
  36. ok = true
  37. }
  38. return
  39. }
  40. func (c *ColorValue) String() string {
  41. if c.Kelvin > 0 {
  42. return fmt.Sprintf("k:%d", c.Kelvin)
  43. }
  44. return fmt.Sprintf("hs:%.4g,%.3g", c.Hue, c.Saturation)
  45. }
  46. func ParseColorValue(raw string) (ColorValue, error) {
  47. tokens := strings.SplitN(raw, ":", 2)
  48. if len(tokens) != 2 {
  49. return ColorValue{}, ErrBadInput
  50. }
  51. switch tokens[0] {
  52. case "kelvin", "k":
  53. {
  54. parsedPart, err := strconv.Atoi(tokens[1])
  55. if err != nil {
  56. return ColorValue{}, ErrBadInput
  57. }
  58. return ColorValue{Kelvin: parsedPart}, nil
  59. }
  60. case "xy":
  61. {
  62. parts := strings.Split(tokens[1], ",")
  63. if len(parts) < 2 {
  64. return ColorValue{}, ErrUnknownColorFormat
  65. }
  66. x, err1 := strconv.ParseFloat(parts[0], 64)
  67. y, err2 := strconv.ParseFloat(parts[1], 64)
  68. if err1 != nil || err2 != nil {
  69. return ColorValue{}, ErrBadInput
  70. }
  71. return ColorValue{XY: &ColorXY{X: x, Y: y}}, nil
  72. }
  73. case "hs":
  74. {
  75. parts := strings.Split(tokens[1], ",")
  76. if len(parts) < 2 {
  77. return ColorValue{}, ErrUnknownColorFormat
  78. }
  79. part1, err1 := strconv.ParseFloat(parts[0], 64)
  80. part2, err2 := strconv.ParseFloat(parts[1], 64)
  81. if err1 != nil || err2 != nil {
  82. return ColorValue{}, ErrBadInput
  83. }
  84. return ColorValue{Hue: math.Mod(part1, 360), Saturation: math.Min(math.Max(part2, 0), 1)}, nil
  85. }
  86. case "hsk":
  87. {
  88. parts := strings.Split(tokens[1], ",")
  89. if len(parts) < 3 {
  90. return ColorValue{}, ErrUnknownColorFormat
  91. }
  92. part1, err1 := strconv.ParseFloat(parts[0], 64)
  93. part2, err2 := strconv.ParseFloat(parts[1], 64)
  94. part3, err3 := strconv.Atoi(parts[2])
  95. if err1 != nil || err2 != nil || err3 != nil {
  96. return ColorValue{}, ErrBadInput
  97. }
  98. return ColorValue{
  99. Hue: math.Mod(part1, 360),
  100. Saturation: math.Min(math.Max(part2, 0), 1),
  101. Kelvin: part3,
  102. }, nil
  103. }
  104. }
  105. return ColorValue{}, ErrUnknownColorFormat
  106. }