apa102: change from RawColors to DisableGlobalPWM (#261)

The rationale is that both intensity limiting and temperature correction can
still be used with 8 bits resolution, but perceptual mapping can't.

Make the documentation clearer about the implications. Add global PassThruOpts
to make its clear for users.

Add example for ToRGB() using PassThruOpts.

Add unit tests.

Add flag -g to cmd/apa102.

Extracts a condition from the inner raster loop, increasing performance by up to
25%.

Fix bug in dst overrun.
pull/1/head
M-A 8 years ago committed by GitHub
parent bbc377d76a
commit 8e1f781a4d

@ -27,12 +27,30 @@ func ToRGB(p []color.NRGBA) []byte {
return b return b
} }
// NeutralTemp is the temperature where the color temperature correction is
// disabled.
//
// Use this value for Opts.Temperature so that the driver uses the exact color
// you specified, without temperature correction.
const NeutralTemp uint16 = 6500
// DefaultOpts is the recommended default options. // DefaultOpts is the recommended default options.
var DefaultOpts = Opts{ var DefaultOpts = Opts{
NumPixels: 150, // 150 LEDs is a common strip length. NumPixels: 150, // 150 LEDs is a common strip length.
Intensity: 255, // Full blinding power. Intensity: 255, // Full blinding power.
Temperature: 5000, // More pleasing white balance. Temperature: 5000, // More pleasing white balance than NeutralTemp.
RawColors: false, // Do perceptual color mapping. DisableGlobalPWM: false, // Use full 13 bits range.
}
// PassThruOpts makes the driver draw RGB pixels exactly as specified.
//
// Use this if you want the APA102 LEDs to behave like normal 8 bits LEDs
// without the extended range nor any color temperature correction.
var PassThruOpts = Opts{
NumPixels: 150,
Intensity: 255,
Temperature: NeutralTemp,
DisableGlobalPWM: true,
} }
// Opts defines the options for the device. // Opts defines the options for the device.
@ -43,15 +61,26 @@ type Opts struct {
NumPixels int NumPixels int
// Intensity is the maximum intensity level to use, on a logarithmic scale. // Intensity is the maximum intensity level to use, on a logarithmic scale.
// This is useful to safely limit current draw. // This is useful to safely limit current draw.
// Use 255 for full intensity, 0 turns all lights off.
Intensity uint8 Intensity uint8
// Temperature declares the white color to use, specified in Kelvin. Has no // Temperature declares the white color to use, specified in Kelvin. Has no
// effect when RawColors is true. // effect when RawColors is true.
// //
// This driver assumes the LEDs are emitting a 6500K white color. // This driver assumes the LEDs are emitting a 6500K white color. Use
// NeutralTemp to disable color correction.
Temperature uint16 Temperature uint16
// Skip color mapping and directly write RGB values as received. Temperature // DisableGlobalPWM disables the global 5 bits PWM and only use the 8 bit
// has no effect when this is set to true. // color channels, and also disables perceptual mapping.
RawColors bool //
// The global PWM runs at 580Hz while the color channel PWMs run at 19.2kHz.
// Because of the low frequency of the global PWM, it may result in human
// visible flicker.
//
// The driver will by default use a non-linear intensity mapping to match
// what the human eye perceives. By reducing the dynamic range from 13 bits
// to 8 bits, this also disables the dynamic perceptual mapping of intensity
// since there is not enough bits of resolution to do it effectively.
DisableGlobalPWM bool
} }
// New returns a strip that communicates over SPI to APA102 LEDs. // New returns a strip that communicates over SPI to APA102 LEDs.
@ -79,7 +108,7 @@ func New(p spi.Port, o *Opts) (*Dev, error) {
return &Dev{ return &Dev{
Intensity: o.Intensity, Intensity: o.Intensity,
Temperature: o.Temperature, Temperature: o.Temperature,
RawColors: o.RawColors, DisableGlobalPWM: o.DisableGlobalPWM,
s: c, s: c,
numPixels: o.NumPixels, numPixels: o.NumPixels,
rawBuf: buf, rawBuf: buf,
@ -94,20 +123,24 @@ func New(p spi.Port, o *Opts) (*Dev, error) {
// //
// Includes intensity and temperature correction. // Includes intensity and temperature correction.
type Dev struct { type Dev struct {
// Intensity set the intensity between 0 (off) and 255 (full brightness). // Intensity set the intensity range.
//
// See Opts.Intensity for more information.
// //
// It can be changed, it will take effect on the next Draw() or Write() call. // Takes effect on the next Draw() or Write() call.
Intensity uint8 Intensity uint8
// Temperature is the white adjustment in °Kelvin. Has no effect when // Temperature is the white adjustment in °Kelvin.
// RawColors is true.
// //
// It can be changed, it will take effect on the next Draw() or Write() call. // See Opts.Temperature for more information.
//
// Takes effect on the next Draw() or Write() call.
Temperature uint16 Temperature uint16
// Whether to write raw RGB as received or do perceptual remapping. // DisableGlobalPWM disables the use of the global 5 bits PWM.
//
// See Opts.DisableGlobalPWM for more information.
// //
// It can be changed, it will take effect on the next Draw() or Write() call. // Takes effect on the next Draw() or Write() call.
// When true, Temperature has no effect. DisableGlobalPWM bool
RawColors bool
s spi.Conn // s spi.Conn //
l lut // Updated at each .Write() call. l lut // Updated at each .Write() call.
@ -118,7 +151,7 @@ type Dev struct {
} }
func (d *Dev) String() string { func (d *Dev) String() string {
return fmt.Sprintf("APA102{I:%d, T:%dK, R:%t, %dLEDs, %s}", d.Intensity, d.Temperature, d.RawColors, d.numPixels, d.s) return fmt.Sprintf("APA102{I:%d, T:%dK, GPWM:%t, %dLEDs, %s}", d.Intensity, d.Temperature, !d.DisableGlobalPWM, d.numPixels, d.s)
} }
// ColorModel implements display.Drawer. There's no surprise, it is // ColorModel implements display.Drawer. There's no surprise, it is
@ -186,31 +219,38 @@ func (d *Dev) Halt() error {
// It is expected to be given the part where pixels are, not the header nor // It is expected to be given the part where pixels are, not the header nor
// footer. // footer.
// //
// dst is in APA102 SPI 32 bits word format. src is in RGB 24 bits word format. // dst is in APA102 SPI 32 bits word format. src is in RGB 24 bits, or 32 bits
// word format when srcHasAlpha is true. The src alpha channel is ignored in
// this case.
// //
// src cannot be longer in pixel count than dst. // src cannot be longer in pixel count than dst.
// func (d *Dev) raster(dst []byte, src []byte, srcHasAlpha bool) {
// if hasAlpha is true, the input buffer is interpreted as having alpha values
// (which are ignored).
func (d *Dev) raster(dst []byte, src []byte, hasAlpha bool) {
pBytes := 3 pBytes := 3
if hasAlpha { if srcHasAlpha {
pBytes = 4 pBytes = 4
} }
length := len(src) / pBytes length := len(src) / pBytes
if len(dst) < length { if l := len(dst) / 4; l < length {
length = len(dst) / pBytes length = l
}
if length == 0 {
// Save ourself some unneeded processing.
return
} }
d.l.init(d.Intensity, d.Temperature) d.l.init(d.Intensity, d.Temperature, !d.DisableGlobalPWM)
// For the d.RawColors == true case, allow for fast brightness scaling if d.DisableGlobalPWM {
brightness := int(d.Intensity) + 1 // Faster path when the global 5 bits PWM is forced to full intensity.
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
// The response as seen by the human eye is very non-linear. The APA-102 sOff := pBytes * i
// provides an overall brightness PWM but it is relatively slower and dOff := 4 * i
// results in human visible flicker. On the other hand the minimal color r, g, b := d.l.r[src[sOff]], d.l.g[src[sOff+1]], d.l.b[src[sOff+2]]
// (1/255) is still too intense at full brightness, so for very dark color, dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte(b), byte(g), byte(r)
// it is worth using the overall brightness PWM. The goal is to use }
// brightness!=31 as little as possible. return
}
for i := 0; i < length; i++ {
// The goal is to use brightness!=31 as little as possible.
// //
// Global brightness frequency is 580Hz and color frequency at 19.2kHz. // Global brightness frequency is 580Hz and color frequency at 19.2kHz.
// https://cpldcpu.wordpress.com/2014/08/27/apa102/ // https://cpldcpu.wordpress.com/2014/08/27/apa102/
@ -227,14 +267,6 @@ func (d *Dev) raster(dst []byte, src []byte, hasAlpha bool) {
// Computes brightness, blue, green, red. // Computes brightness, blue, green, red.
sOff := pBytes * i sOff := pBytes * i
dOff := 4 * i dOff := 4 * i
if d.RawColors {
r, g, b := src[sOff], src[sOff+1], src[sOff+2]
// Fast brightness scaling
r = uint8((uint16(r) * uint16(brightness)) >> 8)
g = uint8((uint16(g) * uint16(brightness)) >> 8)
b = uint8((uint16(b) * uint16(brightness)) >> 8)
dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte(b), byte(g), byte(r)
} else {
r, g, b := d.l.r[src[sOff]], d.l.g[src[sOff+1]], d.l.b[src[sOff+2]] r, g, b := d.l.r[src[sOff]], d.l.g[src[sOff+1]], d.l.b[src[sOff+2]]
m := r | g | b m := r | g | b
switch { switch {
@ -248,7 +280,6 @@ func (d *Dev) raster(dst []byte, src []byte, hasAlpha bool) {
dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte((b+15)/31), byte((g+15)/31), byte((r+15)/31) dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte((b+15)/31), byte((g+15)/31), byte((r+15)/31)
} }
} }
}
} }
// rasterImg is the generic version of raster that converts an image instead of raw RGB values. // rasterImg is the generic version of raster that converts an image instead of raw RGB values.
@ -292,7 +323,10 @@ func (d *Dev) rasterImg(dst []byte, rect image.Rectangle, src image.Image, srcR
// //
// maxOut is the maximum intensity of each channel on a APA102 LED. // maxOut is the maximum intensity of each channel on a APA102 LED via the
// combined intensity for the 8 bit channel PWM and 5 bit global PWM.
//
// It is 255 * 31.
const maxOut = 0x1EE1 const maxOut = 0x1EE1
// ramp converts input from [0, 0xFF] as intensity to lightness on a scale of // ramp converts input from [0, 0xFF] as intensity to lightness on a scale of
@ -331,30 +365,56 @@ func ramp(l uint8, max uint16) uint16 {
// lut is a lookup table that initializes itself on the fly. // lut is a lookup table that initializes itself on the fly.
type lut struct { type lut struct {
intensity uint8 // Set an intensity between 0 (off) and 255 (full brightness). // Set an intensity between 0 (off) and 255 (full brightness).
temperature uint16 // In Kelvin. intensity uint8
// In Kelvin.
temperature uint16
// When enabled, use a perceptual curve instead of a linear intensity.
// In this case, use a 8 bits range.
globalPWM bool
// When globalPWM is true, use maxOut range. When globalPWM is false, use 8
// bit range.
r [256]uint16 r [256]uint16
g [256]uint16 g [256]uint16
b [256]uint16 b [256]uint16
} }
func (l *lut) init(i uint8, t uint16) { func (l *lut) init(i uint8, t uint16, g bool) {
if i != l.intensity || t != l.temperature { if i == l.intensity && t == l.temperature && g == l.globalPWM {
return
}
l.intensity = i l.intensity = i
l.temperature = t l.temperature = t
tr, tg, tb := toRGBFast(l.temperature) l.globalPWM = g
tr, tg, tb := toRGBFast(t)
// Linear ramp.
if !g {
// maxR, maxG and maxB are the maximum light intensity to use per channel. // maxR, maxG and maxB are the maximum light intensity to use per channel.
maxR := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tr) + 127*127) / 65025) maxR := (int(i)*int(tr) + 127) / 255
maxG := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tg) + 127*127) / 65025) maxG := (int(i)*int(tg) + 127) / 255
maxB := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tb) + 127*127) / 65025) maxB := (int(i)*int(tb) + 127) / 255
for i := range l.r { for j := range l.r {
l.r[i] = ramp(uint8(i), maxR) // Store uint8 range instead of uint16, so it makes the inner loop faster.
l.r[j] = uint16((j*maxR + 127) / 255)
l.g[j] = uint16((j*maxG + 127) / 255)
l.b[j] = uint16((j*maxB + 127) / 255)
}
return
}
// maxR, maxG and maxB are the maximum light intensity to use per channel.
maxR := uint16((uint32(maxOut)*uint32(i)*uint32(tr) + 127*127) / 65025)
maxG := uint16((uint32(maxOut)*uint32(i)*uint32(tg) + 127*127) / 65025)
maxB := uint16((uint32(maxOut)*uint32(i)*uint32(tb) + 127*127) / 65025)
for j := range l.r {
l.r[j] = ramp(uint8(j), maxR)
} }
if maxG == maxR { if maxG == maxR {
copy(l.g[:], l.r[:]) copy(l.g[:], l.r[:])
} else { } else {
for i := range l.g { for j := range l.g {
l.g[i] = ramp(uint8(i), maxG) l.g[j] = ramp(uint8(j), maxG)
} }
} }
if maxB == maxR { if maxB == maxR {
@ -362,9 +422,8 @@ func (l *lut) init(i uint8, t uint16) {
} else if maxB == maxG { } else if maxB == maxG {
copy(l.b[:], l.g[:]) copy(l.b[:], l.g[:])
} else { } else {
for i := range l.b { for j := range l.b {
l.b[i] = ramp(uint8(i), maxB) l.b[j] = ramp(uint8(j), maxB)
}
} }
} }
} }

