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
129 lines
2.8 KiB
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type ColorValue struct {
|
|
Hue float64 `json:"h,omitempty"` // 0..360
|
|
Saturation float64 `json:"s,omitempty"` // 0..=1
|
|
Kelvin int `json:"kelvin,omitempty"`
|
|
XY *ColorXY `json:"xy,omitempty"`
|
|
}
|
|
|
|
func (c *ColorValue) IsEmpty() bool {
|
|
return c.XY == nil && c.Kelvin == 0 && c.Saturation == 0 && c.Hue == 0
|
|
}
|
|
|
|
func (c *ColorValue) IsHueSat() bool {
|
|
return !c.IsKelvin()
|
|
}
|
|
|
|
func (c *ColorValue) IsKelvin() bool {
|
|
return !c.IsXY() && c.Kelvin > 0
|
|
}
|
|
|
|
func (c *ColorValue) IsXY() bool {
|
|
return c.XY != nil
|
|
}
|
|
|
|
// ToXY converts the color to XY if possible. If the color already is XY, it returns
|
|
// a copy of its held value. There are no guarantees of conforming to a gamut, however.
|
|
func (c *ColorValue) ToXY() (xy ColorXY, ok bool) {
|
|
if c.XY != nil {
|
|
xy = *c.XY
|
|
ok = true
|
|
} else if c.Kelvin > 0 && c.Hue < 0.001 && c.Saturation <= 0.001 {
|
|
ok = false
|
|
} else {
|
|
xy = hsToXY(c.Hue, c.Saturation)
|
|
ok = true
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *ColorValue) String() string {
|
|
if c.Kelvin > 0 {
|
|
return fmt.Sprintf("k:%d", c.Kelvin)
|
|
}
|
|
|
|
return fmt.Sprintf("hs:%.4g,%.3g", c.Hue, c.Saturation)
|
|
}
|
|
|
|
func ParseColorValue(raw string) (ColorValue, error) {
|
|
tokens := strings.SplitN(raw, ":", 2)
|
|
if len(tokens) != 2 {
|
|
return ColorValue{}, ErrBadInput
|
|
}
|
|
|
|
switch tokens[0] {
|
|
case "kelvin", "k":
|
|
{
|
|
parsedPart, err := strconv.Atoi(tokens[1])
|
|
if err != nil {
|
|
return ColorValue{}, ErrBadInput
|
|
}
|
|
|
|
return ColorValue{Kelvin: parsedPart}, nil
|
|
}
|
|
|
|
case "xy":
|
|
{
|
|
parts := strings.Split(tokens[1], ",")
|
|
if len(parts) < 2 {
|
|
return ColorValue{}, ErrUnknownColorFormat
|
|
}
|
|
|
|
x, err1 := strconv.ParseFloat(parts[0], 64)
|
|
y, err2 := strconv.ParseFloat(parts[1], 64)
|
|
if err1 != nil || err2 != nil {
|
|
return ColorValue{}, ErrBadInput
|
|
}
|
|
|
|
return ColorValue{XY: &ColorXY{X: x, Y: y}}, nil
|
|
}
|
|
|
|
case "hs":
|
|
{
|
|
parts := strings.Split(tokens[1], ",")
|
|
if len(parts) < 2 {
|
|
return ColorValue{}, ErrUnknownColorFormat
|
|
}
|
|
|
|
part1, err1 := strconv.ParseFloat(parts[0], 64)
|
|
part2, err2 := strconv.ParseFloat(parts[1], 64)
|
|
if err1 != nil || err2 != nil {
|
|
return ColorValue{}, ErrBadInput
|
|
}
|
|
|
|
return ColorValue{Hue: math.Mod(part1, 360), Saturation: math.Min(math.Max(part2, 0), 1)}, nil
|
|
}
|
|
|
|
case "hsk":
|
|
{
|
|
parts := strings.Split(tokens[1], ",")
|
|
if len(parts) < 3 {
|
|
return ColorValue{}, ErrUnknownColorFormat
|
|
}
|
|
|
|
part1, err1 := strconv.ParseFloat(parts[0], 64)
|
|
part2, err2 := strconv.ParseFloat(parts[1], 64)
|
|
part3, err3 := strconv.Atoi(parts[2])
|
|
if err1 != nil || err2 != nil || err3 != nil {
|
|
return ColorValue{}, ErrBadInput
|
|
}
|
|
|
|
return ColorValue{
|
|
Hue: math.Mod(part1, 360),
|
|
Saturation: math.Min(math.Max(part2, 0), 1),
|
|
Kelvin: part3,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
return ColorValue{}, ErrUnknownColorFormat
|
|
}
|