New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support adding text labels to buttons #6

Open
Lange opened this Issue May 18, 2017 · 12 comments

Comments

Projects
None yet
5 participants
@Lange
Copy link
Owner

Lange commented May 18, 2017

The official Elgato Stream Deck software renders text by just baking it into the bitmap that it sends. We'll need to replicate this behavior.

@Lange Lange added the enhancement label May 18, 2017

@Lange

This comment has been minimized.

Copy link
Owner Author

Lange commented May 18, 2017

node-canvas might be a good lib to use for this. A cursory search did not reveal any viable alternatives.

EDIT: Maybe not, it looks like this requires that the user install a lot of things on their system before it will work?

@Lange

This comment has been minimized.

Copy link
Owner Author

Lange commented May 18, 2017

gm seems to suffer from the same problems as node-canvas, in that it requires the user to install third-party apps separately.

node-gd does not work on Windows.

@ProbablePrime

This comment has been minimized.

Copy link
Collaborator

ProbablePrime commented May 18, 2017

We could potentially add full drawing capabilities. Or some templates too. The twitch viewer number from the original software was interesting.

@danieltian

This comment has been minimized.

Copy link

danieltian commented May 19, 2017

I found this, though I haven't tested it to see how well it works:

https://www.npmjs.com/package/pureimage

Only JS dependencies, no external deps.

@Lange

This comment has been minimized.

Copy link
Owner Author

Lange commented May 19, 2017

Oooh awesome find @danieltian! I think that pureimage is our best choice as of right now.

@Lange

This comment has been minimized.

Copy link
Owner Author

Lange commented May 19, 2017

I've tried out pureimage and I was indeed able to get it working. However, it's not really that great at drawing text yet, but it's better than nothing.

img_20170519_001358

I have committed the code that generated this to examples/text-generation.js. There is a lot of room to make this better before we turn it into a proper API method, so help is welcome!

@danieltian

This comment has been minimized.

Copy link

danieltian commented May 19, 2017

Nice! It looks awesome! That should be good enough for now, I suspect that most people would be using bitmaps anyhow with on-the-fly text rendering as a niche case. Perhaps the feature can be expanded out in the future to allow for on-the-fly drawing of basic shapes, including text.

@paul-lrr

This comment has been minimized.

Copy link

paul-lrr commented May 23, 2017

I was messing around with the sharp library that you are already using for fillImageFromFile and noticed that it can composite SVG files as well as the bitmap graphics. By doing something like

let textSVG = `<svg width="72px" height="72px" viewBox="0 0 72 72">
<text x="5" y="70" style="fill:red;">FOOBAR</text>
</svg>`;
image.overlayWith(new Buffer(textSVG))

You can create an SVG document and overlay it on an existing sharp image. The nice thing is that you automatically get all the fonts, colors, styling, etc that you can do in regular SVG files. It seems like this same technique could also be used for basic shape drawing as mentioned above.

btw: thanks for working on this API so quickly after the release of the Stream Deck. It really opens up all sorts of cool possibilities for this little thing!

@Lange

This comment has been minimized.

Copy link
Owner Author

Lange commented May 23, 2017

Of course! That's brilliant @paul-lrr! We should absolutely take that route.

@paul-lrr

This comment has been minimized.

Copy link

paul-lrr commented May 24, 2017

So I wrinkle that I have encountered with the SVG plan is that, since you can't easily parse a real SVG document in node (without a browser to render it), anything fancy you want to do, like text wrapping or custom fonts, is difficult. I think I have a solution for text wrapping by using opentype.js to measure the text directly to set the line breaks, but it is a bit of a hack 😕

@Lange

This comment has been minimized.

Copy link
Owner Author

Lange commented May 24, 2017

@gagnonfranck

This comment has been minimized.

Copy link

gagnonfranck commented Apr 28, 2018

Here is my example:

20180428_150233

