From 0b05c62a9e322f9f056782501681672baf513173 Mon Sep 17 00:00:00 2001 From: M-A Date: Fri, 29 Sep 2017 10:18:22 -0400 Subject: [PATCH] nrzled: add support for ws281x and clones (#165) - Includes driver and CLI. Completely untested. --- experimental/devices/nrzled/doc.go | 22 ++ experimental/devices/nrzled/nrzled.go | 223 ++++++++++++++ experimental/devices/nrzled/nrzled_test.go | 322 +++++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 experimental/devices/nrzled/doc.go create mode 100644 experimental/devices/nrzled/nrzled.go create mode 100644 experimental/devices/nrzled/nrzled_test.go diff --git a/experimental/devices/nrzled/doc.go b/experimental/devices/nrzled/doc.go new file mode 100644 index 0000000..e536f56 --- /dev/null +++ b/experimental/devices/nrzled/doc.go @@ -0,0 +1,22 @@ +// 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 nrzled is a driver for LEDs ws2811/ws2812/ws2812b and compatible +// devices like sk6812 and ucs1903 that uses a single wire NRZ encoded +// communication protocol. +// +// Note that some ICs are 7 bits with the least significant bit ignored, others +// are using a real 8 bits PWM. The PWM frequency varies across ICs. +// +// Datasheet +// +// This directory contains datasheets for ws2812, ws2812b, ucs190x and various +// sk6812. +// +// https://github.com/cpldcpu/light_ws2812/tree/master/Datasheets +// +// UCS1903 datasheet +// +// http://www.bestlightingbuy.com/pdf/UCS1903%20datasheet.pdf +package nrzled diff --git a/experimental/devices/nrzled/nrzled.go b/experimental/devices/nrzled/nrzled.go new file mode 100644 index 0000000..f87c204 --- /dev/null +++ b/experimental/devices/nrzled/nrzled.go @@ -0,0 +1,223 @@ +// 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 nrzled + +import ( + "errors" + "fmt" + "image" + "image/color" + "time" + + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio/gpiostream" + "periph.io/x/periph/devices" +) + +// NRZ converts a byte into the Non-Return-to-Zero encoded 24 bits. +// +// The upper 8 bits are zeros. +// +// The Non-return-to-zero protocol is a self-clocking signal that enables +// one-way communication without the need of a dedicated clock signal, unlike +// SPI driven LEDs like the apa102. +// +// See https://en.wikipedia.org/wiki/Non-return-to-zero for more technical +// details. +func NRZ(b byte) uint32 { + // The stream is 1x01x01x01x01x01x01x01x0 with the x bits being the bits from + // `b` in reverse order. + out := uint32(0x924924) + out |= uint32(b&0x80) << (3*7 + 1 - 7) + out |= uint32(b&0x40) << (3*6 + 1 - 6) + out |= uint32(b&0x20) << (3*5 + 1 - 5) + out |= uint32(b&0x10) << (3*4 + 1 - 4) + out |= uint32(b&0x08) << (3*3 + 1 - 3) + out |= uint32(b&0x04) << (3*2 + 1 - 2) + out |= uint32(b&0x02) << (3*1 + 1 - 1) + out |= uint32(b&0x01) << (3*0 + 1 - 0) + return out +} + +// Dev is a handle to the LED strip. +type Dev struct { + p gpiostream.PinOut + numPixels int + channels int // Number of channels per pixel + b gpiostream.BitStream // NRZ encoded bits; cached to reduce heap fragmentation + buf []byte // Double buffer of RGB/RGBW pixels; enables partial Draw() +} + +func (d *Dev) String() string { + return fmt.Sprintf("nrzled{%s}", d.p) +} + +// Halt turns the lights off. +// +// It doesn't affect the back buffer. +func (d *Dev) Halt() error { + zero := NRZ(0) + a := byte(zero >> 16) + b := byte(zero >> 8) + c := byte(zero) + for i := 0; i < d.channels*d.numPixels; i++ { + d.b.Bits[3*i+0] = a + d.b.Bits[3*i+1] = b + d.b.Bits[3*i+2] = c + } + if err := d.p.StreamOut(&d.b); err != nil { + return fmt.Errorf("nrzled: %v", err) + } + return nil +} + +// ColorModel implements devices.Display. +// +// It is color.NRGBAModel. +func (d *Dev) ColorModel() color.Model { + return color.NRGBAModel +} + +// Bounds implements devices.Display. Min is guaranteed to be {0, 0}. +func (d *Dev) Bounds() image.Rectangle { + return image.Rectangle{Max: image.Point{X: d.numPixels, Y: 1}} +} + +// Draw implements devices.Display. +// +// 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 +// White channel in RGBW mode. +// +// 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.Bounds()) + srcR := src.Bounds() + srcR.Min = srcR.Min.Add(sp) + if dX := r.Dx(); dX < srcR.Dx() { + srcR.Max.X = srcR.Min.X + dX + } + if dY := r.Dy(); dY < srcR.Dy() { + srcR.Max.Y = srcR.Min.Y + dY + } + if d.buf == nil { + // Allocate d.buf on first Draw() call, in case the user only wants to use + // .Write(). + d.buf = make([]byte, d.numPixels*d.channels) + } + if img, ok := src.(*image.NRGBA); ok { + // Fast path for image.NRGBA. + base := srcR.Min.Y * img.Stride + raster(d.b.Bits, img.Pix[base+4*srcR.Min.X:base+4*srcR.Max.X], d.channels, 4) + } else { + // Generic version. + m := srcR.Max.X - srcR.Min.X + if d.channels == 3 { + for i := 0; i < m; i++ { + c := color.NRGBAModel.Convert(src.At(srcR.Min.X+i, srcR.Min.Y)).(color.NRGBA) + j := 3 * i + put(d.b.Bits[3*(j+0):], c.G) + put(d.b.Bits[3*(j+1):], c.R) + put(d.b.Bits[3*(j+2):], c.B) + } + } else { + for i := 0; i < m; i++ { + c := color.NRGBAModel.Convert(src.At(srcR.Min.X+i, srcR.Min.Y)).(color.NRGBA) + j := 4 * i + put(d.b.Bits[3*(j+0):], c.G) + put(d.b.Bits[3*(j+1):], c.R) + put(d.b.Bits[3*(j+2):], c.B) + put(d.b.Bits[3*(j+3):], c.A) + } + } + } + _ = d.p.StreamOut(&d.b) +} + +// Write accepts a stream of raw RGB/RGBW pixels and sends it as NRZ encoded +// stream. +// +// This bypasses the back buffer. +func (d *Dev) Write(pixels []byte) (int, error) { + if len(pixels)%d.channels != 0 || len(pixels) > d.numPixels*d.channels { + return 0, errors.New("nrzled: invalid RGB stream length") + } + raster(d.b.Bits, pixels, d.channels, d.channels) + if err := d.p.StreamOut(&d.b); err != nil { + return 0, fmt.Errorf("nrzled: %v", err) + } + return len(pixels), nil +} + +// New opens a handle to a compatible LED strip. +// +// The speed (hz) should either be 800000 for fast ICs and 400000 for the slow +// ones. +// +// channels should be either 1 (White only), 3 (RGB) or 4 (RGBW). For RGB and +// RGBW, the encoding is respectively GRB and GRBW. +func New(p gpiostream.PinOut, numPixels, hz int, channels int) (*Dev, error) { + if hz <= 0 || hz > 1000000000 { + return nil, errors.New("nrzled: specify valid speed in hz") + } + if channels != 3 && channels != 4 { + return nil, errors.New("nrzled: specify valid number of channels (3 or 4)") + } + // It is more space effective to use gpiostream.Bits than + // gpiostream.EdgeStream. + return &Dev{ + p: p, + numPixels: numPixels, + channels: channels, + b: gpiostream.BitStream{ + Res: time.Second / time.Duration(hz), + // Each bit is encoded on 3 bits. + Bits: make(gpiostream.Bits, numPixels*3*channels), + }, + }, nil +} + +// + +// raster converts a RGB/RGBW input stream into a binary output stream as it +// must be sent over the GPIO pin. +// +// `in` is RGB 24 bits or RGBW 32 bits. Each bit is encoded over 3 bits so the +// length of `out` must be 3x as large as `in`. +// +// Encoded output format is GRB as 72 bits (24 * 3) or 96 bits (32 * 3). +func raster(out, in []byte, outChannels, inChannels int) { + pixels := len(in) / inChannels + if outChannels == 3 { + for i := 0; i < pixels; i++ { + j := i * inChannels + k := 3 * i + put(out[3*(k+0):], in[j+1]) + put(out[3*(k+1):], in[j+0]) + put(out[3*(k+2):], in[j+2]) + } + } else { + for i := 0; i < pixels; i++ { + j := i * inChannels + k := 4 * i + put(out[3*(k+0):], in[j+1]) + put(out[3*(k+1):], in[j+0]) + put(out[3*(k+2):], in[j+2]) + put(out[3*(k+3):], in[j+3]) + } + } +} + +func put(out []byte, v byte) { + w := NRZ(v) + out[0] = byte(w >> 16) + out[1] = byte(w >> 8) + out[2] = byte(w) +} + +var _ conn.Resource = &Dev{} +var _ devices.Display = &Dev{} +var _ fmt.Stringer = &Dev{} diff --git a/experimental/devices/nrzled/nrzled_test.go b/experimental/devices/nrzled/nrzled_test.go new file mode 100644 index 0000000..7f93359 --- /dev/null +++ b/experimental/devices/nrzled/nrzled_test.go @@ -0,0 +1,322 @@ +// 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 nrzled + +import ( + "bytes" + "image" + "image/color" + "strconv" + "testing" + "time" + + "periph.io/x/periph/conn/gpio/gpiostream" + "periph.io/x/periph/conn/gpio/gpiostream/gpiostreamtest" +) + +func TestNRZ(t *testing.T) { + data := []struct { + in byte + expected uint32 + }{ + {0x00, 0x924924}, + {0x01, 0x924926}, + {0x02, 0x924934}, + {0x04, 0x9249A4}, + {0x08, 0x924D24}, + {0x10, 0x926924}, + {0x20, 0x934924}, + {0x40, 0x9A4924}, + {0x80, 0xD24924}, + {0xFD, 0xDB6DA6}, + {0xFE, 0xDB6DB4}, + {0xFF, 0xDB6DB6}, + } + for i, line := range data { + t.Run(strconv.Itoa(i), func(t *testing.T) { + if actual := NRZ(line.in); line.expected != actual { + t.Fatalf("NRZ(%X): 0x%X != 0x%X", line.in, line.expected, actual) + } + }) + } +} + +func TestNew_3(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{ + N: "Yo", + Ops: []gpiostream.Stream{ + &gpiostream.BitStream{ + Bits: gpiostream.Bits{ + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, + 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, + 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, + 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, + 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, + }, + Res: 2500 * time.Nanosecond, + }, + }, + } + d, err := New(&g, 10, 400000, 3) + if err != nil { + t.Fatal(err) + } + if s := d.String(); s != "nrzled{Yo}" { + t.Fatal(s) + } + if c := d.ColorModel(); c != color.NRGBAModel { + t.Fatal(c) + } + if r := d.Bounds(); r != image.Rect(0, 0, 10, 1) { + t.Fatal(r) + } + if err = d.Halt(); err != nil { + t.Fatal(err) + } + if err = g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestNew_fail(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{} + if _, err := New(&g, 1, 0, 3); err == nil { + t.Fatal("hz == 0") + } + if _, err := New(&g, 1, 400000, 2); err == nil { + t.Fatal("channels == 2") + } +} + +func TestDraw_NRGBA_3(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{ + Ops: []gpiostream.Stream{ + &gpiostream.BitStream{ + Bits: gpiostream.Bits{ + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, + 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, + 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, + 0xb6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24, + }, + Res: 2500 * time.Nanosecond, + }, + }, + } + d, _ := New(&g, 10, 400000, 3) + img := image.NewNRGBA(d.Bounds()) + copy(img.Pix, getRGBW()) + d.Draw(d.Bounds(), img, image.Point{}) + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestDraw_RGBA_3(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{ + Ops: []gpiostream.Stream{ + &gpiostream.BitStream{ + Bits: gpiostream.Bits{ + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, + 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, + 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, + 0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36, + }, + Res: 2500 * time.Nanosecond, + }, + }, + } + d, _ := New(&g, 10, 400000, 3) + img := image.NewRGBA(d.Bounds()) + copy(img.Pix, getRGBW()) + d.Draw(d.Bounds(), img, image.Point{}) + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestDraw_RGBA_4(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{ + Ops: []gpiostream.Stream{ + &gpiostream.BitStream{ + Bits: gpiostream.Bits{ + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, + 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, + 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, + 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, + 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, + 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xd2, 0x49, 0x24, 0xda, 0x49, 0xb6, 0xd3, + 0x4d, 0x34, 0xdb, 0x49, 0x36, 0x92, 0x4d, 0x26, + }, + Res: 2500 * time.Nanosecond, + }, + }, + } + d, _ := New(&g, 10, 400000, 4) + img := image.NewRGBA(d.Bounds()) + copy(img.Pix, getRGBW()) + d.Draw(d.Bounds(), img, image.Point{}) + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestDraw_Limits(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{ + Ops: []gpiostream.Stream{ + &gpiostream.BitStream{ + Bits: gpiostream.Bits{ + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, + 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, + 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, + 0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36, + }, + Res: 2500 * time.Nanosecond, + }, + }, + } + d, _ := New(&g, 10, 400000, 3) + img := image.NewRGBA(image.Rect(-1, -1, 20, 20)) + copy(img.Pix, getRGBW()) + d.Draw(d.Bounds(), img, image.Point{}) + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestWrite_3(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{ + Ops: []gpiostream.Stream{ + &gpiostream.BitStream{ + Bits: gpiostream.Bits{ + 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, + 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, + 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, + 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0xa4, 0x92, 0x49, 0x36, 0x92, 0x49, + 0xa6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24, + }, + Res: 2500 * time.Nanosecond, + }, + }, + } + d, _ := New(&g, 10, 400000, 3) + if n, err := d.Write(getRGB()); n != 30 || err != nil { + t.Fatal(n, err) + } + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestWrite_fail(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{DontPanic: true} + d, _ := New(&g, 10, 400000, 3) + if n, err := d.Write([]byte{1}); n != 0 || err == nil { + t.Fatal(n, err) + } + if n, err := d.Write([]byte{1, 2, 3}); n != 0 || err == nil { + t.Fatal(n, err) + } + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestHalt_fail(t *testing.T) { + g := gpiostreamtest.PinOutPlayback{DontPanic: true} + d, _ := New(&g, 10, 400000, 3) + if d.Halt() == nil { + t.Fatal("expected failure") + } + if err := g.Close(); err != nil { + t.Fatal(err) + } +} + +func TestRaster_3_3(t *testing.T) { + data := []byte{ + // 24 bits per pixel in RGB + 0, 1, 2, + 0xFD, 0xFE, 0xFF, + } + expected := []byte{ + // 72 bits per pixel in GRB + 0x92, 0x49, 0x26, 0x92, 0x49, 0x24, 0x92, 0x49, 0x34, + 0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xb6, + } + actual := make([]byte, len(expected)) + raster(actual, data, 3, 3) + if !bytes.Equal(expected, actual) { + t.Fatalf("\nexpected %#v\n actual %#v", expected, actual) + } +} + +func TestRaster_4_4(t *testing.T) { + data := []byte{ + // 32 bits per pixel in RGBW + 0, 1, 2, 3, + 0xFC, 0xFD, 0xFE, 0xFF, + } + expected := []byte{ + // 96 bits per pixel in GRBW + 0x92, 0x49, 0x26, 0x92, 0x49, 0x24, 0x92, 0x49, 0x34, 0x92, 0x49, 0x36, + 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa4, 0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xb6, + } + actual := make([]byte, len(expected)) + raster(actual, data, 4, 4) + if !bytes.Equal(expected, actual) { + t.Fatalf("\nexpected %#v\n actual %#v", expected, actual) + } +} + +// + +func BenchmarkNRZ(b *testing.B) { + for i := 0; i < b.N; i++ { + NRZ(23) + } +} + +// + +// getRGB returns a buffer of 10 RGB pixels. +func getRGB() []byte { + return []byte{ + 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, + 3, 4, 5, + 6, 7, 8, + } +} + +// getRGBW returns a buffer of 10 RGB pixels. +func getRGBW() []byte { + return []byte{ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, + 6, 7, 8, 9, + } +}