Skip to content

Commit 2c9c3cf

Browse files
committed
Add support for sd_mini and dynamic button sizes
1 parent 81f0d69 commit 2c9c3cf

File tree

9 files changed

+103
-79
lines changed

9 files changed

+103
-79
lines changed

buttons/colour.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ type ColourButton struct {
1717
}
1818

1919
// GetImageForButton is the interface implemention to get the button's image as an image.Image
20-
func (btn *ColourButton) GetImageForButton() image.Image {
21-
ButtonSize := 96
22-
img := image.NewRGBA(image.Rect(0, 0, ButtonSize, ButtonSize))
20+
func (btn *ColourButton) GetImageForButton(btnSize int) image.Image {
21+
img := image.NewRGBA(image.Rect(0, 0, btnSize, btnSize))
2322
//colour := color.RGBA{red, green, blue, 0}
2423
draw.Draw(img, img.Bounds(), image.NewUniform(btn.colour), image.Point{0, 0}, draw.Src)
2524
return img

buttons/imagefile.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ type ImageFileButton struct {
1919
}
2020

2121
// GetImageForButton is the interface implemention to get the button's image as an image.Image
22-
func (btn *ImageFileButton) GetImageForButton() image.Image {
22+
func (btn *ImageFileButton) GetImageForButton(btnSize int) image.Image {
2323
// TODO base the 96 on the image bounds
24-
newimg := image.NewRGBA(image.Rect(0, 0, 80, 80))
24+
newimg := image.NewRGBA(image.Rect(0, 0, btnSize, btnSize))
2525
draw.Draw(newimg, newimg.Bounds(), btn.img, image.Point{0, 0}, draw.Src)
2626
return newimg
2727
}
@@ -37,17 +37,17 @@ func (btn *ImageFileButton) GetButtonIndex() int {
3737
}
3838

3939
// SetFilePath allows the image file to be changed on the fly
40-
func (btn *ImageFileButton) SetFilePath(filePath string) error {
40+
func (btn *ImageFileButton) SetFilePath(filePath string, btnSize int) error {
4141
btn.filePath = filePath
42-
err := btn.loadImage()
42+
err := btn.loadImage(btnSize)
4343
if err != nil {
4444
return err
4545
}
4646
btn.updateHandler(btn)
4747
return nil
4848
}
4949

50-
func (btn *ImageFileButton) loadImage() error {
50+
func (btn *ImageFileButton) loadImage(btnSize int) error {
5151
f, err := os.Open(btn.filePath)
5252
if err != nil {
5353
return err
@@ -59,7 +59,7 @@ func (btn *ImageFileButton) loadImage() error {
5959
newimg, ok := img.(*image.RGBA)
6060
if !ok {
6161
// TODO base the 96 on the button size
62-
newimg = image.NewRGBA(image.Rect(0, 0, 80, 80))
62+
newimg = image.NewRGBA(image.Rect(0, 0, btnSize, btnSize))
6363
draw.Draw(newimg, newimg.Bounds(), img, image.Point{0, 0}, draw.Src)
6464
}
6565

@@ -91,9 +91,9 @@ func (btn *ImageFileButton) Pressed() {
9191
}
9292

9393
// NewImageFileButton creates a new ImageFileButton with the specified image on it
94-
func NewImageFileButton(filePath string) (*ImageFileButton, error) {
94+
func NewImageFileButton(filePath string, btnSize int) (*ImageFileButton, error) {
9595
btn := &ImageFileButton{filePath: filePath}
96-
err := btn.loadImage()
96+
err := btn.loadImage(btnSize)
9797
if err != nil {
9898
return nil, err
9999
}

buttons/text.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ type TextButton struct {
2323
}
2424

2525
// GetImageForButton is the interface implemention to get the button's image as an image.Image
26-
func (btn *TextButton) GetImageForButton() image.Image {
27-
img := getImageWithText(btn.label, btn.textColour, btn.backgroundColour)
26+
func (btn *TextButton) GetImageForButton(btnSize int) image.Image {
27+
img := getImageWithText(btn.label, btn.textColour, btn.backgroundColour, btnSize)
2828
return img
2929
}
3030

@@ -92,9 +92,8 @@ func NewTextButtonWithColours(label string, textColour color.Color, backgroundCo
9292
return btn
9393
}
9494

95-
func getImageWithText(text string, textColour color.Color, backgroundColour color.Color) image.Image {
95+
func getImageWithText(text string, textColour color.Color, backgroundColour color.Color, btnSize int) image.Image {
9696

97-
ButtonSize := 96
9897
size := float64(18)
9998

10099
myfont, err := truetype.Parse(gomedium.TTF)
@@ -113,7 +112,7 @@ func getImageWithText(text string, textColour color.Color, backgroundColour colo
113112

114113
srcImg := image.NewUniform(textColour)
115114

116-
dstImg := image.NewRGBA(image.Rect(0, 0, ButtonSize, ButtonSize))
115+
dstImg := image.NewRGBA(image.Rect(0, 0, btnSize, btnSize))
117116
draw.Draw(dstImg, dstImg.Bounds(), image.NewUniform(backgroundColour), image.Point{0, 0}, draw.Src)
118117

119118
c := freetype.NewContext()
@@ -123,7 +122,7 @@ func getImageWithText(text string, textColour color.Color, backgroundColour colo
123122
c.SetFontSize(size)
124123
c.SetClip(dstImg.Bounds())
125124

126-
x := int((96 - width) / 2) // Horizontally centre text
125+
x := int((btnSize - width) / 2) // Horizontally centre text
127126
y := int(50 + (size / 3)) // Fudged vertical centre, erm, very "heuristic"
128127

129128
pt := freetype.Pt(x, y)

comms.go

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package streamdeck
22

33
import (
44
"errors"
5+
"fmt"
56
"image"
67
"image/color"
78

@@ -125,8 +126,8 @@ func (d *Device) SetBrightness(pct int) {
125126
}
126127

127128
preamble := d.deviceType.brightnessPacket
128-
//payload := append(preamble, byte(pct))
129-
d.fd.SendFeatureReport(preamble)
129+
payload := append(preamble, byte(pct))
130+
d.fd.SendFeatureReport(payload)
130131
}
131132

132133
// ClearButtons writes a black square to all buttons
@@ -137,18 +138,9 @@ func (d *Device) ClearButtons() {
137138
}
138139
}
139140

140-
// Display the Serial Number
141-
func (d *Device) SetSerial(b []byte) error {
142-
_, err := d.fd.Write(b)
143-
if err != nil {
144-
return err
145-
}
146-
return nil
147-
}
148-
149141
// WriteColorToButton writes a specified color to the given button
150142
func (d *Device) WriteColorToButton(btnIndex int, colour color.Color) error {
151-
img := getSolidColourImage(colour)
143+
img := getSolidColourImage(colour, d.deviceType.imageSize.X)
152144
imgForButton, err := getImageForButton(img, d.deviceType.imageFormat)
153145
if err != nil {
154146
return err
@@ -208,7 +200,7 @@ func (d *Device) ResetComms() {
208200

209201
// WriteRawImageToButton takes an `image.Image` and writes it to the given button, after resizing and rotating the image to fit the button (for some reason the StreamDeck screens are all upside down)
210202
func (d *Device) WriteRawImageToButton(btnIndex int, rawImg image.Image) error {
211-
img := resizeAndRotate(rawImg, d.deviceType.imageSize.X, d.deviceType.imageSize.Y)
203+
img := resizeAndRotate(rawImg, d.deviceType.imageSize.X, d.deviceType.imageSize.Y, d.deviceType.name)
212204
imgForButton, err := getImageForButton(img, d.deviceType.imageFormat)
213205
if err != nil {
214206
return err
@@ -218,12 +210,14 @@ func (d *Device) WriteRawImageToButton(btnIndex int, rawImg image.Image) error {
218210

219211
func (d *Device) rawWriteToButton(btnIndex int, rawImage []byte) error {
220212
// Based on set_key_image from https://github.com/abcminiuser/python-elgato-streamdeck/blob/master/src/StreamDeck/Devices/StreamDeckXL.py#L151
213+
214+
if Min(Max(btnIndex, 0), int(d.deviceType.numberOfButtons)) != btnIndex {
215+
return errors.New(fmt.Sprintf("Invalid key index: %d", btnIndex))
216+
}
217+
221218
pageNumber := 0
222219
bytesRemaining := len(rawImage)
223220

224-
// Surely no image can be more than 20 packets...?
225-
payloads := make([][]byte, 20)
226-
227221
for bytesRemaining > 0 {
228222

229223
header := d.deviceType.imageHeaderFunc(uint(bytesRemaining), uint(btnIndex), uint(pageNumber))
@@ -245,13 +239,24 @@ func (d *Device) rawWriteToButton(btnIndex int, rawImage []byte) error {
245239

246240
thingToSend := append(payload, padding...)
247241
d.fd.Write(thingToSend)
248-
payloads[pageNumber] = thingToSend
249242

250243
bytesRemaining = bytesRemaining - thisLength
251244
pageNumber = pageNumber + 1
252-
if pageNumber >= len(payloads) {
253-
return errors.New("Image too big for button comms, aborting - you probably need to reset the Streamdeck at this stage, and modify the size of `payloads` on line 142 to be something bigger")
254-
}
255245
}
256246
return nil
257247
}
248+
249+
// Golang Min/Max
250+
func Min(x, y int) int {
251+
if x < y {
252+
return x
253+
}
254+
return y
255+
}
256+
257+
func Max (x, y int) int {
258+
if x > y {
259+
return x
260+
}
261+
return y
262+
}

decorators/border.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ func NewBorder(width int, colour color.Color) *Border {
1515
return b
1616
}
1717

18-
func (b *Border) Apply(img image.Image) image.Image {
18+
func (b *Border) Apply(img image.Image, size int) image.Image {
1919
newimg := img.(*image.RGBA)
2020
// TODO base the 96 on the image bounds
2121
for i := 0; i < b.width; i++ {
22-
rect(i, i, 96-i, 96-i, newimg, b.colour)
22+
rect(i, i, size-i, size-i, newimg, b.colour)
2323
}
2424
return newimg
2525
}

devices/mini.go

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,54 @@ var (
1717

1818
// GetImageHeaderMini returns the USB comms header for a button image for the XL
1919
func GetImageHeaderMini(bytesRemaining uint, btnIndex uint, pageNumber uint) []byte {
20-
thisLength := uint(0)
20+
var thisLength uint
2121
if miniImageReportPayloadLength < bytesRemaining {
2222
thisLength = miniImageReportPayloadLength
2323
} else {
2424
thisLength = bytesRemaining
2525
}
26-
header := []byte{'\x02', '\x07', byte(btnIndex)}
27-
if thisLength == bytesRemaining {
28-
header = append(header, '\x01')
29-
} else {
30-
header = append(header, '\x00')
26+
header := []byte{
27+
'\x02',
28+
'\x01',
29+
byte(pageNumber),
30+
0,
31+
get_header_element(thisLength, bytesRemaining),
32+
byte(btnIndex + 1),
33+
'\x00',
34+
'\x00',
35+
'\x00',
36+
'\x00',
37+
'\x00',
38+
'\x00',
39+
'\x00',
40+
'\x00',
41+
'\x00',
42+
'\x00',
3143
}
3244

33-
header = append(header, byte(thisLength&0xff))
34-
header = append(header, byte(thisLength>>8))
35-
36-
header = append(header, byte(pageNumber&0xff))
37-
header = append(header, byte(pageNumber>>8))
38-
3945
return header
4046
}
4147

48+
func get_header_element(thisLength, bytesRemaining uint) byte {
49+
if thisLength == bytesRemaining {
50+
return '\x01'
51+
} else {
52+
return '\x00'
53+
}
54+
}
55+
4256
func init() {
4357
miniName = "Streamdeck Mini"
4458
miniButtonWidth = 80
4559
miniButtonHeight = 80
46-
miniImageReportLength = 1024
47-
miniImageReportHeaderLength = 16
48-
miniImageReportPayloadLength = miniImageReportLength - miniImageReportHeaderLength
60+
miniImageReportPayloadLength = 1024
4961
streamdeck.RegisterDevicetype(
5062
miniName, // Name
5163
image.Point{X: int(miniButtonWidth), Y: int(miniButtonHeight)}, // Width/height of a button
5264
0x63, // USB productID
53-
[]byte{'\x03', '\x00', '\x42', '\x00', '\x4C', '\x00', '\x33', '\x00', '\x31', '\x00', '\x4A', '\x00', '\x31', '\x00', '\x42', '\x00', '\x30', '\x00', '\x31', '\x00', '\x35', '\x00', '\x38', '\x00', '\x32', '\x00'}, // Reset packet
65+
[]byte{0x0B, 0x63}, // Reset packet
5466
6, // Number of buttons
55-
//[]byte{'\x05', '\x55', '\xaa', '\xd1', '\x01'}, // Set brightness packet preamble
56-
[]byte{'\x03', '\x00', '\x42', '\x00', '\x4C', '\x00', '\x33', '\x00', '\x31', '\x00', '\x4A', '\x00', '\x31', '\x00', '\x42', '\x00', '\x30', '\x00', '\x31', '\x00', '\x35', '\x00', '\x38', '\x00', '\x32', '\x00'}, // Reset packet
67+
[]byte{0x05, 0x55, 0xaa, 0xd1, 0x01}, // Reset packet
5768
0, // Button read offset
5869
"BMP", // Image format
5970
miniImageReportPayloadLength, // Amount of image payload allowed per USB packet

image.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package streamdeck
33
import (
44
"bytes"
55
"errors"
6+
"fmt"
67
"image"
78
"image/color"
89
"image/draw"
@@ -15,17 +16,31 @@ import (
1516
"golang.org/x/image/bmp"
1617
)
1718

18-
func resizeAndRotate(img image.Image, width, height int) image.Image {
19-
g := gift.New(
20-
gift.Resize(width, height, gift.LanczosResampling),
21-
//gift.UnsharpMask(1, 1, 0),
22-
gift.Rotate180(),
23-
)
19+
func resizeAndRotate(img image.Image, width, height int, devname string) image.Image {
20+
g, _ := deviceSpecifics(devname, width, height)
2421
res := image.NewRGBA(g.Bounds(img.Bounds()))
2522
g.Draw(res, img)
2623
return res
2724
}
2825

26+
func deviceSpecifics(devName string, width, height int) (*gift.GIFT, error) {
27+
switch devName {
28+
case "Streamdeck XL":
29+
return gift.New(
30+
gift.Resize(width, height, gift.LanczosResampling),
31+
gift.Rotate180(),
32+
), nil
33+
case "Streamdeck Mini":
34+
return gift.New(
35+
gift.Resize(width, height, gift.LanczosResampling),
36+
gift.Rotate90(),
37+
gift.FlipVertical(),
38+
), nil
39+
default:
40+
return nil, errors.New(fmt.Sprintf("Unsupported Device: %s", devName))
41+
}
42+
}
43+
2944
func getImageForButton(img image.Image, btnFormat string) ([]byte, error) {
3045
var b bytes.Buffer
3146
switch btnFormat {
@@ -39,9 +54,8 @@ func getImageForButton(img image.Image, btnFormat string) ([]byte, error) {
3954
return b.Bytes(), nil
4055
}
4156

42-
func getSolidColourImage(colour color.Color) *image.RGBA {
43-
ButtonSize := 80
44-
img := image.NewRGBA(image.Rect(0, 0, ButtonSize, ButtonSize))
57+
func getSolidColourImage(colour color.Color, btnSize int) *image.RGBA {
58+
img := image.NewRGBA(image.Rect(0, 0, btnSize, btnSize))
4559
//colour := color.RGBA{red, green, blue, 0}
4660
draw.Draw(img, img.Bounds(), image.NewUniform(colour), image.Point{0, 0}, draw.Src)
4761
return img

streamdeck.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "image"
44

55
// ButtonDisplay is the interface to satisfy for displaying on a button
66
type ButtonDisplay interface {
7-
GetImageForButton() image.Image
7+
GetImageForButton(int) image.Image
88
GetButtonIndex() int
99
SetButtonIndex(int)
1010
RegisterUpdateHandler(func(Button))
@@ -23,7 +23,7 @@ type Button interface {
2323

2424
// ButtonDecorator represents a way to modify the button image, for example to add a highlight or an "on/off" hint
2525
type ButtonDecorator interface {
26-
Apply(image.Image) image.Image
26+
Apply(image.Image, int) image.Image
2727
}
2828

2929
// StreamDeck is the main struct to represent a StreamDeck device, and internally contains the reference to a `Device`
@@ -106,19 +106,15 @@ func (sd *StreamDeck) pressHandler(btnIndex int, d *Device, err error) {
106106
}
107107

108108
func (sd *StreamDeck) updateButton(b Button) error {
109-
img := b.GetImageForButton()
109+
img := b.GetImageForButton(sd.dev.deviceType.imageSize.X)
110110
decorator, ok := sd.decorators[b.GetButtonIndex()]
111111
if ok {
112-
img = decorator.Apply(img)
112+
img = decorator.Apply(img, sd.dev.deviceType.imageSize.X)
113113
}
114114
e := sd.dev.WriteRawImageToButton(b.GetButtonIndex(), img)
115115
return e
116116
}
117117

118118
func (sd *StreamDeck) SetBrightness(brightness int) {
119-
sd.dev.SetBrightness(brightness)
120-
}
121-
122-
func (sd *StreamDeck) SetSerial(b []byte) error {
123-
return sd.dev.SetSerial(b)
119+
sd.dev.SetBrightness(brightness)
124120
}

0 commit comments

Comments
 (0)