1
0
Fork 0
mirror of https://github.com/Luzifer/streamdeck.git synced 2024-12-20 09:41:19 +00:00

Extract text rendering to helper

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2020-08-09 14:16:57 +02:00
parent fd6ac1e954
commit 9065b0ad0f
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
2 changed files with 148 additions and 112 deletions

View file

@ -4,24 +4,17 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"image"
"image/color" "image/color"
"image/draw"
_ "image/jpeg" _ "image/jpeg"
_ "image/png" _ "image/png"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
"github.com/Luzifer/go_helpers/v2/env" "github.com/Luzifer/go_helpers/v2/env"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
) )
func init() { func init() {
@ -34,13 +27,10 @@ type displayElementExec struct {
func (d displayElementExec) Display(ctx context.Context, idx int, attributes map[string]interface{}) error { func (d displayElementExec) Display(ctx context.Context, idx int, attributes map[string]interface{}) error {
var ( var (
err error err error
img draw.Image = image.NewRGBA(image.Rect(0, 0, sd.IconSize(), sd.IconSize())) imgRenderer = newTextOnImageRenderer()
) )
// Initialize black image
draw.Draw(img, img.Bounds(), image.NewUniform(color.RGBA{0x0, 0x0, 0x0, 0xff}), image.ZP, draw.Src)
// Initialize command // Initialize command
cmd, ok := attributes["command"].([]interface{}) cmd, ok := attributes["command"].([]interface{})
if !ok { if !ok {
@ -98,14 +88,9 @@ func (d displayElementExec) Display(ctx context.Context, idx int, attributes map
// Initialize background // Initialize background
if filename, ok := attributes["image"].(string); ok { if filename, ok := attributes["image"].(string); ok {
bgi, err := d.getImageFromDisk(filename) if err = imgRenderer.DrawBackgroundFromFile(filename); err != nil {
if err != nil { return errors.Wrap(err, "Unable to draw background from disk")
return errors.Wrap(err, "Unable to get image from disk")
} }
bgi = autoSizeImage(bgi, sd.IconSize())
draw.Draw(img, img.Bounds(), bgi, image.ZP, draw.Src)
} }
// Initialize color // Initialize color
@ -140,21 +125,8 @@ func (d displayElementExec) Display(ctx context.Context, idx int, attributes map
border = v border = v
} }
// Render text
f, err := d.loadFont()
if err != nil {
return errors.Wrap(err, "Unable to load font")
}
c := freetype.NewContext()
c.SetClip(img.Bounds())
c.SetDPI(72)
c.SetDst(img)
c.SetFont(f)
c.SetHinting(font.HintingNone)
if strings.TrimSpace(attributes["text"].(string)) != "" { if strings.TrimSpace(attributes["text"].(string)) != "" {
if err = d.drawText(c, strings.TrimSpace(attributes["text"].(string)), textColor, fontsize, border); err != nil { if err = imgRenderer.DrawBigText(strings.TrimSpace(attributes["text"].(string)), fontsize, border, textColor); err != nil {
return errors.Wrap(err, "Unable to render text") return errors.Wrap(err, "Unable to render text")
} }
} }
@ -168,7 +140,7 @@ func (d displayElementExec) Display(ctx context.Context, idx int, attributes map
return err return err
} }
return errors.Wrap(sd.FillImage(idx, img), "Unable to set image") return errors.Wrap(sd.FillImage(idx, imgRenderer.GetImage()), "Unable to set image")
} }
func (d displayElementExec) NeedsLoop(attributes map[string]interface{}) bool { func (d displayElementExec) NeedsLoop(attributes map[string]interface{}) bool {
@ -206,81 +178,3 @@ func (d *displayElementExec) StopLoopDisplay() error {
d.running = false d.running = false
return nil return nil
} }
func (displayElementExec) drawText(c *freetype.Context, text string, textColor color.Color, fontsize float64, border int) error {
c.SetSrc(image.NewUniform(color.RGBA{0x0, 0x0, 0x0, 0x0})) // Transparent for text size guessing
textLines := strings.Split(text, "\n")
for {
c.SetFontSize(fontsize)
var maxX fixed.Int26_6
for _, tl := range textLines {
ext, err := c.DrawString(tl, freetype.Pt(0, 0))
if err != nil {
return errors.Wrap(err, "Unable to measure text")
}
if ext.X > maxX {
maxX = ext.X
}
}
if int(float64(maxX)/64) > sd.IconSize()-2*border || (int(c.PointToFixed(fontsize)/64))*len(textLines)+(len(textLines)-1)*2 > sd.IconSize()-2*border {
fontsize -= 2
continue
}
break
}
var (
yTotal = (int(c.PointToFixed(fontsize)/64))*len(textLines) + len(textLines)*2
yLineTop = int(float64(sd.IconSize())/2.0 - float64(yTotal)/2.0)
)
for _, tl := range textLines {
c.SetSrc(image.NewUniform(color.RGBA{0x0, 0x0, 0x0, 0x0})) // Transparent for text size guessing
ext, err := c.DrawString(tl, freetype.Pt(0, 0))
if err != nil {
return errors.Wrap(err, "Unable to measure text")
}
c.SetSrc(image.NewUniform(textColor))
xcenter := (float64(sd.IconSize()-2*border) / 2.0) - (float64(int(float64(ext.X)/64)) / 2.0) + float64(border)
ylower := yLineTop + int(c.PointToFixed(fontsize)/64)
if _, err = c.DrawString(tl, freetype.Pt(int(xcenter), int(ylower))); err != nil {
return errors.Wrap(err, "Unable to draw text")
}
yLineTop += int(c.PointToFixed(fontsize)/64) + 2
}
return nil
}
func (displayElementExec) getImageFromDisk(filename string) (image.Image, error) {
f, err := os.Open(filename)
if err != nil {
return nil, errors.Wrap(err, "Unable to open image")
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return nil, errors.Wrap(err, "Umable to decode image")
}
return img, nil
}
func (displayElementExec) loadFont() (*truetype.Font, error) {
fontRaw, err := ioutil.ReadFile(userConfig.RenderFont)
if err != nil {
return nil, errors.Wrap(err, "Unable to read font file")
}
return truetype.Parse(fontRaw)
}

View file

@ -0,0 +1,142 @@
package main
import (
"image"
"image/color"
"image/draw"
"io/ioutil"
"os"
"strings"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/pkg/errors"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type textOnImageRenderer struct {
img draw.Image
}
func newTextOnImageRenderer() *textOnImageRenderer {
// Create new black image in icon size
var img draw.Image = image.NewRGBA(image.Rect(0, 0, sd.IconSize(), sd.IconSize()))
draw.Draw(img, img.Bounds(), image.NewUniform(color.RGBA{0x0, 0x0, 0x0, 0xff}), image.ZP, draw.Src)
return &textOnImageRenderer{
img: img,
}
}
func (t *textOnImageRenderer) DrawBackground(bgi image.Image) {
bgi = autoSizeImage(bgi, sd.IconSize())
draw.Draw(t.img, t.img.Bounds(), bgi, image.ZP, draw.Src)
}
func (t *textOnImageRenderer) DrawBackgroundFromFile(filename string) error {
bgi, err := t.getImageFromDisk(filename)
if err != nil {
return errors.Wrap(err, "Unable to get image from disk")
}
t.DrawBackground(bgi)
return nil
}
func (t *textOnImageRenderer) DrawBigText(text string, fontSizeHint float64, border int, textColor color.Color) error {
// Render text
f, err := t.loadFont()
if err != nil {
return errors.Wrap(err, "Unable to load font")
}
c := freetype.NewContext()
c.SetClip(t.img.Bounds())
c.SetDPI(72)
c.SetDst(t.img)
c.SetFont(f)
c.SetHinting(font.HintingNone)
return t.drawText(c, text, textColor, fontSizeHint, border)
}
func (t textOnImageRenderer) GetImage() image.Image { return t.img }
func (t *textOnImageRenderer) drawText(c *freetype.Context, text string, textColor color.Color, fontsize float64, border int) error {
c.SetSrc(image.NewUniform(color.RGBA{0x0, 0x0, 0x0, 0x0})) // Transparent for text size guessing
textLines := strings.Split(text, "\n")
for {
c.SetFontSize(fontsize)
var maxX fixed.Int26_6
for _, tl := range textLines {
ext, err := c.DrawString(tl, freetype.Pt(0, 0))
if err != nil {
return errors.Wrap(err, "Unable to measure text")
}
if ext.X > maxX {
maxX = ext.X
}
}
if int(float64(maxX)/64) > sd.IconSize()-2*border || (int(c.PointToFixed(fontsize)/64))*len(textLines)+(len(textLines)-1)*2 > sd.IconSize()-2*border {
fontsize -= 2
continue
}
break
}
var (
yTotal = (int(c.PointToFixed(fontsize)/64))*len(textLines) + len(textLines)*2
yLineTop = int(float64(sd.IconSize())/2.0 - float64(yTotal)/2.0)
)
for _, tl := range textLines {
c.SetSrc(image.NewUniform(color.RGBA{0x0, 0x0, 0x0, 0x0})) // Transparent for text size guessing
ext, err := c.DrawString(tl, freetype.Pt(0, 0))
if err != nil {
return errors.Wrap(err, "Unable to measure text")
}
c.SetSrc(image.NewUniform(textColor))
xcenter := (float64(sd.IconSize()-2*border) / 2.0) - (float64(int(float64(ext.X)/64)) / 2.0) + float64(border)
ylower := yLineTop + int(c.PointToFixed(fontsize)/64)
if _, err = c.DrawString(tl, freetype.Pt(int(xcenter), int(ylower))); err != nil {
return errors.Wrap(err, "Unable to draw text")
}
yLineTop += int(c.PointToFixed(fontsize)/64) + 2
}
return nil
}
func (textOnImageRenderer) getImageFromDisk(filename string) (image.Image, error) {
f, err := os.Open(filename)
if err != nil {
return nil, errors.Wrap(err, "Unable to open image")
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return nil, errors.Wrap(err, "Umable to decode image")
}
return img, nil
}
func (textOnImageRenderer) loadFont() (*truetype.Font, error) {
fontRaw, err := ioutil.ReadFile(userConfig.RenderFont)
if err != nil {
return nil, errors.Wrap(err, "Unable to read font file")
}
return truetype.Parse(fontRaw)
}