From 501558c627199e515cae2a3ece6b0acb49e4eacc Mon Sep 17 00:00:00 2001 From: Marc-Antoine Ruel Date: Mon, 25 Jun 2018 14:15:37 -0400 Subject: [PATCH] conn/display: simplify Drawer display: move devices.Display as display.Drawer This removes the last interface from devices, which was misplaced due to historical accident. The new package display will also be the location for an interface for text output only devices. Remove io.Writer from this interface. While it's a good performance optimization for some drivers, it shouldn't be required. Change Draw(): - Return an error, so that communication erorr can be surfaced correctly, instead of an adhoc driver specific Err() method. --- devices/apa102/apa102.go | 28 ++++----- devices/apa102/apa102_test.go | 28 ++++++--- devices/apa102/example_test.go | 4 +- devices/devices.go | 42 -------------- devices/devicestest/display.go | 58 ------------------- devices/devicestest/doc.go | 7 --- devices/doc.go | 4 +- devices/ssd1306/example_test.go | 3 +- devices/ssd1306/ssd1306.go | 28 +++------ devices/ssd1306/ssd1306_test.go | 12 ++-- .../ssd1306smoketest/ssd1306smoketest.go | 15 ++--- experimental/devices/nrzled/nrzled.go | 21 ++++--- experimental/devices/nrzled/nrzled_test.go | 16 +++-- 13 files changed, 81 insertions(+), 185 deletions(-) delete mode 100644 devices/devices.go delete mode 100644 devices/devicestest/display.go delete mode 100644 devices/devicestest/doc.go 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) }