Skip to content

Commit e88d4d5

Browse files
committed
Merge remote-tracking branch 'dhollinger/sd_mini'
2 parents 1cabaab + 2c9c3cf commit e88d4d5

File tree

9 files changed

+148
-46
lines changed

9 files changed

+148
-46
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, 96, 96))
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, 96, 96))
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: 23 additions & 9 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

@@ -139,7 +140,7 @@ func (d *Device) ClearButtons() {
139140

140141
// WriteColorToButton writes a specified color to the given button
141142
func (d *Device) WriteColorToButton(btnIndex int, colour color.Color) error {
142-
img := getSolidColourImage(colour)
143+
img := getSolidColourImage(colour, d.deviceType.imageSize.X)
143144
imgForButton, err := getImageForButton(img, d.deviceType.imageFormat)
144145
if err != nil {
145146
return err
@@ -199,7 +200,7 @@ func (d *Device) ResetComms() {
199200

200201
// 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)
201202
func (d *Device) WriteRawImageToButton(btnIndex int, rawImg image.Image) error {
202-
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)
203204
imgForButton, err := getImageForButton(img, d.deviceType.imageFormat)
204205
if err != nil {
205206
return err
@@ -209,12 +210,14 @@ func (d *Device) WriteRawImageToButton(btnIndex int, rawImg image.Image) error {
209210

210211
func (d *Device) rawWriteToButton(btnIndex int, rawImage []byte) error {
211212
// 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+
212218
pageNumber := 0
213219
bytesRemaining := len(rawImage)
214220

215-
// Surely no image can be more than 20 packets...?
216-
payloads := make([][]byte, 20)
217-
218221
for bytesRemaining > 0 {
219222

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

237240
thingToSend := append(payload, padding...)
238241
d.fd.Write(thingToSend)
239-
payloads[pageNumber] = thingToSend
240242

241243
bytesRemaining = bytesRemaining - thisLength
242244
pageNumber = pageNumber + 1
243-
if pageNumber >= len(payloads) {
244-
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")
245-
}
246245
}
247246
return nil
248247
}
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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package devices
2+
3+
import (
4+
"image"
5+
6+
streamdeck "github.com/magicmonkey/go-streamdeck"
7+
)
8+
9+
var (
10+
miniName string
11+
miniButtonWidth uint
12+
miniButtonHeight uint
13+
miniImageReportPayloadLength uint
14+
miniImageReportHeaderLength uint
15+
miniImageReportLength uint
16+
)
17+
18+
// GetImageHeaderMini returns the USB comms header for a button image for the XL
19+
func GetImageHeaderMini(bytesRemaining uint, btnIndex uint, pageNumber uint) []byte {
20+
var thisLength uint
21+
if miniImageReportPayloadLength < bytesRemaining {
22+
thisLength = miniImageReportPayloadLength
23+
} else {
24+
thisLength = bytesRemaining
25+
}
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',
43+
}
44+
45+
return header
46+
}
47+
48+
func get_header_element(thisLength, bytesRemaining uint) byte {
49+
if thisLength == bytesRemaining {
50+
return '\x01'
51+
} else {
52+
return '\x00'
53+
}
54+
}
55+
56+
func init() {
57+
miniName = "Streamdeck Mini"
58+
miniButtonWidth = 80
59+
miniButtonHeight = 80
60+
miniImageReportPayloadLength = 1024
61+
streamdeck.RegisterDevicetype(
62+
miniName, // Name
63+
image.Point{X: int(miniButtonWidth), Y: int(miniButtonHeight)}, // Width/height of a button
64+
0x63, // USB productID
65+
[]byte{0x0B, 0x63}, // Reset packet
66+
6, // Number of buttons
67+
[]byte{0x05, 0x55, 0xaa, 0xd1, 0x01}, // Reset packet
68+
0, // Button read offset
69+
"BMP", // Image format
70+
miniImageReportPayloadLength, // Amount of image payload allowed per USB packet
71+
GetImageHeaderMini, // Function to get the comms image header
72+
)
73+
}

image.go

Lines changed: 26 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"
@@ -12,33 +13,49 @@ import (
1213
"os"
1314

1415
"github.com/disintegration/gift"
16+
"golang.org/x/image/bmp"
1517
)
1618

17-
func resizeAndRotate(img image.Image, width, height int) image.Image {
18-
g := gift.New(
19-
gift.Resize(width, height, gift.LanczosResampling),
20-
//gift.UnsharpMask(1, 1, 0),
21-
gift.Rotate180(),
22-
)
19+
func resizeAndRotate(img image.Image, width, height int, devname string) image.Image {
20+
g, _ := deviceSpecifics(devname, width, height)
2321
res := image.NewRGBA(g.Bounds(img.Bounds()))
2422
g.Draw(res, img)
2523
return res
2624
}
2725

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+
2844
func getImageForButton(img image.Image, btnFormat string) ([]byte, error) {
2945
var b bytes.Buffer
3046
switch btnFormat {
3147
case "JPEG":
3248
jpeg.Encode(&b, img, nil)
49+
case "BMP":
50+
bmp.Encode(&b, img)
3351
default:
3452
return nil, errors.New("Unknown button image format: " + btnFormat)
3553
}
3654
return b.Bytes(), nil
3755
}
3856

39-
func getSolidColourImage(colour color.Color) *image.RGBA {
40-
ButtonSize := 96
41-
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))
4259
//colour := color.RGBA{red, green, blue, 0}
4360
draw.Draw(img, img.Bounds(), image.NewUniform(colour), image.Point{0, 0}, draw.Src)
4461
return img

streamdeck.go

Lines changed: 5 additions & 5 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,15 +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)
119+
sd.dev.SetBrightness(brightness)
120120
}

0 commit comments

Comments
 (0)