Skip to content

Commit

Permalink
[feat] Enable/Disable Gamma Correction (#13)
Browse files Browse the repository at this point in the history
* For correction

* correct it

* For colors

* For lock and comments

* set on/off for degamma
  • Loading branch information
hyorigo committed Oct 27, 2023
1 parent 62021d3 commit 296f19a
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 14 deletions.
13 changes: 13 additions & 0 deletions color.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,16 @@ func RandomColor() color.Color {
// convert to RGB and return
return convRGBToColor(convHSBToRGB(hue, saturation, brightness))
}

// DecodeGammaColor operates gamma correction on the given color and returns the decoded color.
// The gamma correction is automatically applied for state and pattern while playing or writing.
// This is the inverse function of EncodeGammaColor.
func DecodeGammaColor(cl color.Color) color.Color {
return convRGBToColor(degammaRGB(convColorToRGB(cl)))
}

// EncodeGammaColor operates gamma correction on the given color and returns the encoded color.
// Encoding a decoded color may not return the original color, but it will be the same color when decoded again.
func EncodeGammaColor(cl color.Color) color.Color {
return convRGBToColor(engammaRGB(convColorToRGB(cl)))
}
12 changes: 12 additions & 0 deletions color_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,15 @@ func TestRandomColor(t *testing.T) {
t.Errorf("RandomColor(*) = %v, want different colors", lc)
}
}

func TestGammaColor(t *testing.T) {
for i := 0; i < 256; i++ {
cl0 := b1.RGBToColor(uint8(i), uint8(i), uint8(i))
cl1 := b1.DecodeGammaColor(cl0)
cl2 := b1.EncodeGammaColor(cl1)
cl3 := b1.DecodeGammaColor(cl2)
if cl3 != cl1 {
t.Errorf("Decode(%v) got = %v, Decode(Encode(Decode(%v))) got = %v, different", cl0, cl1, cl0, cl3)
}
}
}
13 changes: 11 additions & 2 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
type Controller struct {
mu sync.Mutex
dev *Device
gamma bool
quitCh chan struct{}
}

Expand All @@ -20,12 +21,12 @@ func OpenController(info *hid.DeviceInfo) (*Controller, error) {
if err != nil {
return nil, err
}
return &Controller{dev: dev}, nil
return &Controller{dev: dev, gamma: true}, nil
}

// NewController creates a blink(1) controller for existing device instance.
func NewController(dev *Device) *Controller {
return &Controller{dev: dev}
return &Controller{dev: dev, gamma: true}
}

