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 }