@ -335,9 +335,9 @@ func TestDevEmpty(t *testing.T) {
t.Fatalf("%d %v", n, err) t.Fatalf("%d %v", n, err)
} }
if expected := []byte{0x0, 0x0, 0x0, 0x0, 0xFF}; !bytes.Equal(expected, buf.Bytes()) { if expected := []byte{0x0, 0x0, 0x0, 0x0, 0xFF}; !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("\ngot: %#v\nwant: %#v\n", buf.Bytes(), expected) t.Fatalf("\ngot: %#02v\nwant: %#02v\n", buf.Bytes(), expected)
} }
if s := d.String(); s != "APA102{I:255, T:5000K, R:false, 0LEDs, recordraw}" { if s := d.String(); s != "APA102{I:255, T:5000K, GPWM:true, 0LEDs, recordraw}" {
t.Fatal(s) t.Fatal(s)
} }
} }
@ -357,7 +357,7 @@ func TestDevLen(t *testing.T) {
t.Fatalf("%d %v", n, err) t.Fatalf("%d %v", n, err)
} }
if expected := []byte{}; !bytes.Equal(expected, buf.Bytes()) { if expected := []byte{}; !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("\ngot: %#v\nwant: %#v\n", buf.Bytes(), expected) t.Fatalf("\ngot: %#02v\nwant: %#02v\n", buf.Bytes(), expected)
} }
} }
@ -397,7 +397,7 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
Intensity: 255, Intensity: 255,
Temperature: 6500, Temperature: NeutralTemp,
}, },
}, },
{ {
@ -430,11 +430,11 @@ var writeTests = []struct {
}, },
opts: Opts{ opts: Opts{
Intensity: 127, Intensity: 127,
Temperature: 6500, Temperature: NeutralTemp,
}, },
}, },
{ {
name: "Raw", name: "PassThru",
pixels: ToRGB([]color.NRGBA{ pixels: ToRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00}, {0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00}, {0xFE, 0xFE, 0xFE, 0x00},
@ -461,9 +461,74 @@ var writeTests = []struct {
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
0xFF, 0xFF,
}, },
opts: PassThruOpts,
},
{
name: "DisableGlobalPWM - Intensity",
pixels: ToRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00},
{0xF0, 0xF0, 0xF0, 0x00},
{0x80, 0x80, 0x80, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x00, 0x80, 0x00, 0x00},
{0x00, 0x00, 0x80, 0x00},
{0x00, 0x00, 0x10, 0x00},
{0x00, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00},
}),
want: []byte{
0x00, 0x00, 0x00, 0x00,
0xff, 0x40, 0x40, 0x40,
0xff, 0x40, 0x40, 0x40,
0xff, 0x3c, 0x3c, 0x3c,
0xff, 0x20, 0x20, 0x20,
0xff, 0x00, 0x00, 0x20,
0xff, 0x00, 0x20, 0x00,
0xff, 0x20, 0x00, 0x00,
0xff, 0x04, 0x00, 0x00,
0xff, 0x00, 0x00, 0x00,
0xff, 0x00, 0x00, 0x00,
0xff,
},
opts: Opts{
Intensity: 64,
Temperature: NeutralTemp,
DisableGlobalPWM: true,
},
},
{
name: "DisableGlobalPWM - Temp",
pixels: ToRGB([]color.NRGBA{
{0xFF, 0xFF, 0xFF, 0x00},
{0xFE, 0xFE, 0xFE, 0x00},
{0xF0, 0xF0, 0xF0, 0x00},
{0x80, 0x80, 0x80, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x00, 0x80, 0x00, 0x00},
{0x00, 0x00, 0x80, 0x00},
{0x00, 0x00, 0x10, 0x00},
{0x00, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00},
}),
want: []byte{
0x00, 0x00, 0x00, 0x00,
0xff, 0xd5, 0xe8, 0xff,
0xff, 0xd4, 0xe7, 0xfe,
0xff, 0xc8, 0xda, 0xf0,
0xff, 0x6b, 0x74, 0x80,
0xff, 0x00, 0x00, 0x80,
0xff, 0x00, 0x74, 0x00,
0xff, 0x6b, 0x00, 0x00,
0xff, 0x0d, 0x00, 0x00,
0xff, 0x01, 0x00, 0x00,
0xff, 0x00, 0x00, 0x00,
0xff,
},
opts: Opts{ opts: Opts{
Intensity: 255, Intensity: 255,
RawColors: true, Temperature: 5000,
DisableGlobalPWM: true,
}, },
}, },
{ {
@ -496,7 +561,7 @@ func TestWrites(t *testing.T) {
t.Fatalf("%s: Got %d bytes result, want %d", tt.name, n, len(tt.pixels)*3) t.Fatalf("%s: Got %d bytes result, want %d", tt.name, n, len(tt.pixels)*3)
} }
if !bytes.Equal(buf.Bytes(), tt.want) { if !bytes.Equal(buf.Bytes(), tt.want) {
t.Fatalf("%s:\ngot: %#v\nwant: %#v\n", tt.name, buf.Bytes(), tt.want) t.Fatalf("%s:\ngot: %#02v\nwant: %#02v\n", tt.name, buf.Bytes(), tt.want)
} }
} }
} }
@ -525,7 +590,7 @@ func TestDevLong(t *testing.T) {
trailer[i] = 0xFF trailer[i] = 0xFF
} }
if !bytes.Equal(expected, buf.Bytes()) { if !bytes.Equal(expected, buf.Bytes()) {
t.Fatalf("\ngot: %#v\nwant: %#v\n", buf.Bytes(), expected) t.Fatalf("\ngot: %#02v\nwant: %#02v\n", buf.Bytes(), expected)
} }
} }
@ -561,13 +626,13 @@ var expectedi250t6500 = []byte{
0x3E, 0x38, 0x32, 0xFF, 0xFF, 0x3E, 0x38, 0x32, 0xFF, 0xFF,
} }
// expectedi250raw is using RawColors = true. // expectedi250raw is using DisableGlobalPWM = true.
var expectedi250raw = []byte{ var expectedi250raw = []byte{
0x00, 0x00, 0x00, 0x00, 0xFF, 0x07, 0x03, 0x00, 0xFF, 0x13, 0x0F, 0x0B, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x08, 0x04, 0x00, 0xFF, 0x14, 0x10, 0x0C, 0xFF,
0x1F, 0x1B, 0x17, 0xFF, 0x2B, 0x27, 0x23, 0xFF, 0x36, 0x32, 0x2F, 0xFF, 0x42, 0x1F, 0x1B, 0x18, 0xFF, 0x2B, 0x27, 0x23, 0xFF, 0x37, 0x33, 0x2F, 0xFF, 0x43,
0x3E, 0x3A, 0xFF, 0x4E, 0x4A, 0x46, 0xFF, 0x5A, 0x56, 0x52, 0xFF, 0x65, 0x62, 0x3F, 0x3B, 0xFF, 0x4E, 0x4B, 0x47, 0xFF, 0x5A, 0x56, 0x52, 0xFF, 0x66, 0x62,
0x5E, 0xFF, 0x71, 0x6D, 0x69, 0xFF, 0x7D, 0x79, 0x75, 0xFF, 0x89, 0x85, 0x81, 0x5E, 0xFF, 0x72, 0x6E, 0x6A, 0xFF, 0x7D, 0x7A, 0x76, 0xFF, 0x89, 0x85, 0x81,
0xFF, 0x95, 0x91, 0x8D, 0xFF, 0xA0, 0x9C, 0x98, 0xFF, 0xAC, 0xA8, 0xA4, 0xFF, 0xFF, 0x95, 0x91, 0x8D, 0xFF, 0xA1, 0x9D, 0x99, 0xFF, 0xAD, 0xA9, 0xA5, 0xFF,
0xB8, 0xB4, 0xB0, 0xFF, 0xFF, 0xB8, 0xB4, 0xB0, 0xFF, 0xFF,
} }
@ -611,11 +676,11 @@ var drawTests = []struct {
opts: Opts{ opts: Opts{
NumPixels: 16, NumPixels: 16,
Intensity: 250, Intensity: 250,
Temperature: 6500, Temperature: NeutralTemp,
}, },
}, },
{ {
name: "Draw NRGBA Raw", name: "Draw NRGBA no global PWM",
img: func() image.Image { img: func() image.Image {
im := image.NewNRGBA(image.Rect(0, 0, 16, 1)) im := image.NewNRGBA(image.Rect(0, 0, 16, 1))
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
@ -630,8 +695,9 @@ var drawTests = []struct {
want: expectedi250raw, want: expectedi250raw,
opts: Opts{ opts: Opts{
NumPixels: 16, NumPixels: 16,
Temperature: NeutralTemp,
Intensity: 250, Intensity: 250,
RawColors: true, DisableGlobalPWM: true,
}, },
}, },
{ {
@ -676,7 +742,7 @@ var drawTests = []struct {
}, },
}, },
{ {
name: "Draw RGBA Raw", name: "Draw RGBA no global PWM",
img: func() image.Image { img: func() image.Image {
im := image.NewRGBA(image.Rect(0, 0, 16, 1)) im := image.NewRGBA(image.Rect(0, 0, 16, 1))
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
@ -690,8 +756,9 @@ var drawTests = []struct {
want: expectedi250raw, want: expectedi250raw,
opts: Opts{ opts: Opts{
NumPixels: 16, NumPixels: 16,
Temperature: NeutralTemp,
Intensity: 250, Intensity: 250,
RawColors: true, DisableGlobalPWM: true,
}, },
}, },
} }
@ -704,7 +771,7 @@ func TestDraws(t *testing.T) {
t.Fatalf("%s: %v", tt.name, err) t.Fatalf("%s: %v", tt.name, err)
} }
if !bytes.Equal(buf.Bytes(), tt.want) { if !bytes.Equal(buf.Bytes(), tt.want) {
t.Fatalf("%s:\ngot: %#v\nwant: %#v\n", tt.name, buf.Bytes(), tt.want) t.Fatalf("%s:\ngot: %#02v\nwant: %#02v\n", tt.name, buf.Bytes(), tt.want)
} }
} }
} }
@ -809,7 +876,7 @@ func TestOffsetDraws(t *testing.T) {
t.Fatalf("%s: %v", tt.name, err) t.Fatalf("%s: %v", tt.name, err)
} }
if !bytes.Equal(buf.Bytes(), tt.want) { if !bytes.Equal(buf.Bytes(), tt.want) {
t.Fatalf("%s:\ngot: %#v\nwant: %#v\n", tt.name, buf.Bytes(), tt.want) t.Fatalf("%s:\ngot: %#02v\nwant: %#02v\n", tt.name, buf.Bytes(), tt.want)
} }
} }
} }
@ -837,7 +904,7 @@ func TestHalt(t *testing.T) {
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
// Catch the "maxB == maxG" line. // Catch the "maxB == maxG" line.
l := lut{} l := lut{}
l.init(255, 6000) l.init(255, 6000, true)
if equalUint16(l.r[:], l.g[:]) || !equalUint16(l.g[:], l.b[:]) { if equalUint16(l.r[:], l.g[:]) || !equalUint16(l.g[:], l.b[:]) {
t.Fatal("test case is for only when maxG == maxB but maxR != maxG") t.Fatal("test case is for only when maxG == maxB but maxR != maxG")
} }
@ -896,10 +963,9 @@ func BenchmarkWriteColorful(b *testing.B) {
benchmarkWrite(b, o, 150, genColorfulPixel) benchmarkWrite(b, o, 150, genColorfulPixel)
} }
func BenchmarkWriteColorfulRaw(b *testing.B) { func BenchmarkWriteColorfulPassThru(b *testing.B) {
o := DefaultOpts o := PassThruOpts
o.Intensity = 250 o.Intensity = 250
o.RawColors = true
benchmarkWrite(b, o, 150, genColorfulPixel) benchmarkWrite(b, o, 150, genColorfulPixel)
} }
@ -955,10 +1021,9 @@ func BenchmarkDrawNRGBAColorful(b *testing.B) {
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel) benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
} }
func BenchmarkDrawNRGBAColorfulRaw(b *testing.B) { func BenchmarkDrawNRGBAColorfulPassThru(b *testing.B) {
o := DefaultOpts o := PassThruOpts
o.Intensity = 250 o.Intensity = 250
o.RawColors = true
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel) benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
} }
@ -976,10 +1041,9 @@ func BenchmarkDrawRGBAColorful(b *testing.B) {
benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel) benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
} }
func BenchmarkDrawRGBAColorfulRaw(b *testing.B) { func BenchmarkDrawRGBAColorfulPassThru(b *testing.B) {
o := DefaultOpts o := PassThruOpts
o.Intensity = 250 o.Intensity = 250
o.RawColors = true
benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel) benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
} }

@ -35,13 +35,37 @@ func Example() {
o.Temperature = 3500 o.Temperature = 3500
dev, err := apa102.New(p, &o) dev, err := apa102.New(p, &o)
if err != nil { if err != nil {
log.Fatalf("failed to open apa102: %v", err) log.Fatalf("failed to open: %v", err)
} }
img := image.NewNRGBA(image.Rect(0, 0, dev.Bounds().Dy(), 1)) img := image.NewNRGBA(image.Rect(0, 0, dev.Bounds().Dy(), 1))
for x := 0; x < img.Rect.Max.X; x++ { for x := 0; x < img.Rect.Max.X; x++ {
img.SetNRGBA(x, 0, color.NRGBA{uint8(x), uint8(255 - x), 0, 255}) img.SetNRGBA(x, 0, color.NRGBA{uint8(x), uint8(255 - x), 0, 255})
} }
if err := dev.Draw(dev.Bounds(), img, image.Point{}); err != nil { if err := dev.Draw(dev.Bounds(), img, image.Point{}); err != nil {
log.Fatalf("failed to draw: %v", err)
}
}
func ExampleToRGB() {
// Make sure periph is initialized.
if _, err := host.Init(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Use spireg SPI port registry to find the first available SPI bus.
p, err := spireg.Open("")
if err != nil {
log.Fatal(err)
}
defer p.Close()
o := apa102.PassThruOpts
o.NumPixels = 2
dev, err := apa102.New(p, &o)
if err != nil {
log.Fatalf("failed to open: %v", err)
}
if _, err = dev.Write(apa102.ToRGB([]color.NRGBA{{R: 0xFF, G: 0xFF, B: 0xFF}, {R: 0x80, G: 0x80, B: 0x80}})); err != nil {
log.Fatalf("failed to draw: %v", err)
}
} }

Loading…
Cancel
Save