func (c *Controller) String() string {
Expand All @@ -41,3 +42,11 @@ func (c *Controller) GetDevice() *Device {
func (c *Controller) Close() {
c.dev.Close()
}

// SetGammaCorrection sets the gamma correction on/off for the controller. Default is on.
// If it is true, the gamma correction will be applied for state and pattern while playing or writing.
func (c *Controller) SetGammaCorrection(on bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.gamma = on
}
43 changes: 33 additions & 10 deletions controller_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (c *Controller) GetFirmwareVersion() (int, error) {

// PlayStateBlocking fades the given LED to the specified RGB color over the specified time, and blocks until the fade is finished.
func (c *Controller) PlayStateBlocking(st LightState) error {
// NOTE: no lock here, since PlayState will lock
// play state
if err := c.PlayState(st); err != nil {
return err
Expand All @@ -37,7 +38,10 @@ func (c *Controller) PlayState(st LightState) error {
c.mu.Lock()
defer c.mu.Unlock()

r, g, b := degammaRGB(convColorToRGB(st.Color))
r, g, b := convColorToRGB(st.Color)
if c.gamma {
r, g, b = degammaRGB(r, g, b)
}
msec := uint(st.FadeTime.Milliseconds())
return c.dev.FadeToRGB(r, g, b, msec, st.LED)
}
Expand All @@ -47,7 +51,10 @@ func (c *Controller) PlayColor(cl color.Color) error {
c.mu.Lock()
defer c.mu.Unlock()

r, g, b := degammaRGB(convColorToRGB(cl))
r, g, b := convColorToRGB(cl)
if c.gamma {
r, g, b = degammaRGB(r, g, b)
}
return c.dev.SetRGBNow(r, g, b, LEDAll)
}

Expand All @@ -66,7 +73,10 @@ func (c *Controller) PlayHSB(hue, saturation, brightness float64) error {
c.mu.Lock()
defer c.mu.Unlock()

r, g, b := degammaRGB(convHSBToRGB(hue, saturation, brightness))
r, g, b := convHSBToRGB(hue, saturation, brightness)
if c.gamma {
r, g, b = degammaRGB(r, g, b)
}
return c.dev.SetRGBNow(r, g, b, LEDAll)
}

Expand All @@ -85,6 +95,7 @@ func (c *Controller) ReadColor(ledN LEDIndex) (color.Color, error) {
// PlayPatternBlocking plays the given pattern, and blocks until the pattern is finished. It may block forever if the pattern is set to loop forever.
// If the pattern has no states, it will only play the pattern without writing states to the device's RAM, and blocks until the pattern is finished.
func (c *Controller) PlayPatternBlocking(pt Pattern) error {
// NOTE: no lock here, since PlayPattern will lock
// play pattern
if err := c.PlayPattern(pt); err != nil {
return err
Expand Down Expand Up @@ -136,17 +147,27 @@ func (c *Controller) PlayPattern(pt Pattern) error {
}

// load pattern to RAM
if err := c.LoadPattern(pt.StartPosition, pt.EndPosition, pt.Sequence); err != nil {
if err := c.loadStateSequence(pt.StartPosition, pt.EndPosition, pt.Sequence); err != nil {
return err
}

// play pattern
return c.dev.PlayLoop(true, pt.StartPosition, pt.EndPosition, pt.RepeatTimes)
}

// LoadPattern loads the given pattern to the device's RAM.
func (c *Controller) LoadPattern(posStart, posEnd uint, states []LightState) error {
sc := len(states) // sc for state counter
// LoadPattern writes the given pattern to the device's RAM and it will be lost after the device is powered off.
// To save the pattern to the device's flash, call WritePattern() after calling this function.
func (c *Controller) LoadPattern(posStart, posEnd uint, seq StateSequence) error {
c.mu.Lock()
defer c.mu.Unlock()

// load pattern to RAM
return c.loadStateSequence(posStart, posEnd, seq)
}

// loadStateSequence loads the given pattern to the device's RAM.
func (c *Controller) loadStateSequence(posStart, posEnd uint, seq StateSequence) error {
sc := len(seq) // sc for state counter
if sc == 0 {
// no states, just do nothing
return nil
Expand All @@ -164,8 +185,10 @@ func (c *Controller) LoadPattern(posStart, posEnd uint, states []LightState) err
pc := 0 // pc for position counter
for pos := posStart; pos <= posEnd; pos++ {
// convert state with degamma and set as pattern
st := convLightState(states[pc])
st.R, st.G, st.B = degammaRGB(st.R, st.G, st.B)
st := convLightState(seq[pc])
if c.gamma {
st.R, st.G, st.B = degammaRGB(st.R, st.G, st.B)
}

// operate on device
if err := retryWorkload(func() error {
Expand Down Expand Up @@ -204,7 +227,7 @@ func (c *Controller) ReadPattern() (StateSequence, error) {
return ls, nil
}

// WritePattern writes the pattern in the device's RAM to its flash.
// WritePattern writes the pattern stored in the device's RAM to its flash. For mk2 device, only the first 16 patterns can be saved.
func (c *Controller) WritePattern() error {
c.mu.Lock()
defer c.mu.Unlock()
Expand Down
2 changes: 1 addition & 1 deletion device.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Device struct {
sn string // serial number

// state
mu sync.Mutex
mu sync.Mutex // mutex lock, only for atomic operations like I/O & close
info *hid.DeviceInfo
dev hid.Device
}
Expand Down
26 changes: 25 additions & 1 deletion util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"image/color"
"math"
"math/big"
"sync"
"time"
)

Expand Down Expand Up @@ -242,7 +243,30 @@ var gammaE = []byte{
191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,
222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255}

// degammaRGB operates degamma correction for 8-bit RGB values.
// degammaRGB operates gamma correction decoding for 8-bit RGB values.
func degammaRGB(r, g, b uint8) (rr, gg, bb uint8) {
return gammaE[r], gammaE[g], gammaE[b]
}

var (
onceGamma sync.Once
gammaD []byte
)

// engammaRGB operates gamma correction encoding for 8-bit RGB values.
func engammaRGB(r, g, b uint8) (rr, gg, bb uint8) {
onceGamma.Do(func() {
l := len(gammaE)
dd := make([]byte, l)
for i := 0; i < l; i++ {
dd[gammaE[i]] = byte(i)
}
for i := 1; i < l; i++ {
if dd[i] == 0 {
dd[i] = dd[i-1]
}
}
gammaD = dd
})
return gammaD[r], gammaD[g], gammaD[b]
}

0 comments on commit 296f19a

Please sign in to comment.