rework color system and make it work with drivers.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
+11
-39
@@ -1,49 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"git.aiterp.net/lucifer/new-server/internal/drivers/hue2"
|
|
||||||
"git.aiterp.net/lucifer/new-server/models"
|
"git.aiterp.net/lucifer/new-server/models"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
client := hue2.NewClient("10.80.8.8", "o2XKGgmVUGNBghYFdLUCVuinOTMxFH4pHV9PuTbU")
|
cv, _ := models.ParseColorValue("rgb:#fff")
|
||||||
bridge := hue2.NewBridge(client)
|
hs, _ := cv.ToHS()
|
||||||
|
log.Println(cv.String())
|
||||||
err := bridge.RefreshAll(context.Background())
|
log.Println(hs.String())
|
||||||
if err != nil {
|
xy, _ := hs.ToXY()
|
||||||
log.Fatalln(err)
|
log.Println(xy.String())
|
||||||
}
|
}
|
||||||
j, _ := json.Marshal(bridge.GenerateDevices())
|
|
||||||
fmt.Println(string(j))
|
func toJSON(v interface{}) string {
|
||||||
|
j, _ := json.Marshal(v)
|
||||||
ch := make(chan models.Event)
|
return string(j)
|
||||||
go func() {
|
|
||||||
for event := range ch {
|
|
||||||
log.Println("EVENT", event.Name, event.Payload)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i, dev := range bridge.GenerateDevices() {
|
|
||||||
device := dev
|
|
||||||
switch device.InternalID {
|
|
||||||
case "6d5a45b0-ec69-4417-8588-717358b05086":
|
|
||||||
c, _ := models.ParseColorValue("xy:0.22,0.18")
|
|
||||||
device.State.Color = c
|
|
||||||
device.State.Intensity = 0.3
|
|
||||||
case "a71128f4-5295-4ae4-9fbc-5541abc8739b":
|
|
||||||
c, _ := models.ParseColorValue("k:6500")
|
|
||||||
device.State.Color = c
|
|
||||||
device.State.Intensity = 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
device.ID = i + 1
|
|
||||||
bridge.Update(device)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bridge.Run(context.Background(), ch)
|
|
||||||
log.Println(err)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (s *hueLightState) Update(state models.DeviceState) {
|
|||||||
if state.Power {
|
if state.Power {
|
||||||
input.On = ptrBool(true)
|
input.On = ptrBool(true)
|
||||||
if state.Color.IsKelvin() {
|
if state.Color.IsKelvin() {
|
||||||
input.CT = ptrInt(1000000 / state.Color.Kelvin)
|
input.CT = ptrInt(1000000 / *state.Color.K)
|
||||||
if *input.CT < s.info.Capabilities.Control.CT.Min {
|
if *input.CT < s.info.Capabilities.Control.CT.Min {
|
||||||
*input.CT = s.info.Capabilities.Control.CT.Min
|
*input.CT = s.info.Capabilities.Control.CT.Min
|
||||||
}
|
}
|
||||||
@@ -51,13 +51,13 @@ func (s *hueLightState) Update(state models.DeviceState) {
|
|||||||
if s.input.CT == nil || *s.input.CT != *input.CT {
|
if s.input.CT == nil || *s.input.CT != *input.CT {
|
||||||
s.stale = true
|
s.stale = true
|
||||||
}
|
}
|
||||||
} else {
|
} else if color, ok := state.Color.ToHS(); ok {
|
||||||
input.Hue = ptrInt(int(state.Color.Hue*(65536/360)) % 65536)
|
input.Hue = ptrInt(int(color.HS.Hue*(65536/360)) % 65536)
|
||||||
if s.input.Hue == nil || *s.input.Hue != *input.Hue {
|
if s.input.Hue == nil || *s.input.Hue != *input.Hue {
|
||||||
s.stale = true
|
s.stale = true
|
||||||
}
|
}
|
||||||
|
|
||||||
input.Sat = ptrInt(int(state.Color.Saturation * 255))
|
input.Sat = ptrInt(int(color.HS.Sat * 255))
|
||||||
if *input.Sat > 254 {
|
if *input.Sat > 254 {
|
||||||
*input.Sat = 254
|
*input.Sat = 254
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ func (b *Bridge) MakeCongruent(ctx context.Context) (int, error) {
|
|||||||
lightsOut := light.Power != nil && !device.State.Power
|
lightsOut := light.Power != nil && !device.State.Power
|
||||||
if !lightsOut {
|
if !lightsOut {
|
||||||
if light.ColorTemperature != nil && device.State.Color.IsKelvin() {
|
if light.ColorTemperature != nil && device.State.Color.IsKelvin() {
|
||||||
mirek := 1000000 / device.State.Color.Kelvin
|
mirek := 1000000 / *device.State.Color.K
|
||||||
if mirek < light.ColorTemperature.MirekSchema.MirekMinimum {
|
if mirek < light.ColorTemperature.MirekSchema.MirekMinimum {
|
||||||
mirek = light.ColorTemperature.MirekSchema.MirekMinimum
|
mirek = light.ColorTemperature.MirekSchema.MirekMinimum
|
||||||
}
|
}
|
||||||
@@ -280,9 +280,10 @@ func (b *Bridge) MakeCongruent(ctx context.Context) (int, error) {
|
|||||||
update.Mirek = &mirek
|
update.Mirek = &mirek
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
} else if xy, ok := device.State.Color.ToXY(); ok && light.Color != nil {
|
} else if xyColor, ok := device.State.Color.ToXY(); ok && light.Color != nil {
|
||||||
xy = light.Color.Gamut.Conform(xy).Round()
|
xy := light.Color.Gamut.Conform(*xyColor.XY).Round()
|
||||||
if xy.DistanceTo(light.Color.XY) > 0.0009 || (light.ColorTemperature != nil && light.ColorTemperature.Mirek != nil) {
|
if xy.DistanceTo(light.Color.XY) > 0.0009 || (light.ColorTemperature != nil && light.ColorTemperature.Mirek != nil) {
|
||||||
|
log.Println(xyColor.String(), device.State.Color.String())
|
||||||
update.ColorXY = &xy
|
update.ColorXY = &xy
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
@@ -419,9 +420,7 @@ func (b *Bridge) GenerateDevices() []models.Device {
|
|||||||
}
|
}
|
||||||
if light.ColorTemperature != nil {
|
if light.ColorTemperature != nil {
|
||||||
if light.ColorTemperature.Mirek != nil {
|
if light.ColorTemperature.Mirek != nil {
|
||||||
device.State.Color = models.ColorValue{
|
device.State.Color.SetK(1000000 / *light.ColorTemperature.Mirek)
|
||||||
Kelvin: int(1000000 / *light.ColorTemperature.Mirek),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
device.Capabilities = append(device.Capabilities, models.DCColorKelvin)
|
device.Capabilities = append(device.Capabilities, models.DCColorKelvin)
|
||||||
device.DriverProperties["maxTemperature"] = 1000000 / light.ColorTemperature.MirekSchema.MirekMinimum
|
device.DriverProperties["maxTemperature"] = 1000000 / light.ColorTemperature.MirekSchema.MirekMinimum
|
||||||
@@ -530,9 +529,11 @@ func (b *Bridge) applyPatches(patches []ResourceData) {
|
|||||||
resCopy.Color = &cp
|
resCopy.Color = &cp
|
||||||
resCopy.Color.XY = patch.Color.XY
|
resCopy.Color.XY = patch.Color.XY
|
||||||
|
|
||||||
cp2 := *resCopy.ColorTemperature
|
if resCopy.ColorTemperature != nil {
|
||||||
resCopy.ColorTemperature = &cp2
|
cp2 := *resCopy.ColorTemperature
|
||||||
resCopy.ColorTemperature.Mirek = nil
|
resCopy.ColorTemperature = &cp2
|
||||||
|
resCopy.ColorTemperature.Mirek = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if patch.ColorTemperature != nil && resCopy.ColorTemperature != nil {
|
if patch.ColorTemperature != nil && resCopy.ColorTemperature != nil {
|
||||||
cp := *resCopy.ColorTemperature
|
cp := *resCopy.ColorTemperature
|
||||||
|
|||||||
@@ -169,9 +169,11 @@ func (b *Bridge) Run(ctx context.Context, debug bool) error {
|
|||||||
state.deviceState = &models.DeviceState{
|
state.deviceState = &models.DeviceState{
|
||||||
Power: p.On,
|
Power: p.On,
|
||||||
Color: models.ColorValue{
|
Color: models.ColorValue{
|
||||||
Hue: p.Hue,
|
HS: &models.ColorHS{
|
||||||
Saturation: p.Sat,
|
Hue: p.Hue,
|
||||||
Kelvin: p.Kelvin,
|
Sat: p.Sat,
|
||||||
|
},
|
||||||
|
K: &p.Kelvin,
|
||||||
},
|
},
|
||||||
Intensity: p.Bri,
|
Intensity: p.Bri,
|
||||||
}
|
}
|
||||||
@@ -204,7 +206,7 @@ func (b *Bridge) Run(ctx context.Context, debug bool) error {
|
|||||||
b.checkAndUpdateState(state)
|
b.checkAndUpdateState(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Since(state.discoveredTime) > time.Second*10 && time.Since(state.fwSpamTime) > time.Second * 30 {
|
if time.Since(state.discoveredTime) > time.Second*10 && time.Since(state.fwSpamTime) > time.Second*30 {
|
||||||
state.fwSpamTime = time.Now()
|
state.fwSpamTime = time.Now()
|
||||||
|
|
||||||
if state.firmware == nil {
|
if state.firmware == nil {
|
||||||
|
|||||||
@@ -36,17 +36,21 @@ func (s *State) generateUpdate() []Payload {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
c := s.deviceState.Color
|
c, ok := s.deviceState.Color.ToHSK()
|
||||||
|
if !ok {
|
||||||
|
c, _ = models.ParseColorValue("hsk:0,0,4000")
|
||||||
|
}
|
||||||
|
|
||||||
l := s.lightState
|
l := s.lightState
|
||||||
di := s.deviceState.Intensity
|
di := s.deviceState.Intensity
|
||||||
k := c.Kelvin
|
k := *c.K
|
||||||
if k == 0 {
|
if k == 0 {
|
||||||
k = 4000
|
k = 4000
|
||||||
}
|
}
|
||||||
if !equalish(c.Hue, l.Hue) || !equalish(c.Saturation, l.Sat) || !equalish(di, l.Bri) || k != l.Kelvin {
|
if !equalish(c.HS.Hue, l.Hue) || !equalish(c.HS.Sat, l.Sat) || !equalish(di, l.Bri) || k != l.Kelvin {
|
||||||
results = append(results, &SetColor{
|
results = append(results, &SetColor{
|
||||||
Hue: c.Hue,
|
Hue: c.HS.Hue,
|
||||||
Sat: c.Saturation,
|
Sat: c.HS.Sat,
|
||||||
Bri: di,
|
Bri: di,
|
||||||
Kelvin: k,
|
Kelvin: k,
|
||||||
TransitionTime: time.Millisecond * 150,
|
TransitionTime: time.Millisecond * 150,
|
||||||
@@ -72,11 +76,16 @@ func (s *State) handleAck(seq uint8) {
|
|||||||
prevLabel = s.lightState.Label
|
prevLabel = s.lightState.Label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
color, ok := s.deviceState.Color.ToHSK()
|
||||||
|
if !ok {
|
||||||
|
color, _ = models.ParseColorValue("hsk:0,0,4000")
|
||||||
|
}
|
||||||
|
|
||||||
s.lightState = &LightState{
|
s.lightState = &LightState{
|
||||||
Hue: s.deviceState.Color.Hue,
|
Hue: color.HS.Hue,
|
||||||
Sat: s.deviceState.Color.Saturation,
|
Sat: color.HS.Sat,
|
||||||
Bri: s.deviceState.Intensity,
|
Bri: s.deviceState.Intensity,
|
||||||
Kelvin: s.deviceState.Color.Kelvin,
|
Kelvin: *color.K,
|
||||||
On: s.deviceState.Power,
|
On: s.deviceState.Power,
|
||||||
Label: prevLabel,
|
Label: prevLabel,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -31,11 +30,12 @@ type bridge struct {
|
|||||||
func (b *bridge) Devices() []models.Device {
|
func (b *bridge) Devices() []models.Device {
|
||||||
results := make([]models.Device, 0, len(b.panels))
|
results := make([]models.Device, 0, len(b.panels))
|
||||||
for i, panel := range b.panels {
|
for i, panel := range b.panels {
|
||||||
|
// Find normalized RGB and intensity
|
||||||
red := float64(panel.ColorRGBA[0]) / 255.0
|
red := float64(panel.ColorRGBA[0]) / 255.0
|
||||||
green := float64(panel.ColorRGBA[1]) / 255.0
|
green := float64(panel.ColorRGBA[1]) / 255.0
|
||||||
blue := float64(panel.ColorRGBA[2]) / 255.0
|
blue := float64(panel.ColorRGBA[2]) / 255.0
|
||||||
|
hue, sat, value := colorful.Color{R: red, G: green, B: blue}.Hsv()
|
||||||
hue, sat, value := colorful.LinearRgb(red, green, blue).Hsv()
|
rgb := colorful.Hsv(hue, sat, 1)
|
||||||
|
|
||||||
shapeType, shapeTypeOK := shapeTypeMap[panel.ShapeType]
|
shapeType, shapeTypeOK := shapeTypeMap[panel.ShapeType]
|
||||||
if !shapeTypeOK {
|
if !shapeTypeOK {
|
||||||
@@ -70,10 +70,11 @@ func (b *bridge) Devices() []models.Device {
|
|||||||
UserProperties: nil,
|
UserProperties: nil,
|
||||||
State: models.DeviceState{
|
State: models.DeviceState{
|
||||||
Power: panel.ColorRGBA[3] == 0,
|
Power: panel.ColorRGBA[3] == 0,
|
||||||
Color: models.ColorValue{
|
Color: models.ColorValue{RGB: &models.ColorRGB{
|
||||||
Hue: math.Mod(hue, 360),
|
Red: rgb.R,
|
||||||
Saturation: sat,
|
Green: rgb.G,
|
||||||
},
|
Blue: rgb.B,
|
||||||
|
}},
|
||||||
Intensity: value,
|
Intensity: value,
|
||||||
Temperature: 0,
|
Temperature: 0,
|
||||||
},
|
},
|
||||||
@@ -172,9 +173,20 @@ func (b *bridge) Update(devices []models.Device) {
|
|||||||
for _, panel := range b.panels {
|
for _, panel := range b.panels {
|
||||||
if panel.ID == uint16(id) {
|
if panel.ID == uint16(id) {
|
||||||
if device.State.Power {
|
if device.State.Power {
|
||||||
color := colorful.Hsv(device.State.Color.Hue, device.State.Color.Saturation, device.State.Intensity)
|
rgbColor, ok := device.State.Color.ToRGB()
|
||||||
|
if !ok {
|
||||||
|
newColor := [4]byte{255, 255, 255, 255}
|
||||||
|
if newColor != panel.ColorRGBA {
|
||||||
|
panel.update(newColor, time.Now().Add(time.Millisecond*220))
|
||||||
|
}
|
||||||
|
|
||||||
red, green, blue := color.RGB255()
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb := rgbColor.RGB.AtIntensity(device.State.Intensity)
|
||||||
|
red := byte(rgb.Red * 255.9)
|
||||||
|
green := byte(rgb.Green * 255.9)
|
||||||
|
blue := byte(rgb.Blue * 255.9)
|
||||||
newColor := [4]byte{red, green, blue, 255}
|
newColor := [4]byte{red, green, blue, 255}
|
||||||
if newColor != panel.ColorRGBA {
|
if newColor != panel.ColorRGBA {
|
||||||
panel.update(newColor, time.Now().Add(time.Millisecond*220))
|
panel.update(newColor, time.Now().Add(time.Millisecond*220))
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type deviceStateRecord struct {
|
|||||||
Power bool `db:"power"`
|
Power bool `db:"power"`
|
||||||
Intensity float64 `db:"intensity"`
|
Intensity float64 `db:"intensity"`
|
||||||
Temperature int `db:"temperature"`
|
Temperature int `db:"temperature"`
|
||||||
|
Color string `db:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type devicePropertyRecord struct {
|
type devicePropertyRecord struct {
|
||||||
@@ -228,13 +229,14 @@ func (r *DeviceRepo) SaveMany(ctx context.Context, mode models.SaveMode, devices
|
|||||||
|
|
||||||
if mode == 0 || mode&models.SMState != 0 {
|
if mode == 0 || mode&models.SMState != 0 {
|
||||||
_, err = tx.NamedExecContext(ctx, `
|
_, err = tx.NamedExecContext(ctx, `
|
||||||
REPLACE INTO device_state(device_id, hue, saturation, kelvin, power, intensity, temperature)
|
REPLACE INTO device_state(device_id, hue, saturation, kelvin, power, intensity, color, temperature)
|
||||||
VALUES (:device_id, :hue, :saturation, :kelvin, :power, :intensity, :temperature)
|
VALUES (:device_id, :hue, :saturation, :kelvin, :power, :intensity, :color, :temperature)
|
||||||
`, deviceStateRecord{
|
`, deviceStateRecord{
|
||||||
DeviceID: record.ID,
|
DeviceID: record.ID,
|
||||||
Hue: device.State.Color.Hue,
|
Hue: 40,
|
||||||
Saturation: device.State.Color.Saturation,
|
Saturation: 0,
|
||||||
Kelvin: device.State.Color.Kelvin,
|
Kelvin: 0,
|
||||||
|
Color: device.State.Color.String(),
|
||||||
Power: device.State.Power,
|
Power: device.State.Power,
|
||||||
Intensity: device.State.Intensity,
|
Intensity: device.State.Intensity,
|
||||||
Temperature: device.State.Temperature,
|
Temperature: device.State.Temperature,
|
||||||
@@ -373,14 +375,12 @@ func (r *DeviceRepo) populate(ctx context.Context, records []deviceRecord) ([]mo
|
|||||||
|
|
||||||
for _, state := range states {
|
for _, state := range states {
|
||||||
if state.DeviceID == record.ID {
|
if state.DeviceID == record.ID {
|
||||||
|
color, _ := models.ParseColorValue(state.Color)
|
||||||
|
|
||||||
device.State = models.DeviceState{
|
device.State = models.DeviceState{
|
||||||
Power: state.Power,
|
Power: state.Power,
|
||||||
Color: models.ColorValue{
|
Color: color,
|
||||||
Hue: state.Hue,
|
Intensity: state.Intensity,
|
||||||
Saturation: state.Saturation,
|
|
||||||
Kelvin: state.Kelvin,
|
|
||||||
},
|
|
||||||
Intensity: state.Intensity,
|
|
||||||
Temperature: state.Temperature,
|
Temperature: state.Temperature,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type presetRecord struct {
|
|||||||
Hue float64 `db:"hue"`
|
Hue float64 `db:"hue"`
|
||||||
Saturation float64 `db:"saturation"`
|
Saturation float64 `db:"saturation"`
|
||||||
Kelvin int `db:"kelvin"`
|
Kelvin int `db:"kelvin"`
|
||||||
|
Value string `db:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ColorPresetRepo struct {
|
type ColorPresetRepo struct {
|
||||||
@@ -42,8 +43,8 @@ func (c *ColorPresetRepo) Save(ctx context.Context, preset *models.ColorPreset)
|
|||||||
if preset.ID > 0 {
|
if preset.ID > 0 {
|
||||||
_, err := c.DBX.ExecContext(
|
_, err := c.DBX.ExecContext(
|
||||||
ctx,
|
ctx,
|
||||||
"UPDATE color_preset SET name = ?, hue = ?, saturation = ?, kelvin = ? WHERE id = ?",
|
"UPDATE color_preset SET name = ?, value = ? WHERE id = ?",
|
||||||
preset.Name, preset.Value.Hue, preset.Value.Saturation, preset.Value.Kelvin, preset.ID,
|
preset.Name, preset.Value.String(), preset.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -52,8 +53,8 @@ func (c *ColorPresetRepo) Save(ctx context.Context, preset *models.ColorPreset)
|
|||||||
} else {
|
} else {
|
||||||
rs, err := c.DBX.ExecContext(
|
rs, err := c.DBX.ExecContext(
|
||||||
ctx,
|
ctx,
|
||||||
"INSERT INTO color_preset (name, hue, saturation, kelvin) VALUES (?, ?, ?, ?)",
|
"INSERT INTO color_preset (name, value, hue, saturation, kelvin) VALUES (?, ?, 0, 0, 0)",
|
||||||
preset.Name, preset.Value.Hue, preset.Value.Saturation, preset.Value.Kelvin,
|
preset.Name, preset.Value.String(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,14 +85,12 @@ func (c *ColorPresetRepo) Delete(ctx context.Context, preset *models.ColorPreset
|
|||||||
func (c *ColorPresetRepo) fromRecords(records ...presetRecord) []models.ColorPreset {
|
func (c *ColorPresetRepo) fromRecords(records ...presetRecord) []models.ColorPreset {
|
||||||
newList := make([]models.ColorPreset, len(records), len(records))
|
newList := make([]models.ColorPreset, len(records), len(records))
|
||||||
for i, record := range records {
|
for i, record := range records {
|
||||||
|
color, _ := models.ParseColorValue(record.Value)
|
||||||
|
|
||||||
newList[i] = models.ColorPreset{
|
newList[i] = models.ColorPreset{
|
||||||
ID: record.ID,
|
ID: record.ID,
|
||||||
Name: record.Name,
|
Name: record.Name,
|
||||||
Value: models.ColorValue{
|
Value: color,
|
||||||
Hue: record.Hue,
|
|
||||||
Saturation: record.Saturation,
|
|
||||||
Kelvin: record.Kelvin,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "github.com/lucasb-eyer/go-colorful"
|
||||||
|
|
||||||
|
type ColorHS struct {
|
||||||
|
Hue float64 `json:"hue"`
|
||||||
|
Sat float64 `json:"sat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs ColorHS) ToXY() ColorXY {
|
||||||
|
return hs.ToRGB().ToXY()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs ColorHS) ToRGB() ColorRGB {
|
||||||
|
c := colorful.Hsv(hs.Hue, hs.Sat, 1)
|
||||||
|
return ColorRGB{Red: c.R, Green: c.G, Blue: c.B}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "github.com/lucasb-eyer/go-colorful"
|
||||||
|
|
||||||
|
type ColorRGB struct {
|
||||||
|
Red float64 `json:"red"`
|
||||||
|
Green float64 `json:"green"`
|
||||||
|
Blue float64 `json:"blue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rgb ColorRGB) AtIntensity(intensity float64) ColorRGB {
|
||||||
|
hue, sat, _ := colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}.Hsv()
|
||||||
|
hsv2 := colorful.Hsv(hue, sat, intensity)
|
||||||
|
return ColorRGB{Red: hsv2.R, Green: hsv2.G, Blue: hsv2.B}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rgb ColorRGB) ToHS() ColorHS {
|
||||||
|
hue, sat, _ := colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}.Hsv()
|
||||||
|
return ColorHS{Hue: hue, Sat: sat}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rgb ColorRGB) ToXY() ColorXY {
|
||||||
|
x, y, z := (colorful.Color{R: rgb.Red, G: rgb.Green, B: rgb.Blue}).Xyz()
|
||||||
|
|
||||||
|
return ColorXY{
|
||||||
|
X: x / (x + y + z),
|
||||||
|
Y: y / (x + y + z),
|
||||||
|
}
|
||||||
|
}
|
||||||
+283
-44
@@ -2,62 +2,219 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ColorValue struct {
|
type ColorValue struct {
|
||||||
Hue float64 `json:"h,omitempty"` // 0..360
|
RGB *ColorRGB `json:"rgb,omitempty"`
|
||||||
Saturation float64 `json:"s,omitempty"` // 0..=1
|
HS *ColorHS `json:"hs,omitempty"`
|
||||||
Kelvin int `json:"kelvin,omitempty"`
|
K *int `json:"k,omitempty"`
|
||||||
XY *ColorXY `json:"xy,omitempty"`
|
XY *ColorXY `json:"xy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorValue) IsEmpty() bool {
|
func (cv *ColorValue) IsHueSat() bool {
|
||||||
return c.XY == nil && c.Kelvin == 0 && c.Saturation == 0 && c.Hue == 0
|
return cv.HS != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorValue) IsHueSat() bool {
|
func (cv *ColorValue) IsHueSatKelvin() bool {
|
||||||
return !c.IsKelvin()
|
return cv.HS != nil && cv.K != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorValue) IsKelvin() bool {
|
func (cv *ColorValue) IsKelvin() bool {
|
||||||
return !c.IsXY() && c.Kelvin > 0
|
return cv.K != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorValue) IsXY() bool {
|
func (cv *ColorValue) IsEmpty() bool {
|
||||||
return c.XY != nil
|
return *cv == ColorValue{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToXY converts the color to XY if possible. If the color already is XY, it returns
|
func (cv *ColorValue) SetK(k int) {
|
||||||
// a copy of its held value. There are no guarantees of conforming to a gamut, however.
|
*cv = ColorValue{K: &k}
|
||||||
func (c *ColorValue) ToXY() (xy ColorXY, ok bool) {
|
}
|
||||||
if c.XY != nil {
|
|
||||||
xy = *c.XY
|
func (cv *ColorValue) SetXY(xy ColorXY) {
|
||||||
|
*cv = ColorValue{XY: &xy}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (cv *ColorValue) ToRGB() (cv2 ColorValue, ok bool) {
|
||||||
|
if cv.RGB != nil {
|
||||||
|
rgb := *cv.RGB
|
||||||
|
cv2 = ColorValue{RGB: &rgb}
|
||||||
ok = true
|
ok = true
|
||||||
} else if c.Kelvin > 0 && c.Hue < 0.001 && c.Saturation <= 0.001 {
|
} else if cv.HS != nil {
|
||||||
ok = false
|
rgb := cv.HS.ToRGB()
|
||||||
} else {
|
cv2 = ColorValue{RGB: &rgb}
|
||||||
xy = hsToXY(c.Hue, c.Saturation)
|
ok = true
|
||||||
|
} else if cv.XY != nil {
|
||||||
|
rgb := cv.XY.ToRGB()
|
||||||
|
cv2 = ColorValue{RGB: &rgb}
|
||||||
ok = true
|
ok = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorValue) String() string {
|
func (cv *ColorValue) ToHS() (cv2 ColorValue, ok bool) {
|
||||||
if c.Kelvin > 0 {
|
if cv.HS != nil {
|
||||||
return fmt.Sprintf("k:%d", c.Kelvin)
|
hs := *cv.HS
|
||||||
|
cv2 = ColorValue{HS: &hs}
|
||||||
|
ok = true
|
||||||
|
} else if cv.RGB != nil {
|
||||||
|
hs := cv.RGB.ToHS()
|
||||||
|
cv2 = ColorValue{HS: &hs}
|
||||||
|
ok = true
|
||||||
|
} else if cv.XY != nil {
|
||||||
|
hs := cv.XY.ToHS()
|
||||||
|
cv2 = ColorValue{HS: &hs}
|
||||||
|
ok = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("hs:%.4g,%.3g", c.Hue, c.Saturation)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseColorValue(raw string) (ColorValue, error) {
|
func (cv *ColorValue) ToHSK() (cv2 ColorValue, ok bool) {
|
||||||
|
k := 4000
|
||||||
|
|
||||||
|
if cv.HS != nil {
|
||||||
|
hs := *cv.HS
|
||||||
|
cv2 = ColorValue{HS: &hs}
|
||||||
|
|
||||||
|
if cv.K != nil {
|
||||||
|
k = *cv.K
|
||||||
|
}
|
||||||
|
cv2.K = &k
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
} else if cv.RGB != nil {
|
||||||
|
hs := cv.RGB.ToHS()
|
||||||
|
cv2 = ColorValue{HS: &hs}
|
||||||
|
cv2.K = &k
|
||||||
|
ok = true
|
||||||
|
} else if cv.XY != nil {
|
||||||
|
hs := cv.XY.ToHS()
|
||||||
|
cv2 = ColorValue{HS: &hs}
|
||||||
|
cv2.K = &k
|
||||||
|
ok = true
|
||||||
|
} else if cv.K != nil {
|
||||||
|
k = *cv.K
|
||||||
|
cv2.HS = &ColorHS{Hue: 0, Sat: 0}
|
||||||
|
cv2.K = &k
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToXY tries to copy the color to an XY color.
|
||||||
|
func (cv *ColorValue) ToXY() (cv2 ColorValue, ok bool) {
|
||||||
|
if cv.XY != nil {
|
||||||
|
xy := *cv.XY
|
||||||
|
cv2 = ColorValue{XY: &xy}
|
||||||
|
ok = true
|
||||||
|
} else if cv.HS != nil {
|
||||||
|
xy := cv.HS.ToXY()
|
||||||
|
cv2 = ColorValue{XY: &xy}
|
||||||
|
ok = true
|
||||||
|
} else if cv.RGB != nil {
|
||||||
|
xy := cv.RGB.ToXY()
|
||||||
|
cv2 = ColorValue{XY: &xy}
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ColorValue) colorful() colorful.Color {
|
||||||
|
switch {
|
||||||
|
case cv.HS != nil:
|
||||||
|
return colorful.Hsv(cv.HS.Hue, cv.HS.Sat, 1)
|
||||||
|
case cv.RGB != nil:
|
||||||
|
return colorful.Color{R: cv.RGB.Red, G: cv.RGB.Green, B: cv.RGB.Blue}
|
||||||
|
case cv.XY != nil:
|
||||||
|
return colorful.Xyy(cv.XY.X, cv.XY.Y, 0.5)
|
||||||
|
default:
|
||||||
|
return colorful.Color{R: 255, B: 255, G: 255}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ColorValue) Interpolate(other ColorValue, fac float64) ColorValue {
|
||||||
|
// Special case for kelvin values.
|
||||||
|
if cv.IsKelvin() && other.IsKelvin() {
|
||||||
|
k1 := *cv.K
|
||||||
|
k2 := *cv.K
|
||||||
|
k3 := k1 + int(float64(k2-k1)*fac)
|
||||||
|
return ColorValue{K: &k3}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the colorful values.
|
||||||
|
cvCF := cv.colorful()
|
||||||
|
otherCF := other.colorful()
|
||||||
|
|
||||||
|
// Blend and normalize
|
||||||
|
blended := cvCF.BlendLuv(otherCF, fac)
|
||||||
|
blendedHue, blendedSat, _ := blended.Hsv()
|
||||||
|
blendedHs := ColorHS{Hue: blendedHue, Sat: blendedSat}
|
||||||
|
|
||||||
|
// Convert to the first's type
|
||||||
|
switch cv.Kind() {
|
||||||
|
case "rgb":
|
||||||
|
rgb := blendedHs.ToRGB()
|
||||||
|
return ColorValue{RGB: &rgb}
|
||||||
|
case "xy":
|
||||||
|
xy := blendedHs.ToXY()
|
||||||
|
return ColorValue{XY: &xy}
|
||||||
|
default:
|
||||||
|
return ColorValue{HS: &blendedHs}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ColorValue) Kind() string {
|
||||||
|
switch {
|
||||||
|
case cv.RGB != nil:
|
||||||
|
return "rgb"
|
||||||
|
case cv.XY != nil:
|
||||||
|
return "xy"
|
||||||
|
case cv.HS != nil && cv.K != nil:
|
||||||
|
return "hsk"
|
||||||
|
case cv.HS != nil:
|
||||||
|
return "hs"
|
||||||
|
case cv.K != nil:
|
||||||
|
return "k"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ColorValue) String() string {
|
||||||
|
switch {
|
||||||
|
case cv.RGB != nil:
|
||||||
|
return fmt.Sprintf("rgb:%.3f,%.3f,%.3f", cv.RGB.Red, cv.RGB.Green, cv.RGB.Blue)
|
||||||
|
case cv.XY != nil:
|
||||||
|
return fmt.Sprintf("xy:%.4f,%.4f", cv.XY.X, cv.XY.Y)
|
||||||
|
case cv.HS != nil && cv.K != nil:
|
||||||
|
return fmt.Sprintf("hsk:%.4f,%.3f,%d", cv.HS.Hue, cv.HS.Sat, *cv.K)
|
||||||
|
case cv.HS != nil:
|
||||||
|
return fmt.Sprintf("hs:%.4f,%.3f", cv.HS.Hue, cv.HS.Sat)
|
||||||
|
case cv.K != nil:
|
||||||
|
return fmt.Sprintf("k:%d", *cv.K)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseColorValue(raw string) (cv2 ColorValue, err error) {
|
||||||
|
if raw == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
tokens := strings.SplitN(raw, ":", 2)
|
tokens := strings.SplitN(raw, ":", 2)
|
||||||
if len(tokens) != 2 {
|
if len(tokens) != 2 {
|
||||||
return ColorValue{}, ErrBadInput
|
err = ErrBadInput
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tokens[0] {
|
switch tokens[0] {
|
||||||
@@ -65,65 +222,147 @@ func ParseColorValue(raw string) (ColorValue, error) {
|
|||||||
{
|
{
|
||||||
parsedPart, err := strconv.Atoi(tokens[1])
|
parsedPart, err := strconv.Atoi(tokens[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ColorValue{}, ErrBadInput
|
err = ErrBadInput
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return ColorValue{Kelvin: parsedPart}, nil
|
cv2.K = &parsedPart
|
||||||
}
|
}
|
||||||
|
|
||||||
case "xy":
|
case "xy":
|
||||||
{
|
{
|
||||||
parts := strings.Split(tokens[1], ",")
|
parts := strings.Split(tokens[1], ",")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return ColorValue{}, ErrUnknownColorFormat
|
err = ErrUnknownColorFormat
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
x, err1 := strconv.ParseFloat(parts[0], 64)
|
x, err1 := strconv.ParseFloat(parts[0], 64)
|
||||||
y, err2 := strconv.ParseFloat(parts[1], 64)
|
y, err2 := strconv.ParseFloat(parts[1], 64)
|
||||||
if err1 != nil || err2 != nil {
|
if err1 != nil || err2 != nil {
|
||||||
return ColorValue{}, ErrBadInput
|
err = ErrBadInput
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return ColorValue{XY: &ColorXY{X: x, Y: y}}, nil
|
cv2.XY = &ColorXY{x, y}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "hs":
|
case "hs":
|
||||||
{
|
{
|
||||||
parts := strings.Split(tokens[1], ",")
|
parts := strings.Split(tokens[1], ",")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return ColorValue{}, ErrUnknownColorFormat
|
err = ErrUnknownColorFormat
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
part1, err1 := strconv.ParseFloat(parts[0], 64)
|
part1, err1 := strconv.ParseFloat(parts[0], 64)
|
||||||
part2, err2 := strconv.ParseFloat(parts[1], 64)
|
part2, err2 := strconv.ParseFloat(parts[1], 64)
|
||||||
if err1 != nil || err2 != nil {
|
if err1 != nil || err2 != nil {
|
||||||
return ColorValue{}, ErrBadInput
|
err = ErrBadInput
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return ColorValue{Hue: math.Mod(part1, 360), Saturation: math.Min(math.Max(part2, 0), 1)}, nil
|
cv2.HS = &ColorHS{Hue: part1, Sat: part2}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "hsk":
|
case "hsk":
|
||||||
{
|
{
|
||||||
parts := strings.Split(tokens[1], ",")
|
parts := strings.Split(tokens[1], ",")
|
||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
return ColorValue{}, ErrUnknownColorFormat
|
err = ErrUnknownColorFormat
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
part1, err1 := strconv.ParseFloat(parts[0], 64)
|
part1, err1 := strconv.ParseFloat(parts[0], 64)
|
||||||
part2, err2 := strconv.ParseFloat(parts[1], 64)
|
part2, err2 := strconv.ParseFloat(parts[1], 64)
|
||||||
part3, err3 := strconv.Atoi(parts[2])
|
part3, err3 := strconv.Atoi(parts[2])
|
||||||
if err1 != nil || err2 != nil || err3 != nil {
|
if err1 != nil || err2 != nil || err3 != nil {
|
||||||
return ColorValue{}, ErrBadInput
|
err = ErrBadInput
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return ColorValue{
|
cv2.HS = &ColorHS{Hue: part1, Sat: part2}
|
||||||
Hue: math.Mod(part1, 360),
|
cv2.K = &part3
|
||||||
Saturation: math.Min(math.Max(part2, 0), 1),
|
}
|
||||||
Kelvin: part3,
|
|
||||||
}, nil
|
case "rgb":
|
||||||
|
{
|
||||||
|
if strings.HasPrefix(tokens[1], "#") {
|
||||||
|
hex := tokens[1][1:]
|
||||||
|
if !validHex(hex) {
|
||||||
|
err = ErrBadInput
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hex) == 6 {
|
||||||
|
cv2.RGB = &ColorRGB{
|
||||||
|
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 {
|
||||||
|
cv2.RGB = &ColorRGB{
|
||||||
|
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 = ErrBadInput
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
cv2.RGB = &ColorRGB{Red: part1, Green: part2, Blue: part3}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedRGB := cv2.RGB.ToHS().ToRGB()
|
||||||
|
cv2.RGB = &normalizedRGB
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ColorValue{}, ErrUnknownColorFormat
|
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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-33
@@ -159,6 +159,17 @@ type ColorXY struct {
|
|||||||
Y float64 `json:"y"`
|
Y float64 `json:"y"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (xy ColorXY) ToRGB() ColorRGB {
|
||||||
|
h, s, _ := colorful.Xyy(xy.X, xy.Y, 0.5).Hsv()
|
||||||
|
c := colorful.Hsv(h, s, 1)
|
||||||
|
return ColorRGB{Red: c.R, Green: c.G, Blue: c.B}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xy ColorXY) ToHS() ColorHS {
|
||||||
|
h, s, _ := colorful.Xyy(xy.X, xy.Y, 0.5).Hsv()
|
||||||
|
return ColorHS{Hue: h, Sat: s}
|
||||||
|
}
|
||||||
|
|
||||||
func (xy ColorXY) DistanceTo(other ColorXY) float64 {
|
func (xy ColorXY) DistanceTo(other ColorXY) float64 {
|
||||||
return math.Sqrt(math.Pow(xy.X-other.X, 2) + math.Pow(xy.Y-other.Y, 2))
|
return math.Sqrt(math.Pow(xy.X-other.X, 2) + math.Pow(xy.Y-other.Y, 2))
|
||||||
}
|
}
|
||||||
@@ -169,36 +180,3 @@ func (xy ColorXY) Round() ColorXY {
|
|||||||
Y: math.Round(xy.Y*10000) / 10000,
|
Y: math.Round(xy.Y*10000) / 10000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hsToXY(hue, sat float64) ColorXY {
|
|
||||||
c := colorful.Hsv(hue, sat, 1)
|
|
||||||
red255, green255, blue255 := c.RGB255()
|
|
||||||
red := float64(red255) / 255.0
|
|
||||||
green := float64(green255) / 255.0
|
|
||||||
blue := float64(blue255) / 255.0
|
|
||||||
|
|
||||||
return rgbToXY(red, green, blue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rgbToXY(red float64, green float64, blue float64) ColorXY {
|
|
||||||
x := red*0.649926 + green*0.103455 + blue*0.197109
|
|
||||||
y := red*0.234327 + green*0.743075 + blue*0.022598
|
|
||||||
z := green*0.053077 + blue*1.035763
|
|
||||||
|
|
||||||
return ColorXY{
|
|
||||||
X: x / (x + y + z),
|
|
||||||
Y: y / (x + y + z),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func screenRGBToXY(red, green, blue float64) ColorXY {
|
|
||||||
for _, component := range []*float64{&red, &green, &blue} {
|
|
||||||
if *component > 0.04045 {
|
|
||||||
*component = math.Pow((*component+0.055)/(1.055), 2.4)
|
|
||||||
} else {
|
|
||||||
*component /= 12.92
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rgbToXY(red, green, blue)
|
|
||||||
}
|
|
||||||
|
|||||||
+10
-6
@@ -87,6 +87,7 @@ var (
|
|||||||
DCColorHSK DeviceCapability = "ColorHSK"
|
DCColorHSK DeviceCapability = "ColorHSK"
|
||||||
DCColorKelvin DeviceCapability = "ColorKelvin"
|
DCColorKelvin DeviceCapability = "ColorKelvin"
|
||||||
DCColorXY DeviceCapability = "ColorXY"
|
DCColorXY DeviceCapability = "ColorXY"
|
||||||
|
DCColorRGB DeviceCapability = "ColorRGB"
|
||||||
DCButtons DeviceCapability = "Buttons"
|
DCButtons DeviceCapability = "Buttons"
|
||||||
DCPresence DeviceCapability = "Presence"
|
DCPresence DeviceCapability = "Presence"
|
||||||
DCIntensity DeviceCapability = "Intensity"
|
DCIntensity DeviceCapability = "Intensity"
|
||||||
@@ -190,7 +191,14 @@ func (d *Device) SetState(newState NewDeviceState) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.IsKelvin() && d.HasCapability(DCColorKelvin, DCColorHSK)) || (parsed.IsHueSat() && d.HasCapability(DCColorHS)) {
|
switch {
|
||||||
|
case (parsed.RGB != nil || parsed.XY != nil || parsed.HS != nil) || d.HasCapability(DCColorHS, DCColorXY, DCColorRGB):
|
||||||
|
d.State.Color = parsed
|
||||||
|
case parsed.K != nil && d.HasCapability(DCColorKelvin, DCColorHSK):
|
||||||
|
if !d.HasCapability(DCColorKelvin) {
|
||||||
|
d.State.Color.HS = &ColorHS{Hue: 0, Sat: 0}
|
||||||
|
}
|
||||||
|
|
||||||
d.State.Color = parsed
|
d.State.Color = parsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,11 +240,7 @@ func (s *NewDeviceState) Interpolate(other NewDeviceState, fac float64) NewDevic
|
|||||||
sc, err := ParseColorValue(*s.Color)
|
sc, err := ParseColorValue(*s.Color)
|
||||||
oc, err2 := ParseColorValue(*other.Color)
|
oc, err2 := ParseColorValue(*other.Color)
|
||||||
if err == nil && err2 == nil {
|
if err == nil && err2 == nil {
|
||||||
rc := ColorValue{}
|
rc := sc.Interpolate(oc, fac)
|
||||||
rc.Hue = interpolateFloat(sc.Hue, oc.Hue, fac)
|
|
||||||
rc.Saturation = interpolateFloat(sc.Saturation, oc.Saturation, fac)
|
|
||||||
rc.Kelvin = interpolateInt(sc.Kelvin, oc.Kelvin, fac)
|
|
||||||
|
|
||||||
rcStr := rc.String()
|
rcStr := rc.String()
|
||||||
n.Color = &rcStr
|
n.Color = &rcStr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
TRUNCATE color_preset;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE color_preset ADD COLUMN value VARCHAR(255) NOT NULL DEFAULT 'hs:0,0';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE color_preset DROP COLUMN value;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE device_state ADD COLUMN color VARCHAR(255) NOT NULL DEFAULT 'hs:0,0';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE device_state DROP COLUMN IF EXISTS color;
|
||||||
|
-- +goose StatementEnd
|
||||||
Reference in New Issue
Block a user