diff --git a/devices/apa102/apa102.go b/devices/apa102/apa102.go index 23dfcc0..1be9e53 100644 --- a/devices/apa102/apa102.go +++ b/devices/apa102/apa102.go @@ -96,12 +96,11 @@ func (l *lut) init(i uint8, t uint16) { // // dst is in APA102 SPI 32 bits word format. src is in RGB 24 bits word format. // maxR, maxG and maxB are the maximum light intensity to use per channel. +// +// src cannot be longer in pixel count than dst. func (l *lut) raster(dst []byte, src []byte) { // Whichever is the shortest. length := len(src) / 3 - if o := len(dst) / 4; o < length { - length = o - } for i := 0; i < length; i++ { // Converts a color into the 4 bytes needed to control an APA-102 LED. // @@ -221,7 +220,8 @@ type Dev struct { s spi.Conn l lut // Updated at each .Write() call. numLights int - buf []byte + rawBuf []byte + pixels []byte } // ColorModel implements devices.Display. There's no surprise, it is @@ -250,8 +250,8 @@ func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { srcR.Max.Y = srcR.Min.Y + dY } d.l.init(d.Intensity, d.Temperature) - d.l.rasterImg(d.buf[4:4+4*d.numLights], r, src, srcR) - _ = d.s.Tx(d.buf, nil) + d.l.rasterImg(d.pixels, r, src, srcR) + _ = d.s.Tx(d.rawBuf, nil) } // Write accepts a stream of raw RGB pixels and sends it as APA102 encoded @@ -261,8 +261,13 @@ func (d *Dev) Write(pixels []byte) (int, error) { return 0, errLength } d.l.init(d.Intensity, d.Temperature) - d.l.raster(d.buf[4:4+4*d.numLights], pixels) - err := d.s.Tx(d.buf, nil) + // Trying to write more pixels than defined? + if o := len(d.pixels) / 4; o < len(pixels)/3 { + pixels = pixels[:o*3] + } + // Do not touch header and footer. + d.l.raster(d.pixels, pixels) + err := d.s.Tx(d.rawBuf, nil) return len(pixels), err } @@ -294,7 +299,8 @@ func New(s spi.Conn, numLights int, intensity uint8, temperature uint16) (*Dev, Temperature: temperature, s: s, numLights: numLights, - buf: buf, + rawBuf: buf, + pixels: buf[4 : 4+4*numLights], }, nil } diff --git a/devices/apa102/apa102_test.go b/devices/apa102/apa102_test.go index 8a9b82a..a902e97 100644 --- a/devices/apa102/apa102_test.go +++ b/devices/apa102/apa102_test.go @@ -6,6 +6,7 @@ package apa102 import ( "bytes" + "errors" "fmt" "image" "image/color" @@ -334,6 +335,12 @@ func TestDevEmpty(t *testing.T) { } } +func TestConfigureFail(t *testing.T) { + if d, err := New(&configFail{}, 150, 255, 6500); d != nil || err == nil { + t.Fatal("Configure() call have failed") + } +} + func TestDevLen(t *testing.T) { buf := bytes.Buffer{} d, _ := New(spitest.NewRecordRaw(&buf), 1, 255, 6500) @@ -445,8 +452,17 @@ func TestDevLong(t *testing.T) { } } -// Expected output for 3 test cases. Each test case use a completely different -// code path so make sure each code path results in the exact same output. +func TestDevWriteShort(t *testing.T) { + buf := bytes.Buffer{} + d, _ := New(spitest.NewRecordRaw(&buf), 1, 250, 6500) + if n, err := d.Write([]byte{0, 0, 0, 1, 1, 1}); n != 3 || err != nil { + t.Fatal(n, err) + } +} + +// expectedi250t5000 is the expected output for 3 test cases. Each test case +// use a completely different code path so make sure each code path results in +// the exact same output. var expectedi250t5000 = []byte{ 0x00, 0x00, 0x00, 0x00, 0xE1, 0x08, 0x04, 0x00, 0xE1, 0x14, 0x10, 0xC, 0xE1, 0x20, 0x1C, 0x18, 0xE1, 0x2C, 0x28, 0x24, 0xE1, 0x38, 0x34, 0x30, 0xE1, 0x41, @@ -456,6 +472,16 @@ var expectedi250t5000 = []byte{ 0x3A, 0x36, 0x32, 0xFF, 0xFF, } +// expectedi250t6500 is the default color temperature. +var expectedi250t6500 = []byte{ + 0x00, 0x00, 0x00, 0x00, 0xE1, 0x08, 0x04, 0x00, 0xE1, 0x14, 0x10, 0x0C, 0xE1, + 0x20, 0x1C, 0x18, 0xE1, 0x2C, 0x28, 0x24, 0xE1, 0x38, 0x34, 0x30, 0xE1, 0x44, + 0x40, 0x3C, 0xE1, 0x4E, 0x4C, 0x48, 0xE1, 0x52, 0x4F, 0x4E, 0xE1, 0x66, 0x5C, + 0x56, 0xE1, 0x9A, 0x84, 0x73, 0xE1, 0xFB, 0xD4, 0xB4, 0xE2, 0xCB, 0xAE, 0x94, + 0xE4, 0xA0, 0x8A, 0x77, 0xE4, 0xF0, 0xD2, 0xB8, 0xFF, 0x2D, 0x28, 0x23, 0xFF, + 0x3E, 0x38, 0x32, 0xFF, 0xFF, +} + func TestDevTemperatureWarm(t *testing.T) { buf := bytes.Buffer{} pixels := make([]byte, 16*3) @@ -489,6 +515,20 @@ func TestDrawNRGBA(t *testing.T) { } } +func TestDrawNRGBA_wide(t *testing.T) { + img := image.NewNRGBA(image.Rect(0, 0, 17, 2)) + for x := 0; x < 16; x++ { + // Test all intensity code paths. Confirm that alpha is ignored. + img.SetNRGBA(x, 0, color.NRGBA{uint8((3 * x) << 2), uint8((3*x + 1) << 2), uint8((3*x + 2) << 2), 0}) + } + buf := bytes.Buffer{} + d, _ := New(spitest.NewRecordRaw(&buf), 16, 250, 6500) + d.Draw(d.Bounds(), img, image.Point{}) + if !bytes.Equal(expectedi250t6500, buf.Bytes()) { + t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes()) + } +} + func TestDrawRGBA(t *testing.T) { img := image.NewRGBA(image.Rect(0, 0, 16, 1)) for i := 0; i < 16; i++ { @@ -506,6 +546,15 @@ func TestDrawRGBA(t *testing.T) { } } +func TestInit(t *testing.T) { + // Catch the "maxB == maxG" line. + l := lut{} + l.init(255, 6000) + if equalUint16(l.r[:], l.g[:]) || !equalUint16(l.g[:], l.b[:]) { + t.Fatal("test case is for only when maxG == maxB but maxR != maxG") + } +} + // func Example() { @@ -625,3 +674,25 @@ func BenchmarkWriteColorfulVariation(b *testing.B) { _, _ = d.Write(pixels) } } + +// + +type configFail struct { + spitest.Record +} + +func (c *configFail) Configure(mode spi.Mode, bits int) error { + return errors.New("injected error") +} + +func equalUint16(a, b []uint16) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/devices/apa102/temperature_test.go b/devices/apa102/temperature_test.go new file mode 100644 index 0000000..aafb781 --- /dev/null +++ b/devices/apa102/temperature_test.go @@ -0,0 +1,17 @@ +// Copyright 2017 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package apa102 + +import "testing" + +func TestToRGBFast_limits(t *testing.T) { + if r, g, b := toRGBFast(999); r != 255 || g != 83 || b != 0 { + t.Fatal(r, g, b) + } + + if r, g, b := toRGBFast(30000); r != 159 || g != 191 || b != 255 { + t.Fatal(r, g, b) + } +}