diff --git a/devices/apa102/apa102.go b/devices/apa102/apa102.go index b4cf38a..fa1a913 100644 --- a/devices/apa102/apa102.go +++ b/devices/apa102/apa102.go @@ -10,10 +10,9 @@ import ( "image" "image/color" - "periph.io/x/periph/conn" + "periph.io/x/periph/conn/display" "periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/spi" - "periph.io/x/periph/devices" ) // ToRGB converts a slice of color.NRGBA to a byte stream of RGB pixels. @@ -109,23 +108,25 @@ func (d *Dev) String() string { return fmt.Sprintf("APA102{I:%d, T:%dK, %dLEDs, %s}", d.Intensity, d.Temperature, d.numPixels, d.s) } -// ColorModel implements devices.Display. There's no surprise, it is +// ColorModel implements display.Drawer. There's no surprise, it is // color.NRGBAModel. func (d *Dev) ColorModel() color.Model { return color.NRGBAModel } -// Bounds implements devices.Display. Min is guaranteed to be {0, 0}. +// Bounds implements display.Drawer. Min is guaranteed to be {0, 0}. func (d *Dev) Bounds() image.Rectangle { return d.rect } -// Draw implements devices.Display. +// Draw implements display.Drawer. // // Using something else than image.NRGBA is 10x slower. When using image.NRGBA, // the alpha channel is ignored. -func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { - r = r.Intersect(d.rect) +func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) error { + if r = r.Intersect(d.rect); r.Empty() { + return nil + } srcR := src.Bounds() srcR.Min = srcR.Min.Add(sp) if dX := r.Dx(); dX < srcR.Dx() { @@ -134,9 +135,12 @@ func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { if dY := r.Dy(); dY < srcR.Dy() { srcR.Max.Y = srcR.Min.Y + dY } + if srcR.Empty() { + return nil + } d.l.init(d.Intensity, d.Temperature) d.l.rasterImg(d.pixels, r, src, srcR) - _ = d.s.Tx(d.rawBuf, nil) + return d.s.Tx(d.rawBuf, nil) } // Write accepts a stream of raw RGB pixels and sends it as APA102 encoded @@ -301,10 +305,10 @@ func (l *lut) raster(dst []byte, src []byte) { } // rasterImg is the generic version of raster. -func (l *lut) rasterImg(dst []byte, r image.Rectangle, src image.Image, srcR image.Rectangle) { +func (l *lut) rasterImg(dst []byte, rect image.Rectangle, src image.Image, srcR image.Rectangle) { // Render directly into the buffer for maximum performance and to keep // untouched sections intact. - deltaX4 := 4 * (r.Min.X - srcR.Min.X) + deltaX4 := 4 * (rect.Min.X - srcR.Min.X) if img, ok := src.(*image.NRGBA); ok { // Fast path for image.NRGBA. pix := img.Pix[srcR.Min.Y*img.Stride:] @@ -354,6 +358,4 @@ func (l *lut) rasterImg(dst []byte, r image.Rectangle, src image.Image, srcR ima } } -var _ conn.Resource = &Dev{} -var _ devices.Display = &Dev{} -var _ fmt.Stringer = &Dev{} +var _ display.Drawer = &Dev{} diff --git a/devices/apa102/apa102_test.go b/devices/apa102/apa102_test.go index 9b79c49..3651b2e 100644 --- a/devices/apa102/apa102_test.go +++ b/devices/apa102/apa102_test.go @@ -536,7 +536,9 @@ func TestDrawNRGBA(t *testing.T) { o.Intensity = 250 o.Temperature = 5000 d, _ := New(spitest.NewRecordRaw(&buf), &o) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if !bytes.Equal(expectedi250t5000, buf.Bytes()) { t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes()) } @@ -554,7 +556,9 @@ func TestDrawNRGBA_wide(t *testing.T) { o.Intensity = 250 o.Temperature = 6500 d, _ := New(spitest.NewRecordRaw(&buf), &o) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if !bytes.Equal(expectedi250t6500, buf.Bytes()) { t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes()) } @@ -575,7 +579,9 @@ func TestDrawRGBA(t *testing.T) { o.Intensity = 250 o.Temperature = 5000 d, _ := New(spitest.NewRecordRaw(&buf), &o) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if !bytes.Equal(expectedi250t5000, buf.Bytes()) { t.Fatalf("%#v != %#v", expectedi250t5000, buf.Bytes()) } @@ -692,10 +698,14 @@ func BenchmarkDrawNRGBAColorful(b *testing.B) { d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &o) r := d.Bounds() p := image.Point{} - d.Draw(r, img, p) + if err := d.Draw(r, img, p); err != nil { + b.Fatal(err) + } b.ResetTimer() for i := 0; i < b.N; i++ { - d.Draw(r, img, p) + if err := d.Draw(r, img, p); err != nil { + b.Fatal(err) + } } } @@ -713,10 +723,14 @@ func BenchmarkDrawRGBAColorful(b *testing.B) { d, _ := New(spitest.NewRecordRaw(ioutil.Discard), &o) r := d.Bounds() p := image.Point{} - d.Draw(r, img, p) + if err := d.Draw(r, img, p); err != nil { + b.Fatal(err) + } b.ResetTimer() for i := 0; i < b.N; i++ { - d.Draw(r, img, p) + if err := d.Draw(r, img, p); err != nil { + b.Fatal(err) + } } } diff --git a/devices/apa102/example_test.go b/devices/apa102/example_test.go index edbb5c4..c05f1ee 100644 --- a/devices/apa102/example_test.go +++ b/devices/apa102/example_test.go @@ -41,5 +41,7 @@ func Example() { for x := 0; x < img.Rect.Max.X; x++ { img.SetNRGBA(x, 0, color.NRGBA{uint8(x), uint8(255 - x), 0, 255}) } - dev.Draw(dev.Bounds(), img, image.Point{}) + if err := dev.Draw(dev.Bounds(), img, image.Point{}); err != nil { + log.Fatal(err) + } } diff --git a/devices/devices.go b/devices/devices.go deleted file mode 100644 index a44c76f..0000000 --- a/devices/devices.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2016 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 devices - -import ( - "image" - "image/color" - "io" - - "periph.io/x/periph/conn" -) - -// Display represents a pixel output device. It is a write-only interface. -// -// What Display represents can be as varied as a 1 bit OLED display or a strip -// of LED lights. -type Display interface { - conn.Resource - - // Writer can be used when the native display pixel format is known. Each - // write must cover exactly the whole screen as a single packed stream of - // pixels. - io.Writer - // ColorModel returns the device native color model. - // - // It is generally color.NRGBA for a color display. - ColorModel() color.Model - // Bounds returns the size of the output device. - // - // Generally displays should have Min at {0, 0} but this is not guaranteed in - // multiple displays setup or when an instance of this interface represents a - // section of a larger logical display. - Bounds() image.Rectangle - // Draw updates the display with this image starting at 'sp' offset into the - // display into 'r'. The code will likely be faster if the image is in the - // display's native color format. - // - // To be compatible with draw.Drawer, this function doesn't return an error. - Draw(r image.Rectangle, src image.Image, sp image.Point) -} diff --git a/devices/devicestest/display.go b/devices/devicestest/display.go deleted file mode 100644 index d55c779..0000000 --- a/devices/devicestest/display.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2016 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 devicestest - -import ( - "errors" - "fmt" - "image" - "image/color" - "image/draw" - - "periph.io/x/periph/conn" - "periph.io/x/periph/devices" -) - -// Display is a fake devices.Display. -type Display struct { - Img *image.NRGBA -} - -func (d *Display) String() string { - return "Display" -} - -// Halt implements conn.Resource. It is a noop. -func (d *Display) Halt() error { - return nil -} - -// Write implements devices.Display. -func (d *Display) Write(pixels []byte) (int, error) { - if len(pixels)%3 != 0 { - return 0, errors.New("devicetest: invalid RGB stream length") - } - copy(d.Img.Pix, pixels) - return len(pixels), nil -} - -// ColorModel implements image.Image. -func (d *Display) ColorModel() color.Model { - return d.Img.ColorModel() -} - -// Bounds implements image.Image. -func (d *Display) Bounds() image.Rectangle { - return d.Img.Bounds() -} - -// Draw implements draw.Image. -func (d *Display) Draw(r image.Rectangle, src image.Image, sp image.Point) { - draw.Draw(d.Img, r, src, sp, draw.Src) -} - -var _ conn.Resource = &Display{} -var _ devices.Display = &Display{} -var _ fmt.Stringer = &Display{} diff --git a/devices/devicestest/doc.go b/devices/devicestest/doc.go deleted file mode 100644 index 49cbbfb..0000000 --- a/devices/devicestest/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2016 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 devicestest contains non-hardware devices implementations for -// testing or emulation purpose. -package devicestest diff --git a/devices/doc.go b/devices/doc.go index 40b9ed0..035c6c1 100644 --- a/devices/doc.go +++ b/devices/doc.go @@ -2,10 +2,8 @@ // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. -// Package devices contains interfaces for classes of devices. +// Package devices is a container for device drivers. // // Subpackages contain the concrete implementations. Devices accept port // interface, constructors return concrete type. -// -// Subpackage devicestest contains fake implementations for testing. package devices diff --git a/devices/ssd1306/example_test.go b/devices/ssd1306/example_test.go index 3f86bd7..e244ceb 100644 --- a/devices/ssd1306/example_test.go +++ b/devices/ssd1306/example_test.go @@ -47,8 +47,7 @@ func Example() { // Dot: fixed.P(0, img.Bounds().Dy()-1-f.Descent), // } // drawer.DrawString("Hello from periph!") - dev.Draw(dev.Bounds(), img, image.Point{}) - if err := dev.Err(); err != nil { + if err := dev.Draw(dev.Bounds(), img, image.Point{}); err != nil { log.Fatal(err) } } diff --git a/devices/ssd1306/ssd1306.go b/devices/ssd1306/ssd1306.go index 5787e72..53a6152 100644 --- a/devices/ssd1306/ssd1306.go +++ b/devices/ssd1306/ssd1306.go @@ -43,11 +43,11 @@ import ( "image/draw" "periph.io/x/periph/conn" + "periph.io/x/periph/conn/display" "periph.io/x/periph/conn/gpio" "periph.io/x/periph/conn/i2c" "periph.io/x/periph/conn/physic" "periph.io/x/periph/conn/spi" - "periph.io/x/periph/devices" "periph.io/x/periph/devices/ssd1306/image1bit" ) @@ -158,7 +158,6 @@ type Dev struct { startCol, endCol int scrolled bool halted bool - err error } func (d *Dev) String() string { @@ -168,28 +167,26 @@ func (d *Dev) String() string { return fmt.Sprintf("ssd1360.Dev{%s, %s}", d.c, d.rect.Max) } -// ColorModel implements devices.Display. +// ColorModel implements display.Drawer. // // It is a one bit color model, as implemented by image1bit.Bit. func (d *Dev) ColorModel() color.Model { return image1bit.BitModel } -// Bounds implements devices.Display. Min is guaranteed to be {0, 0}. +// Bounds implements display.Drawer. Min is guaranteed to be {0, 0}. func (d *Dev) Bounds() image.Rectangle { return d.rect } -// Draw implements devices.Display. +// Draw implements display.Drawer. // // It draws synchronously, once this function returns, the display is updated. -// It means that on slow bus (I²C), it may be preferable to defer Draw() calls +// It means that on slow bus (I²C), it may be preferable to defer Draw() calls // to a background goroutine. -// -// It discards any failure. -func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { +func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) error { var next []byte - if img, ok := src.(*image1bit.VerticalLSB); ok && r == d.rect && src.Bounds() == d.rect && sp.X == 0 && sp.Y == 0 { + if img, ok := src.(*image1bit.VerticalLSB); ok && r == d.rect && img.Rect == d.rect && sp.X == 0 && sp.Y == 0 { // Exact size, full frame, image1bit encoding: fast path! next = img.Pix } else { @@ -200,12 +197,7 @@ func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { next = d.next.Pix draw.Src.Draw(d.next, r, src, sp) } - d.err = d.drawInternal(next) -} - -// Err returns the last error that occurred -func (d *Dev) Err() error { - return d.err + return d.drawInternal(next) } // Write writes a buffer of pixels to the display. @@ -500,6 +492,4 @@ const ( i2cData = 0x40 // I²C transaction has stream of data bytes ) -var _ conn.Resource = &Dev{} -var _ devices.Display = &Dev{} -var _ fmt.Stringer = &Dev{} +var _ display.Drawer = &Dev{} diff --git a/devices/ssd1306/ssd1306_test.go b/devices/ssd1306/ssd1306_test.go index 52f061c..69db1f1 100644 --- a/devices/ssd1306/ssd1306_test.go +++ b/devices/ssd1306/ssd1306_test.go @@ -84,8 +84,7 @@ func TestI2C_Draw_VerticalLSD_fast(t *testing.T) { } img := image1bit.NewVerticalLSB(dev.Bounds()) img.Pix[22] = 1 - dev.Draw(dev.Bounds(), img, image.Point{}) - if err := dev.Err(); err != nil { + if err := dev.Draw(dev.Bounds(), img, image.Point{}); err != nil { t.Fatal(err) } if err := bus.Close(); err != nil { @@ -203,8 +202,7 @@ func TestI2C_Draw_fail(t *testing.T) { if err != nil { t.Fatal(err) } - dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{}) - if err := dev.Err(); !conntest.IsErr(err) { + if err := dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{}); !conntest.IsErr(err) { t.Fatalf("expected conntest error: %v", err) } if err := bus.Close(); err != nil { @@ -225,13 +223,11 @@ func TestI2C_DrawGray(t *testing.T) { if err != nil { t.Fatal(err) } - dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{0, 0}) - if err := dev.Err(); err != nil { + if err := dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{}); err != nil { t.Fatal(err) } // No-op (skip path). - dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{0, 0}) - if err := dev.Err(); err != nil { + if err := dev.Draw(dev.Bounds(), makeGrayCheckboard(dev.Bounds()), image.Point{}); err != nil { t.Fatal(err) } if err := bus.Close(); err != nil { diff --git a/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go b/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go index 96152d6..a6d7411 100644 --- a/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go +++ b/devices/ssd1306/ssd1306smoketest/ssd1306smoketest.go @@ -196,8 +196,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, for i, d := range s.devices { start := time.Now() - d.Draw(d.Bounds(), imgBunnyNRGBA, image.Point{}) - if err := d.Err(); err != nil { + if err := d.Draw(d.Bounds(), imgBunnyNRGBA, image.Point{}); err != nil { return err } s.timings[i] = time.Since(start) @@ -206,8 +205,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, for i, d := range s.devices { start := time.Now() - d.Draw(d.Bounds(), imgBunny1bitLarge, image.Point{}) - if err := d.Err(); err != nil { + if err := d.Draw(d.Bounds(), imgBunny1bitLarge, image.Point{}); err != nil { return err } s.timings[i] = time.Since(start) @@ -216,8 +214,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, for i, d := range s.devices { start := time.Now() - d.Draw(d.Bounds(), imgBunny1bit, image.Point{}) - if err := d.Err(); err != nil { + if err := d.Draw(d.Bounds(), imgBunny1bit, image.Point{}); err != nil { return err } s.timings[i] = time.Since(start) @@ -289,8 +286,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, for i, d := range s.devices { start := time.Now() - d.Draw(d.Bounds(), imgBunny1bitLarge, image.Point{}) - if err := d.Err(); err != nil { + if err := d.Draw(d.Bounds(), imgBunny1bitLarge, image.Point{}); err != nil { return err } s.timings[i] = time.Since(start) @@ -406,8 +402,7 @@ func (s *SmokeTest) run(i2cBus i2c.Bus, spiPort spi.PortCloser, dc gpio.PinOut, draw.DrawMask(bmp, r, &image.Uniform{C: image1bit.On}, image.Point{}, &periphImg, image.Point{}, draw.Over) for i, d := range s.devices { start := time.Now() - d.Draw(d.Bounds(), bmp, image.Point{}) - if err := d.Err(); err != nil { + if err := d.Draw(d.Bounds(), bmp, image.Point{}); err != nil { return err } s.timings[i] = time.Since(start) diff --git a/experimental/devices/nrzled/nrzled.go b/experimental/devices/nrzled/nrzled.go index 2b8029a..c3bfdc6 100644 --- a/experimental/devices/nrzled/nrzled.go +++ b/experimental/devices/nrzled/nrzled.go @@ -10,10 +10,9 @@ import ( "image" "image/color" - "periph.io/x/periph/conn" + "periph.io/x/periph/conn/display" "periph.io/x/periph/conn/gpio/gpiostream" "periph.io/x/periph/conn/physic" - "periph.io/x/periph/devices" ) // NRZ converts a byte into the MSB-first Non-Return-to-Zero encoded 24 bits. @@ -119,19 +118,19 @@ func (d *Dev) Halt() error { return nil } -// ColorModel implements devices.Display. +// ColorModel implements display.Drawer. // // It is color.NRGBAModel. func (d *Dev) ColorModel() color.Model { return color.NRGBAModel } -// Bounds implements devices.Display. Min is guaranteed to be {0, 0}. +// Bounds implements display.Drawer. Min is guaranteed to be {0, 0}. func (d *Dev) Bounds() image.Rectangle { return d.rect } -// Draw implements devices.Display. +// Draw implements display.Drawer. // // Using something else than image.NRGBA is 10x slower and is not recommended. // When using image.NRGBA, the alpha channel is ignored in RGB mode and used as @@ -139,8 +138,10 @@ func (d *Dev) Bounds() image.Rectangle { // // A back buffer is kept so that partial updates are supported, albeit the full // LED strip is updated synchronously. -func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { - r = r.Intersect(d.rect) +func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) error { + if r = r.Intersect(d.rect); r.Empty() { + return nil + } srcR := src.Bounds() srcR.Min = srcR.Min.Add(sp) if dX := r.Dx(); dX < srcR.Dx() { @@ -180,7 +181,7 @@ func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) { } } } - _ = d.p.StreamOut(&d.b) + return d.p.StreamOut(&d.b) } // Write accepts a stream of raw RGB/RGBW pixels and sends it as NRZ encoded @@ -237,6 +238,4 @@ func put(out []byte, v byte) { out[2] = byte(w) } -var _ conn.Resource = &Dev{} -var _ devices.Display = &Dev{} -var _ fmt.Stringer = &Dev{} +var _ display.Drawer = &Dev{} diff --git a/experimental/devices/nrzled/nrzled_test.go b/experimental/devices/nrzled/nrzled_test.go index df7dc14..cda9218 100644 --- a/experimental/devices/nrzled/nrzled_test.go +++ b/experimental/devices/nrzled/nrzled_test.go @@ -92,7 +92,9 @@ func TestDraw_NRGBA_3(t *testing.T) { d, _ := New(&g, &opts) img := image.NewNRGBA(d.Bounds()) copy(img.Pix, getRGBW()) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if err := g.Close(); err != nil { t.Fatal(err) } @@ -120,7 +122,9 @@ func TestDraw_RGBA_3(t *testing.T) { d, _ := New(&g, &opts) img := image.NewRGBA(d.Bounds()) copy(img.Pix, getRGBW()) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if err := g.Close(); err != nil { t.Fatal(err) } @@ -151,7 +155,9 @@ func TestDraw_RGBA_4(t *testing.T) { d, _ := New(&g, &opts) img := image.NewRGBA(d.Bounds()) copy(img.Pix, getRGBW()) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if err := g.Close(); err != nil { t.Fatal(err) } @@ -179,7 +185,9 @@ func TestDraw_Limits(t *testing.T) { d, _ := New(&g, &opts) img := image.NewRGBA(image.Rect(-1, -1, 20, 20)) copy(img.Pix, getRGBW()) - d.Draw(d.Bounds(), img, image.Point{}) + if err := d.Draw(d.Bounds(), img, image.Point{}); err != nil { + t.Fatal(err) + } if err := g.Close(); err != nil { t.Fatal(err) }