func (sd *StreamDeck) FillPanel(img image.Image) error {
	rect := img.Bounds()
	if rect.Dx() != PanelWidth {
		newWidthRatio := float32(rect.Dx()) / float32((PanelWidth))
		img = resize(img, PanelWidth, int(float32(rect.Dy())/newWidthRatio))
	}
	rect = img.Bounds()
	if rect.Dx() > PanelWidth || rect.Dy() > PanelHeight {
		img = cropCenter(img, PanelWidth, PanelHeight)
	}
	counter := 0
	for row := 0; row < NumButtonRows; row++ {
		for col := 0; col < NumButtonColumns; col++ {
			rect := image.Rectangle{
				Min: image.Point{
					PanelWidth - ButtonSize - col*ButtonSize - col*Spacer,
					row*ButtonSize + row*Spacer,
				},
				Max: image.Point{
					PanelWidth - 1 - col*ButtonSize - col*Spacer,
					ButtonSize - 1 + row*ButtonSize + row*Spacer,
				},
			}
			sd.FillImage(counter, img.(*image.RGBA).SubImage(rect))
			counter++
		}
	}
	return nil
}
FillPanel fills the whole panel witn an image. The image is scaled to fit
and then center-cropped (if necessary). The native picture size is 360px x 216px.
package StreamDeck
import (
	"fmt"
	"image"
	"os"
	"sync"
	"github.com/disintegration/gift"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"github.com/dh1tw/hid"
	"image/color"
	"image/draw"
	_ "image/gif"   
	_ "image/jpeg"  
	_ "image/png"   
)
const VendorID = 4057
const ProductID = 96
const NumButtons = 15
const numFirstMsgPixels = 2583
const numSecondMsgPixels = 2601
const ButtonSize = 72
const NumButtonColumns = 5
const NumButtonRows = 3
const Spacer = 19
const PanelWidth = NumButtonColumns*ButtonSize + Spacer*(NumButtonColumns-1)
const PanelHeight = NumButtonRows*ButtonSize + Spacer*(NumButtonRows-1)
type BtnEvent func(btnIndex int, newBtnState BtnState)
type BtnState int
const (
	BtnPressed BtnState = iota
	BtnReleased
)
type ReadErrorCb func(err error)
type StreamDeck struct {
	sync.Mutex
	device     *hid.Device
	btnEventCb BtnEvent
	btnState   []BtnState
}
type TextButton struct {
	Lines   []TextLine
	BgColor color.Color
}
type TextLine struct {
	Text      string
	PosX      int
	PosY      int
	Font      *truetype.Font
	FontSize  float64
	FontColor color.Color
}
type Page interface {
	Set(btnIndex int, state BtnState) Page
	Parent() Page
	Draw()
	SetActive(bool)
}
func NewStreamDeck(serial ...string) (*StreamDeck, error) {
	if len(serial) > 1 {
		return nil, fmt.Errorf("only <= 1 serial numbers must be provided")
	}
	devices := hid.Enumerate(VendorID, ProductID)
	if len(devices) == 0 {
		return nil, fmt.Errorf("no stream deck device found")
	}
	id := 0
	if len(serial) == 1 {
		found := false
		for i, d := range devices {
			if d.Serial == serial[0] {
				id = i
				found = true
			}
		}
		if !found {
			return nil, fmt.Errorf("no stream deck device found with serial number %s", serial[0])
		}
	}
	device, err := devices[id].Open()
	if err != nil {
		return nil, err
	}
	sd := &StreamDeck{
		device:   device,
		btnState: make([]BtnState, NumButtons),
	}
	for i := range sd.btnState {
		sd.btnState[i] = BtnReleased
	}
	sd.ClearAllBtns()
	go sd.read()
	return sd, nil
}
func (sd *StreamDeck) SetBtnEventCb(ev BtnEvent) {
	sd.Lock()
	defer sd.Unlock()
	sd.btnEventCb = ev
}
func (sd *StreamDeck) read() {
	for {
		data := make([]byte, 16)
		_, err := sd.device.Read(data)
		if err != nil {
			fmt.Println(err)
		}
		data = data[1:]  
		sd.Lock()
		for i, b := range data {
			if sd.btnState[i] != itob(int(b)) {
				sd.btnState[i] = itob(int(b))
				if sd.btnEventCb != nil {
					btnState := sd.btnState[i]
					go sd.btnEventCb(i, btnState)
				}
			}
		}
		sd.Unlock()
	}
}
func (sd *StreamDeck) Close() error {
	sd.Lock()
	sd.Unlock()
	return sd.device.Close()
}
func (sd *StreamDeck) ClearBtn(btnIndex int) error {
	if err := checkValidKeyIndex(btnIndex); err != nil {
		return err
	}
	return sd.FillColor(btnIndex, 0, 0, 0)
}
func (sd *StreamDeck) ClearAllBtns() {
	for i := 14; i >= 0; i-- {
		sd.ClearBtn(i)
	}
}
func (sd *StreamDeck) FillColor(btnIndex, r, g, b int) error {
	if err := checkRGB(r); err != nil {
		return err
	}
	if err := checkRGB(g); err != nil {
		return err
	}
	if err := checkRGB(b); err != nil {
		return err
	}
	img := image.NewRGBA(image.Rect(0, 0, ButtonSize, ButtonSize))
	color := color.RGBA{uint8(r), uint8(g), uint8(b), 0}
	draw.Draw(img, img.Bounds(), image.NewUniform(color), image.Point{0, 0}, draw.Src)
	return sd.FillImage(btnIndex, img)
}
func (sd *StreamDeck) FillImage(btnIndex int, img image.Image) error {
	if err := checkValidKeyIndex(btnIndex); err != nil {
		return err
	}
	rect := img.Bounds()
	if rect.Dx() != ButtonSize {
		img = resize(img, ButtonSize, ButtonSize)
	}
	imgBuf := make([]byte, 0, ButtonSize*ButtonSize*3)
	for row := 0; row < ButtonSize; row++ {
		for line := ButtonSize - 1; line >= 0; line-- {
			r, g, b, _ := img.At(line, row).RGBA()
			imgBuf = append(imgBuf, byte(r), byte(b), byte(g))
		}
	}
	page1 := imgBuf[0 : numFirstMsgPixels*3]
	page2 := imgBuf[numFirstMsgPixels*3:]
	sd.Lock()
	sd.writeMsg1(btnIndex, page1)
	sd.writeMsg2(btnIndex, page2)
	sd.Unlock()
	return nil
}
func (sd *StreamDeck) FillImageFromFile(keyIndex int, path string) error {
	reader, err := os.Open(path)
	if err != nil {
		return err
	}
	defer reader.Close()
	img, _, err := image.Decode(reader)
	if err != nil {
		return err
	}
	return sd.FillImage(keyIndex, img)
}