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

package color
import (
"encoding/json"
"fmt"
"git.aiterp.net/lucifer3/server/internal/gentools"
"github.com/lucasb-eyer/go-colorful"
"log"
"math"
"strconv"
"strings"
)
type Color struct {
RGB *RGB `json:"rgb,omitempty"`
HS *HueSat `json:"hs,omitempty"`
K *int `json:"k,omitempty"`
XY *XY `json:"xy,omitempty"`
}
func (col *Color) MarshalJSON() ([]byte, error) {
return json.Marshal(col.String())
}
func (col *Color) UnmarshalJSON(bytes []byte) error {
var s string
err := json.Unmarshal(bytes, &s)
if err != nil {
return err
}
*col, err = Parse(s)
return err
}
func (col *Color) IsHueSat() bool {
return col.HS != nil
}
func (col *Color) IsHueSatKelvin() bool {
return col.HS != nil && col.K != nil
}
func (col *Color) IsKelvin() bool {
return col.K != nil
}
func (col *Color) IsEmpty() bool {
return *col == Color{}
}
func (col *Color) SetK(k int) {
*col = Color{K: &k}
}
func (col *Color) SetXY(xy XY) {
*col = Color{XY: &xy}
}
func (col *Color) AlmostEquals(other Color) bool {
if col.HS != nil && other.HS != nil {
if math.Abs(col.HS.Hue-other.HS.Hue) > 0.01 {
return false
}
if math.Abs(col.HS.Sat-other.HS.Sat) > 0.01 {
return false
}
if col.K != nil && other.K != nil && *col.K != *other.K {
return false
}
return true
}
if col.K != nil {
if other.K != nil {
return false
}
return *col.K == *other.K
}
if col.RGB != nil && other.RGB != nil {
if math.Abs(col.RGB.Red-other.RGB.Red) > 0.01 {
return false
}
if math.Abs(col.RGB.Blue-other.RGB.Blue) > 0.01 {
return false
}
if math.Abs(col.RGB.Green-other.RGB.Green) > 0.01 {
return false
}
return true
}
xy1, _ := col.ToXY()
xy2, _ := other.ToXY()
return math.Abs(xy1.XY.X-xy2.XY.X)+math.Abs(xy1.XY.Y-xy2.XY.Y) < 0.001
}
// ToRGB tries to copy the color to an RGB color. If it's already RGB, it will be plainly copied, but HS
// will be returned. If ok is false, then no copying has occurred and cv2 will be blank.
func (col *Color) ToRGB() (col2 Color, ok bool) {
if col.RGB != nil {
rgb := *col.RGB
col2 = Color{RGB: &rgb}
ok = true
} else if col.HS != nil {
rgb := col.HS.ToRGB()
col2 = Color{RGB: &rgb}
ok = true
} else if col.XY != nil {
rgb := col.XY.ToRGB()
col2 = Color{RGB: &rgb}
ok = true
} else if col.K != nil {
rgb := kToRGB(*col.K)
col2 = Color{RGB: &rgb}
ok = true
}
if ok {
col2 = Color{RGB: &RGB{
Red: math.Max(col2.RGB.Red, 0.0),
Green: math.Max(col2.RGB.Green, 0.0),
Blue: math.Max(col2.RGB.Blue, 0.0),
}}
}
return
}
func (col *Color) ToHS() (col2 Color, ok bool) {
if col.HS != nil {
hs := *col.HS
col2 = Color{HS: &hs}
ok = true
} else if col.RGB != nil {
hs := col.RGB.ToHS()
col2 = Color{HS: &hs}
ok = true
} else if col.XY != nil {
hs := col.XY.ToHS()
col2 = Color{HS: &hs}
ok = true
} else if col.K != nil {
hs := kToRGB(*col.K).ToHS()
col2 = Color{HS: &hs}
ok = true
}
return
}
func (col *Color) ToHSK() (col2 Color, ok bool) {
k := 4000
if col.HS != nil {
hs := *col.HS
col2 = Color{HS: &hs}
if col.K != nil {
k = *col.K
}
col2.K = &k
ok = true
} else if col.RGB != nil {
hs := col.RGB.ToHS()
col2 = Color{HS: &hs}
col2.K = &k
ok = true
} else if col.XY != nil {
hs := col.XY.ToHS()
col2 = Color{HS: &hs}
col2.K = &k
ok = true
} else if col.K != nil {
k = *col.K
col2.HS = &HueSat{Hue: 0, Sat: 0}
col2.K = &k
ok = true
}
return
}
// ToXY tries to copy the color to an XY color.
func (col *Color) ToXY() (col2 Color, ok bool) {
if col.XY != nil {
xy := *col.XY
col2 = Color{XY: &xy}
ok = true
} else if col.HS != nil {
xy := col.HS.ToXY()
col2 = Color{XY: &xy}
ok = true
} else if col.RGB != nil {
xy := col.RGB.ToXY()
col2 = Color{XY: &xy}
ok = true
} else if col.K != nil {
xy := kToRGB(*col.K).ToXY()
col2 = Color{XY: &xy}
ok = true
}
return
}
func (col *Color) Interpolate(other Color, fac float64) Color {
if col.AlmostEquals(other) {
log.Println("ALMOST EQUAL", col.String(), other.String())
return *col
}
// Special case for both kelvin values.
if col.IsKelvin() && other.IsKelvin() {
k1 := *col.K
k2 := *other.K
if k2 > k1 {
return Color{K: gentools.Ptr(k1 + int(float64(k2-k1)*fac))}
} else {
return Color{K: gentools.Ptr(k1 - int(float64(k1-k2)*fac))}
}
}
if fac < 0.000001 {
return *col
} else if fac > 0.999999 {
return other
}
// Get the colorful values.
cvCF := col.colorful()
otherCF := other.colorful()
// Blend and normalize (clamping is hax to avoid issues with some colors)
blended := cvCF.BlendLuv(otherCF, fac)
blendedHue, blendedSat, _ := blended.Hsv()
blendedHs := HueSat{Hue: blendedHue, Sat: blendedSat}
// Convert to the first's type
switch col.Kind() {
case "rgb":
rgb := blendedHs.ToRGB()
return Color{RGB: &rgb}
case "xy":
xy := blendedHs.ToXY()
return Color{XY: &xy}
default:
return Color{HS: &blendedHs}
}
}
func (col *Color) Kind() string {
switch {
case col.RGB != nil:
return "rgb"
case col.XY != nil:
return "xy"
case col.HS != nil && col.K != nil:
return "hsk"
case col.HS != nil:
return "hs"
case col.K != nil:
return "k"
default:
return ""
}
}
func (col *Color) String() string {
switch {
case col.RGB != nil:
return fmt.Sprintf("rgb:%.3f,%.3f,%.3f", col.RGB.Red, col.RGB.Green, col.RGB.Blue)
case col.XY != nil:
return fmt.Sprintf("xy:%.4f,%.4f", col.XY.X, col.XY.Y)
case col.HS != nil && col.K != nil:
return fmt.Sprintf("hsk:%.2f,%.3f,%d", col.HS.Hue, col.HS.Sat, *col.K)
case col.HS != nil:
return fmt.Sprintf("hs:%.2f,%.3f", col.HS.Hue, col.HS.Sat)
case col.K != nil:
return fmt.Sprintf("k:%d", *col.K)
default:
return ""
}
}
func (col *Color) colorful() colorful.Color {
switch {
case col.K != nil:
col2, _ := col.ToXY()
return col2.colorful()
case col.HS != nil:
return colorful.Hsv(col.HS.Hue, col.HS.Sat, 1)
case col.RGB != nil:
return colorful.Color{R: col.RGB.Red, G: col.RGB.Green, B: col.RGB.Blue}
case col.XY != nil:
return colorful.Xyy(col.XY.X, col.XY.Y, 0.5)
default:
return colorful.Color{R: 1, B: 1, G: 1}
}
}
func MustParse(raw string) Color {
col, err := Parse(raw)
if err != nil {
panic(err)
}
return col
}
func Parse(raw string) (col Color, err error) {
if raw == "" {
return
}
convertTokens := strings.SplitN(raw, "|", 2)
tokens := strings.SplitN(convertTokens[0], ":", 2)
if len(tokens) != 2 {
err = ErrBadColorInput
return
}
switch tokens[0] {
case "kelvin", "k":
{
parsedPart, err := strconv.Atoi(tokens[1])
if err != nil {
err = ErrBadColorInput
break
}
col.K = &parsedPart
}
case "xy":
{
parts := strings.Split(tokens[1], ",")
if len(parts) < 2 {
err = ErrUnknownColorFormat
return
}
x, err1 := strconv.ParseFloat(parts[0], 64)
y, err2 := strconv.ParseFloat(parts[1], 64)
if err1 != nil || err2 != nil {
err = ErrBadColorInput
break
}
col.XY = &XY{x, y}
}
case "hs":
{
parts := strings.Split(tokens[1], ",")
if len(parts) < 2 {
err = ErrUnknownColorFormat
return
}
part1, err1 := strconv.ParseFloat(parts[0], 64)
part2, err2 := strconv.ParseFloat(parts[1], 64)
if err1 != nil || err2 != nil {
err = ErrBadColorInput
break
}
col.HS = &HueSat{Hue: part1, Sat: part2}
}
case "hsk":
{
parts := strings.Split(tokens[1], ",")
if len(parts) < 3 {
err = ErrUnknownColorFormat
return
}
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 {
err = ErrBadColorInput
break
}
col.HS = &HueSat{Hue: part1, Sat: part2}
col.K = &part3
}
case "rgb":
{
if strings.HasPrefix(tokens[1], "#") {
hex := tokens[1][1:]
if !validHex(hex) {
err = ErrBadColorInput
break
}
if len(hex) == 6 {
col.RGB = &RGB{
Red: float64(hex2num(hex[0:2])) / 255.0,
Green: float64(hex2num(hex[2:4])) / 255.0,
Blue: float64(hex2num(hex[4:6])) / 255.0,
}
} else if len(hex) == 3 {
col.RGB = &RGB{
Red: float64(hex2digit(hex[0])) / 15.0,
Green: float64(hex2digit(hex[1])) / 15.0,
Blue: float64(hex2digit(hex[2])) / 15.0,
}
} else {
err = ErrUnknownColorFormat
return
}
} else {
parts := strings.Split(tokens[1], ",")
if len(parts) < 3 {
err = ErrUnknownColorFormat
return
}
part1, err1 := strconv.ParseFloat(parts[0], 64)
part2, err2 := strconv.ParseFloat(parts[1], 64)
part3, err3 := strconv.ParseFloat(parts[2], 64)
if err1 != nil || err2 != nil || err3 != nil {
err = ErrBadColorInput
break
}
col.RGB = &RGB{Red: part1, Green: part2, Blue: part3}
}
normalizedRGB := col.RGB.ToHS().ToRGB()
col.RGB = &normalizedRGB
}
default:
err = ErrUnknownColorFormat
}
if err == nil && len(convertTokens) == 2 {
switch convertTokens[1] {
case "xy":
col2, ok := col.ToXY()
if !ok {
err = ErrUnknownColorFormat
} else {
col = col2
}
case "rgb":
col2, ok := col.ToRGB()
if !ok {
err = ErrUnknownColorFormat
} else {
col = col2
}
case "hs":
col2, ok := col.ToHS()
if !ok {
err = ErrUnknownColorFormat
} else {
col = col2
}
default:
err = ErrUnknownColorFormat
}
}
return
}
func validHex(h string) bool {
for _, ch := range h {
if !((ch >= 'a' && ch <= 'f') || (ch >= '0' || ch <= '9')) {
return false
}
}
return true
}
func hex2num(s string) int {
v := 0
for _, h := range s {
v *= 16
v += hex2digit(byte(h))
}
return v
}
func hex2digit(h byte) int {
if h >= 'a' && h <= 'f' {
return 10 + int(h-'a')
} else {
return int(h - '0')
}
}
func kToRGB(kelvin int) RGB {
if kelvin < 1000 {
kelvin = 1000
} else if kelvin > 12000 {
kelvin = 12000
}
if kelvin%100 == 0 {
return kelvinRGBTable[kelvin]
}
fac := float64(kelvin%100) / 100
floor := (kelvin / 100) * 100
ceil := floor + 100
floorRGB := kelvinRGBTable[floor]
ceilRGB := kelvinRGBTable[ceil]
return RGB{
Red: (floorRGB.Red * fac) + (ceilRGB.Red * (1 - fac)),
Green: (floorRGB.Green * fac) + (ceilRGB.Green * (1 - fac)),
Blue: (floorRGB.Blue * fac) + (ceilRGB.Blue * (1 - fac)),
}
}
var kelvinRGBTable = map[int]RGB{
1000: {Red: 1.000, Green: 0.220, Blue: 0.000},
1100: {Red: 1.000, Green: 0.278, Blue: 0.000},
1200: {Red: 1.000, Green: 0.325, Blue: 0.000},
1300: {Red: 1.000, Green: 0.365, Blue: 0.000},
1400: {Red: 1.000, Green: 0.396, Blue: 0.000},
1500: {Red: 1.000, Green: 0.427, Blue: 0.000},
1600: {Red: 1.000, Green: 0.451, Blue: 0.000},
1700: {Red: 1.000, Green: 0.475, Blue: 0.000},
1800: {Red: 1.000, Green: 0.494, Blue: 0.000},
1900: {Red: 1.000, Green: 0.514, Blue: 0.000},
2000: {Red: 1.000, Green: 0.541, Blue: 0.071},
2100: {Red: 1.000, Green: 0.557, Blue: 0.129},
2200: {Red: 1.000, Green: 0.576, Blue: 0.173},
2300: {Red: 1.000, Green: 0.596, Blue: 0.212},
2400: {Red: 1.000, Green: 0.616, Blue: 0.247},
2500: {Red: 1.000, Green: 0.631, Blue: 0.282},
2600: {Red: 1.000, Green: 0.647, Blue: 0.310},
2700: {Red: 1.000, Green: 0.663, Blue: 0.341},
2800: {Red: 1.000, Green: 0.678, Blue: 0.369},
2900: {Red: 1.000, Green: 0.694, Blue: 0.396},
3000: {Red: 1.000, Green: 0.706, Blue: 0.420},
3100: {Red: 1.000, Green: 0.722, Blue: 0.447},
3200: {Red: 1.000, Green: 0.733, Blue: 0.471},
3300: {Red: 1.000, Green: 0.745, Blue: 0.494},
3400: {Red: 1.000, Green: 0.757, Blue: 0.518},
3500: {Red: 1.000, Green: 0.769, Blue: 0.537},
3600: {Red: 1.000, Green: 0.780, Blue: 0.561},
3700: {Red: 1.000, Green: 0.788, Blue: 0.580},
3800: {Red: 1.000, Green: 0.800, Blue: 0.600},
3900: {Red: 1.000, Green: 0.808, Blue: 0.624},
4000: {Red: 1.000, Green: 0.820, Blue: 0.639},
4100: {Red: 1.000, Green: 0.827, Blue: 0.659},
4200: {Red: 1.000, Green: 0.835, Blue: 0.678},
4300: {Red: 1.000, Green: 0.843, Blue: 0.694},
4400: {Red: 1.000, Green: 0.851, Blue: 0.714},
4500: {Red: 1.000, Green: 0.859, Blue: 0.729},
4600: {Red: 1.000, Green: 0.867, Blue: 0.745},
4700: {Red: 1.000, Green: 0.875, Blue: 0.761},
4800: {Red: 1.000, Green: 0.882, Blue: 0.776},
4900: {Red: 1.000, Green: 0.890, Blue: 0.792},
5000: {Red: 1.000, Green: 0.894, Blue: 0.808},
5100: {Red: 1.000, Green: 0.902, Blue: 0.824},
5200: {Red: 1.000, Green: 0.910, Blue: 0.835},
5300: {Red: 1.000, Green: 0.914, Blue: 0.851},
5400: {Red: 1.000, Green: 0.922, Blue: 0.863},
5500: {Red: 1.000, Green: 0.925, Blue: 0.878},
5600: {Red: 1.000, Green: 0.933, Blue: 0.890},
5700: {Red: 1.000, Green: 0.937, Blue: 0.902},
5800: {Red: 1.000, Green: 0.941, Blue: 0.914},
5900: {Red: 1.000, Green: 0.949, Blue: 0.925},
6000: {Red: 1.000, Green: 0.953, Blue: 0.937},
6100: {Red: 1.000, Green: 0.957, Blue: 0.949},
6200: {Red: 1.000, Green: 0.961, Blue: 0.961},
6300: {Red: 1.000, Green: 0.965, Blue: 0.969},
6400: {Red: 1.000, Green: 0.973, Blue: 0.984},
6500: {Red: 1.000, Green: 0.976, Blue: 0.992},
6600: {Red: 0.996, Green: 0.976, Blue: 1.000},
6700: {Red: 0.988, Green: 0.969, Blue: 1.000},
6800: {Red: 0.976, Green: 0.965, Blue: 1.000},
6900: {Red: 0.969, Green: 0.961, Blue: 1.000},
7000: {Red: 0.961, Green: 0.953, Blue: 1.000},
7100: {Red: 0.953, Green: 0.949, Blue: 1.000},
7200: {Red: 0.941, Green: 0.945, Blue: 1.000},
7300: {Red: 0.937, Green: 0.941, Blue: 1.000},
7400: {Red: 0.929, Green: 0.937, Blue: 1.000},
7500: {Red: 0.922, Green: 0.933, Blue: 1.000},
7600: {Red: 0.914, Green: 0.929, Blue: 1.000},
7700: {Red: 0.906, Green: 0.925, Blue: 1.000},
7800: {Red: 0.902, Green: 0.922, Blue: 1.000},
7900: {Red: 0.894, Green: 0.918, Blue: 1.000},
8000: {Red: 0.890, Green: 0.914, Blue: 1.000},
8100: {Red: 0.882, Green: 0.910, Blue: 1.000},
8200: {Red: 0.878, Green: 0.906, Blue: 1.000},
8300: {Red: 0.871, Green: 0.902, Blue: 1.000},
8400: {Red: 0.867, Green: 0.902, Blue: 1.000},
8500: {Red: 0.863, Green: 0.898, Blue: 1.000},
8600: {Red: 0.855, Green: 0.898, Blue: 1.000},
8700: {Red: 0.851, Green: 0.890, Blue: 1.000},
8800: {Red: 0.847, Green: 0.890, Blue: 1.000},
8900: {Red: 0.843, Green: 0.886, Blue: 1.000},
9000: {Red: 0.839, Green: 0.882, Blue: 1.000},
9100: {Red: 0.831, Green: 0.882, Blue: 1.000},
9200: {Red: 0.827, Green: 0.878, Blue: 1.000},
9300: {Red: 0.824, Green: 0.875, Blue: 1.000},
9400: {Red: 0.820, Green: 0.875, Blue: 1.000},
9500: {Red: 0.816, Green: 0.871, Blue: 1.000},
9600: {Red: 0.812, Green: 0.867, Blue: 1.000},
9700: {Red: 0.812, Green: 0.867, Blue: 1.000},
9800: {Red: 0.808, Green: 0.863, Blue: 1.000},
9900: {Red: 0.804, Green: 0.863, Blue: 1.000},
10000: {Red: 0.812, Green: 0.855, Blue: 1.000},
10100: {Red: 0.812, Green: 0.855, Blue: 1.000},
10200: {Red: 0.808, Green: 0.851, Blue: 1.000},
10300: {Red: 0.804, Green: 0.851, Blue: 1.000},
10400: {Red: 0.800, Green: 0.847, Blue: 1.000},
10500: {Red: 0.800, Green: 0.847, Blue: 1.000},
10600: {Red: 0.796, Green: 0.843, Blue: 1.000},
10700: {Red: 0.792, Green: 0.843, Blue: 1.000},
10800: {Red: 0.792, Green: 0.839, Blue: 1.000},
10900: {Red: 0.788, Green: 0.839, Blue: 1.000},
11000: {Red: 0.784, Green: 0.835, Blue: 1.000},
11100: {Red: 0.784, Green: 0.835, Blue: 1.000},
11200: {Red: 0.780, Green: 0.831, Blue: 1.000},
11300: {Red: 0.776, Green: 0.831, Blue: 1.000},
11400: {Red: 0.776, Green: 0.831, Blue: 1.000},
11500: {Red: 0.773, Green: 0.827, Blue: 1.000},
11600: {Red: 0.773, Green: 0.827, Blue: 1.000},
11700: {Red: 0.773, Green: 0.824, Blue: 1.000},
11800: {Red: 0.769, Green: 0.824, Blue: 1.000},
11900: {Red: 0.765, Green: 0.824, Blue: 1.000},
12000: {Red: 0.765, Green: 0.820, Blue: 1.000},
}