diff --git a/devices/apa102/apa102.go b/devices/apa102/apa102.go index b63b59f..27fca9d 100644 --- a/devices/apa102/apa102.go +++ b/devices/apa102/apa102.go @@ -27,12 +27,30 @@ func ToRGB(p []color.NRGBA) []byte { 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. var DefaultOpts = Opts{ - NumPixels: 150, // 150 LEDs is a common strip length. - Intensity: 255, // Full blinding power. - Temperature: 5000, // More pleasing white balance. - RawColors: false, // Do perceptual color mapping. + NumPixels: 150, // 150 LEDs is a common strip length. + Intensity: 255, // Full blinding power. + Temperature: 5000, // More pleasing white balance than NeutralTemp. + 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. @@ -43,15 +61,26 @@ type Opts struct { NumPixels int // Intensity is the maximum intensity level to use, on a logarithmic scale. // This is useful to safely limit current draw. + // Use 255 for full intensity, 0 turns all lights off. Intensity uint8 // Temperature declares the white color to use, specified in Kelvin. Has no // 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 - // Skip color mapping and directly write RGB values as received. Temperature - // has no effect when this is set to true. - RawColors bool + // DisableGlobalPWM disables the global 5 bits PWM and only use the 8 bit + // color channels, and also disables perceptual mapping. + // + // 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. @@ -77,14 +106,14 @@ func New(p spi.Port, o *Opts) (*Dev, error) { tail[i] = 0xFF } return &Dev{ - Intensity: o.Intensity, - Temperature: o.Temperature, - RawColors: o.RawColors, - s: c, - numPixels: o.NumPixels, - rawBuf: buf, - pixels: buf[4 : 4+4*o.NumPixels], - rect: image.Rect(0, 0, o.NumPixels, 1), + Intensity: o.Intensity, + Temperature: o.Temperature, + DisableGlobalPWM: o.DisableGlobalPWM, + s: c, + numPixels: o.NumPixels, + rawBuf: buf, + pixels: buf[4 : 4+4*o.NumPixels], + rect: image.Rect(0, 0, o.NumPixels, 1), }, nil } @@ -94,20 +123,24 @@ func New(p spi.Port, o *Opts) (*Dev, error) { // // Includes intensity and temperature correction. 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 - // Temperature is the white adjustment in °Kelvin. Has no effect when - // RawColors is true. + // Temperature is the white adjustment in °Kelvin. // - // 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 - // 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. - // When true, Temperature has no effect. - RawColors bool + // Takes effect on the next Draw() or Write() call. + DisableGlobalPWM bool s spi.Conn // l lut // Updated at each .Write() call. @@ -118,7 +151,7 @@ type Dev struct { } 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 @@ -186,31 +219,38 @@ func (d *Dev) Halt() error { // It is expected to be given the part where pixels are, not the header nor // 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. -// -// 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) { +func (d *Dev) raster(dst []byte, src []byte, srcHasAlpha bool) { pBytes := 3 - if hasAlpha { + if srcHasAlpha { pBytes = 4 } length := len(src) / pBytes - if len(dst) < length { - length = len(dst) / pBytes + if l := len(dst) / 4; l < length { + length = l + } + if length == 0 { + // Save ourself some unneeded processing. + return } - d.l.init(d.Intensity, d.Temperature) - // For the d.RawColors == true case, allow for fast brightness scaling - brightness := int(d.Intensity) + 1 + d.l.init(d.Intensity, d.Temperature, !d.DisableGlobalPWM) + if d.DisableGlobalPWM { + // Faster path when the global 5 bits PWM is forced to full intensity. + for i := 0; i < length; i++ { + sOff := pBytes * i + dOff := 4 * i + r, g, b := d.l.r[src[sOff]], d.l.g[src[sOff+1]], d.l.b[src[sOff+2]] + dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte(b), byte(g), byte(r) + } + return + } + for i := 0; i < length; i++ { - // The response as seen by the human eye is very non-linear. The APA-102 - // provides an overall brightness PWM but it is relatively slower and - // results in human visible flicker. On the other hand the minimal color - // (1/255) is still too intense at full brightness, so for very dark color, - // it is worth using the overall brightness PWM. The goal is to use - // brightness!=31 as little as possible. + // The goal is to use brightness!=31 as little as possible. // // Global brightness frequency is 580Hz and color frequency at 19.2kHz. // https://cpldcpu.wordpress.com/2014/08/27/apa102/ @@ -227,26 +267,17 @@ func (d *Dev) raster(dst []byte, src []byte, hasAlpha bool) { // Computes brightness, blue, green, red. sOff := pBytes * 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]] - m := r | g | b - switch { - case m <= 255: - dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xE1, byte(b), byte(g), byte(r) - case m <= 511: - dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xE2, byte(b/2), byte(g/2), byte(r/2) - case m <= 1023: - dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xE4, byte((b+2)/4), byte((g+2)/4), byte((r+2)/4) - default: - dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte((b+15)/31), byte((g+15)/31), byte((r+15)/31) - } + r, g, b := d.l.r[src[sOff]], d.l.g[src[sOff+1]], d.l.b[src[sOff+2]] + m := r | g | b + switch { + case m <= 255: + dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xE1, byte(b), byte(g), byte(r) + case m <= 511: + dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xE2, byte(b/2), byte(g/2), byte(r/2) + case m <= 1023: + dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xE4, byte((b+2)/4), byte((g+2)/4), byte((r+2)/4) + default: + dst[dOff], dst[dOff+1], dst[dOff+2], dst[dOff+3] = 0xFF, byte((b+15)/31), byte((g+15)/31), byte((r+15)/31) } } } @@ -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 // ramp converts input from [0, 0xFF] as intensity to lightness on a scale of @@ -331,40 +365,65 @@ func ramp(l uint8, max uint16) uint16 { // lut is a lookup table that initializes itself on the fly. type lut struct { - intensity uint8 // Set an intensity between 0 (off) and 255 (full brightness). - temperature uint16 // In Kelvin. - r [256]uint16 - g [256]uint16 - b [256]uint16 + // Set an intensity between 0 (off) and 255 (full brightness). + 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 + g [256]uint16 + b [256]uint16 } -func (l *lut) init(i uint8, t uint16) { - if i != l.intensity || t != l.temperature { - l.intensity = i - l.temperature = t - tr, tg, tb := toRGBFast(l.temperature) +func (l *lut) init(i uint8, t uint16, g bool) { + if i == l.intensity && t == l.temperature && g == l.globalPWM { + return + } + l.intensity = i + l.temperature = t + 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 := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tr) + 127*127) / 65025) - maxG := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tg) + 127*127) / 65025) - maxB := uint16((uint32(maxOut)*uint32(l.intensity)*uint32(tb) + 127*127) / 65025) - for i := range l.r { - l.r[i] = ramp(uint8(i), maxR) + maxR := (int(i)*int(tr) + 127) / 255 + maxG := (int(i)*int(tg) + 127) / 255 + maxB := (int(i)*int(tb) + 127) / 255 + for j := range l.r { + // 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) } - if maxG == maxR { - copy(l.g[:], l.r[:]) - } else { - for i := range l.g { - l.g[i] = ramp(uint8(i), maxG) - } + 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 { + copy(l.g[:], l.r[:]) + } else { + for j := range l.g { + l.g[j] = ramp(uint8(j), maxG) } - if maxB == maxR { - copy(l.b[:], l.r[:]) - } else if maxB == maxG { - copy(l.b[:], l.g[:]) - } else { - for i := range l.b { - l.b[i] = ramp(uint8(i), maxB) - } + } + if maxB == maxR { + copy(l.b[:], l.r[:]) + } else if maxB == maxG { + copy(l.b[:], l.g[:]) + } else { + for j := range l.b { + l.b[j] = ramp(uint8(j), maxB) } } } diff --git a/devices/apa102/apa102_test.go b/devices/apa102/apa102_test.go index 1f6f8be..4a10d3a 100644 --- a/devices/apa102/apa102_test.go +++ b/devices/apa102/apa102_test.go @@ -335,9 +335,9 @@ func TestDevEmpty(t *testing.T) { t.Fatalf("%d %v", n, err) } 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) } } @@ -357,7 +357,7 @@ func TestDevLen(t *testing.T) { t.Fatalf("%d %v", n, err) } 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{ Intensity: 255, - Temperature: 6500, + Temperature: NeutralTemp, }, }, { @@ -430,11 +430,11 @@ var writeTests = []struct { }, opts: Opts{ Intensity: 127, - Temperature: 6500, + Temperature: NeutralTemp, }, }, { - name: "Raw", + name: "PassThru", pixels: ToRGB([]color.NRGBA{ {0xFF, 0xFF, 0xFF, 0x00}, {0xFE, 0xFE, 0xFE, 0x00}, @@ -461,9 +461,74 @@ var writeTests = []struct { 0xFF, 0x00, 0x00, 0x00, 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: 255, - RawColors: true, + 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{ + Intensity: 255, + 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) } 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 } 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, } -// expectedi250raw is using RawColors = true. +// expectedi250raw is using DisableGlobalPWM = true. var expectedi250raw = []byte{ - 0x00, 0x00, 0x00, 0x00, 0xFF, 0x07, 0x03, 0x00, 0xFF, 0x13, 0x0F, 0x0B, 0xFF, - 0x1F, 0x1B, 0x17, 0xFF, 0x2B, 0x27, 0x23, 0xFF, 0x36, 0x32, 0x2F, 0xFF, 0x42, - 0x3E, 0x3A, 0xFF, 0x4E, 0x4A, 0x46, 0xFF, 0x5A, 0x56, 0x52, 0xFF, 0x65, 0x62, - 0x5E, 0xFF, 0x71, 0x6D, 0x69, 0xFF, 0x7D, 0x79, 0x75, 0xFF, 0x89, 0x85, 0x81, - 0xFF, 0x95, 0x91, 0x8D, 0xFF, 0xA0, 0x9C, 0x98, 0xFF, 0xAC, 0xA8, 0xA4, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x08, 0x04, 0x00, 0xFF, 0x14, 0x10, 0x0C, 0xFF, + 0x1F, 0x1B, 0x18, 0xFF, 0x2B, 0x27, 0x23, 0xFF, 0x37, 0x33, 0x2F, 0xFF, 0x43, + 0x3F, 0x3B, 0xFF, 0x4E, 0x4B, 0x47, 0xFF, 0x5A, 0x56, 0x52, 0xFF, 0x66, 0x62, + 0x5E, 0xFF, 0x72, 0x6E, 0x6A, 0xFF, 0x7D, 0x7A, 0x76, 0xFF, 0x89, 0x85, 0x81, + 0xFF, 0x95, 0x91, 0x8D, 0xFF, 0xA1, 0x9D, 0x99, 0xFF, 0xAD, 0xA9, 0xA5, 0xFF, 0xB8, 0xB4, 0xB0, 0xFF, 0xFF, } @@ -611,11 +676,11 @@ var drawTests = []struct { opts: Opts{ NumPixels: 16, Intensity: 250, - Temperature: 6500, + Temperature: NeutralTemp, }, }, { - name: "Draw NRGBA Raw", + name: "Draw NRGBA no global PWM", img: func() image.Image { im := image.NewNRGBA(image.Rect(0, 0, 16, 1)) for i := 0; i < 16; i++ { @@ -629,9 +694,10 @@ var drawTests = []struct { }(), want: expectedi250raw, opts: Opts{ - NumPixels: 16, - Intensity: 250, - RawColors: true, + NumPixels: 16, + Temperature: NeutralTemp, + Intensity: 250, + DisableGlobalPWM: true, }, }, { @@ -676,7 +742,7 @@ var drawTests = []struct { }, }, { - name: "Draw RGBA Raw", + name: "Draw RGBA no global PWM", img: func() image.Image { im := image.NewRGBA(image.Rect(0, 0, 16, 1)) for i := 0; i < 16; i++ { @@ -689,9 +755,10 @@ var drawTests = []struct { }(), want: expectedi250raw, opts: Opts{ - NumPixels: 16, - Intensity: 250, - RawColors: true, + NumPixels: 16, + Temperature: NeutralTemp, + Intensity: 250, + DisableGlobalPWM: true, }, }, } @@ -704,7 +771,7 @@ func TestDraws(t *testing.T) { t.Fatalf("%s: %v", tt.name, err) } 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) } 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) { // Catch the "maxB == maxG" line. l := lut{} - l.init(255, 6000) + l.init(255, 6000, true) if equalUint16(l.r[:], l.g[:]) || !equalUint16(l.g[:], l.b[:]) { 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) } -func BenchmarkWriteColorfulRaw(b *testing.B) { - o := DefaultOpts +func BenchmarkWriteColorfulPassThru(b *testing.B) { + o := PassThruOpts o.Intensity = 250 - o.RawColors = true 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) } -func BenchmarkDrawNRGBAColorfulRaw(b *testing.B) { - o := DefaultOpts +func BenchmarkDrawNRGBAColorfulPassThru(b *testing.B) { + o := PassThruOpts o.Intensity = 250 - o.RawColors = true 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) } -func BenchmarkDrawRGBAColorfulRaw(b *testing.B) { - o := DefaultOpts +func BenchmarkDrawRGBAColorfulPassThru(b *testing.B) { + o := PassThruOpts o.Intensity = 250 - o.RawColors = true benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel) } diff --git a/devices/apa102/example_test.go b/devices/apa102/example_test.go index c05f1ee..3ca2f6f 100644 --- a/devices/apa102/example_test.go +++ b/devices/apa102/example_test.go @@ -35,13 +35,37 @@ func Example() { o.Temperature = 3500 dev, err := apa102.New(p, &o) 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)) for x := 0; x < img.Rect.Max.X; x++ { img.SetNRGBA(x, 0, color.NRGBA{uint8(x), uint8(255 - x), 0, 255}) } 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) } + + // 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) + } }