`
const StreamDeck = require('elgato-stream-deck');
const sharp = require('sharp');
const myStreamDeck = new StreamDeck();
const iconWidth = StreamDeck.ICON_SIZE
const iconHeight = StreamDeck.ICON_SIZE
const textHeight1 = 0
const textHeight2 = 20
const textHeight3 = 40

myStreamDeck.on('down', keyIndex => {
console.log('key %d down', keyIndex);
setIcon(keyIndex, text1=keyIndex, text2=keyIndex, text3=getRandomInt(), backgroundColor=getRandomColor(), textColor=getRandomColor())
});

myStreamDeck.on('up', keyIndex => {
console.log('key %d up', keyIndex);
setIcon(keyIndex, text1=keyIndex, text2=keyIndex, text3=getRandomInt(), backgroundColor=getRandomColor(), textColor=getRandomColor())
});

myStreamDeck.on('error', error => {
console.error(error);
});

function getRandomInt() {
return Math.floor(Math.random() * Math.floor(255));
}

function getRandomColor() {
return '#'+Math.floor(Math.random()*16777215).toString(16)
}

function fillImageFromBuffer(key, buffer) {
sharp(buffer)
.flatten() // Eliminate alpha channel, if any.
.raw()
.toBuffer()
.then(buffer => {
return myStreamDeck.fillImage(key, buffer);
});
}

function setIcon(key, text1="", text2="", text3="", backgroundColor="#0000FF", textColor="#000000", isStatusGood=true) {
var Canvas = require('canvas')
, Image = Canvas.Image
, canvas = new Canvas(iconWidth, iconHeight)
, ctx = canvas.getContext('2d');

ctx.font = '20px Arial';
ctx.textBaseline = 'top';

// Background
ctx.fillStyle=backgroundColor;
ctx.fillRect(0,0,iconWidth,iconHeight);

// Small status rectangle in bottom right corner
if(isStatusGood){
	ctx.fillStyle="#00FF00";
}else{
	ctx.fillStyle="#FF0000";
}
ctx.fillRect(iconWidth-15, iconHeight-15,10,5);

// Text
ctx.fillStyle=textColor;
ctx.fillText(text1, (iconWidth-ctx.measureText(text1).width)/2, textHeight1);
ctx.fillText(text2, (iconWidth-ctx.measureText(text2).width)/2, textHeight2);
ctx.fillText(text3, (iconWidth-ctx.measureText(text3).width)/2, textHeight3);
ctx.restore();

// Write Image
fillImageFromBuffer(key, canvas.toBuffer())

}

function clock() {
var time = new Date(),
hours = time.getHours(),
minutes = time.getMinutes(),
seconds = time.getSeconds();
milliseconds = time.getMilliseconds();

setIcon(4, text1=pad(hours)+'h', text2=pad(minutes)+'m', text3=pad(seconds)+'s',backgroundColor="#0000FF", textColor="#000000",isStatusGood=(seconds % 2 == 0));

setIcon(8, text1=pad(hours)+'h');
setIcon(7, text1=pad(minutes)+'m');
setIcon(6, text1=pad(seconds)+'s');
setIcon(5, text1=pad(milliseconds, len=3)+'ms');

function pad(n, len=2) {

s = n.toString();
if (s.length < len) {
    s = ('0000000000' + s).slice(-len);
}

return s;

}
}

function TestPanel() {
setIcon(4, text1='4')
setIcon(3, text1='3')
setIcon(2, text1='2')
setIcon(1, text1='1')
setIcon(0, text1='0')
setIcon(9, text1='9')
setIcon(8, text1='8')
setIcon(7, text1='7')
setIcon(6, text1='6')
setIcon(5, text1='5')
setIcon(14, text1='14')
setIcon(13, text1='13')
setIcon(12, text1='12')
setIcon(11, text1='11')
setIcon(10, text1='10',text2='',text3='',backgroundColor="#0000FF", textColor="#000000",isStatusGood=false)
}

TestPanel();
setIcon(9, text1='Time:');
setInterval(clock, 10);
console.log('Done');
`

You need to install node-canvas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment