1
0

Compare commits

...

3 Commits

12 changed files with 102 additions and 48 deletions

View File

@ -175,9 +175,12 @@ func renderCBDTGlyph(r rune, targetSize int, scale float64) (image.Image, bool)
raw := emojiCBDT.imgs[idx] raw := emojiCBDT.imgs[idx]
displaySize := int(float64(targetSize) * scale) displaySize := int(float64(targetSize) * scale)
if displaySize < 1 { if displaySize > targetSize {
displaySize = targetSize displaySize = targetSize
} }
if displaySize < 1 {
displaySize = 1
}
scaled := raw scaled := raw
if raw.Bounds().Dx() != displaySize || raw.Bounds().Dy() != displaySize { if raw.Bounds().Dx() != displaySize || raw.Bounds().Dy() != displaySize {

View File

@ -3,6 +3,7 @@
"log_level": "info", "log_level": "info",
"default_page": "main", "default_page": "main",
"font": "medium", "font": "medium",
"show_label_background": true,
"devices": [ "devices": [
{ {
"serial": "", "serial": "",

View File

@ -57,6 +57,7 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
for i, d := range decks { for i, d := range decks {
pageMgrs[i] = NewPageManager(d) pageMgrs[i] = NewPageManager(d)
pageMgrs[i].defaultFont = cfg.Font pageMgrs[i].defaultFont = cfg.Font
pageMgrs[i].showLabelBackground = cfg.ShowLabelBackground
pageMgrs[i].LoadPages(cfg.Pages) pageMgrs[i].LoadPages(cfg.Pages)
} }
primaryPM := pageMgrs[0] primaryPM := pageMgrs[0]
@ -230,6 +231,7 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
activePages = append(activePages, pm.ActivePageName()) activePages = append(activePages, pm.ActivePageName())
pm.stopPeriodicKeys() pm.stopPeriodicKeys()
pm.defaultFont = cfg.Font pm.defaultFont = cfg.Font
pm.showLabelBackground = cfg.ShowLabelBackground
pm.LoadPages(cfg.Pages) pm.LoadPages(cfg.Pages)
} }
ge.ReloadTiming(cfg.Timing.LongPressMs, cfg.Timing.DoubleTapMs) ge.ReloadTiming(cfg.Timing.LongPressMs, cfg.Timing.DoubleTapMs)
@ -315,6 +317,7 @@ func reconnectDeck(ctx context.Context, cfg *config.Config, pm **PageManager, as
if err == nil { if err == nil {
*pm = NewPageManager(newDeck) *pm = NewPageManager(newDeck)
(*pm).defaultFont = cfg.Font (*pm).defaultFont = cfg.Font
(*pm).showLabelBackground = cfg.ShowLabelBackground
(*pm).LoadPages(cfg.Pages) (*pm).LoadPages(cfg.Pages)
(*pm).ActivatePage(cfg.DefaultPage) (*pm).ActivatePage(cfg.DefaultPage)
(*pm).startPeriodicKeys() (*pm).startPeriodicKeys()

17
deck.go
View File

@ -8,6 +8,7 @@ import (
"log/slog" "log/slog"
"sync" "sync"
"time" "time"
"unicode/utf8"
"github.com/bearsh/hid" "github.com/bearsh/hid"
"github.com/dh1tw/streamdeck" "github.com/dh1tw/streamdeck"
@ -319,7 +320,7 @@ func (d *Deck) WriteText(keyIndex int, text string, bg color.Color, fontName str
}) })
} }
func (d *Deck) WriteTextOnImage(keyIndex int, img image.Image, text, fontName string, fontSize float64) error { func (d *Deck) WriteTextOnImage(keyIndex int, img image.Image, text, fontName string, fontSize float64, showLabelBackground bool) error {
ks := d.cfg.KeySize ks := d.cfg.KeySize
g := gift.New( g := gift.New(
@ -338,12 +339,14 @@ func (d *Deck) WriteTextOnImage(keyIndex int, img image.Image, text, fontName st
font = streamdeck.MonoMedium font = streamdeck.MonoMedium
} }
barHeight := 20 if showLabelBackground {
if ks < 72 { barHeight := 20
barHeight = 18 if ks < 72 {
barHeight = 18
}
barRect := image.Rect(0, ks-barHeight, ks, ks)
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
} }
barRect := image.Rect(0, ks-barHeight, ks, ks)
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
offsetY := 24 offsetY := 24
baselineY := ks - 6 baselineY := ks - 6
@ -353,7 +356,7 @@ func (d *Deck) WriteTextOnImage(keyIndex int, img image.Image, text, fontName st
} }
charW := fontSize * 0.55 charW := fontSize * 0.55
textW := int(float64(len(text)) * charW) textW := int(float64(utf8.RuneCountInString(text)) * charW)
posX := (ks - textW) / 2 posX := (ks - textW) / 2
if posX < 2 { if posX < 2 {
posX = 2 posX = 2

View File

@ -115,11 +115,14 @@ func renderFAGlyph(style faStyle, name string, size int, scale float64) (image.I
rgba := image.NewRGBA(image.Rect(0, 0, size, size)) rgba := image.NewRGBA(image.Rect(0, 0, size, size))
offX := (size - int(fontSize)) / 2 adv := font.MeasureString(face, string(cp)).Ceil()
offX := (size - adv) / 2
if offX < 0 { if offX < 0 {
offX = size / 8 offX = 0
} }
baselineY := size * 7 / 10
metrics := face.Metrics()
baselineY := (size + metrics.Ascent.Ceil() - metrics.Descent.Ceil()) / 2
d := font.Drawer{ d := font.Drawer{
Dst: rgba, Dst: rgba,

View File

@ -153,12 +153,12 @@ func svgToPNG(svgPath string, targetSize int, scale float64) (image.Image, error
scale = 0.55 scale = 0.55
} }
renderSize := targetSize renderSize := int(float64(targetSize) * scale)
if scale < 1 { if renderSize > targetSize {
renderSize = int(float64(targetSize) * scale) renderSize = targetSize
if renderSize < 1 { }
renderSize = 1 if renderSize < 1 {
} renderSize = 1
} }
cmd := exec.Command("rsvg-convert", cmd := exec.Command("rsvg-convert",

View File

@ -11,15 +11,16 @@ import (
) )
type Config struct { type Config struct {
Version int `json:"version"` Version int `json:"version"`
LogLevel string `json:"log_level"` LogLevel string `json:"log_level"`
DefaultPage string `json:"default_page"` DefaultPage string `json:"default_page"`
Font string `json:"font,omitempty"` Font string `json:"font,omitempty"`
Devices []DeviceConfig `json:"devices"` ShowLabelBackground bool `json:"show_label_background"`
Pages []PageConfig `json:"pages"` Devices []DeviceConfig `json:"devices"`
AutoSwitch []SwitchRule `json:"auto_switch"` Pages []PageConfig `json:"pages"`
Screensaver ScreensaverCfg `json:"screensaver"` AutoSwitch []SwitchRule `json:"auto_switch"`
Timing TimingConfig `json:"timing,omitempty"` Screensaver ScreensaverCfg `json:"screensaver"`
Timing TimingConfig `json:"timing,omitempty"`
} }
type TimingConfig struct { type TimingConfig struct {
@ -113,9 +114,10 @@ type ScreensaverCfg struct {
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
Version: 1, Version: 1,
LogLevel: "info", LogLevel: "info",
DefaultPage: "default", DefaultPage: "default",
ShowLabelBackground: true,
Pages: []PageConfig{ Pages: []PageConfig{
{ {
Name: "default", Name: "default",

59
page.go
View File

@ -41,14 +41,15 @@ type DisplayOutput struct {
} }
type PageManager struct { type PageManager struct {
mu sync.RWMutex mu sync.RWMutex
pages map[string]*config.PageConfig pages map[string]*config.PageConfig
active string active string
deck *Deck deck *Deck
keyStates map[int]*keyState keyStates map[int]*keyState
displayOutputs map[int]*DisplayOutput displayOutputs map[int]*DisplayOutput
displayMu sync.RWMutex displayMu sync.RWMutex
defaultFont string defaultFont string
showLabelBackground bool
} }
func NewPageManager(deck *Deck) *PageManager { func NewPageManager(deck *Deck) *PageManager {
@ -221,7 +222,7 @@ func (pm *PageManager) renderKey(idx int, k *config.KeyConfig) {
} }
if k.Label != "" { if k.Label != "" {
if err := pm.deck.WriteTextOnImage(idx, img, k.Label, onImgFontName, onImgFontSize); err != nil { if err := pm.deck.WriteTextOnImage(idx, img, k.Label, onImgFontName, onImgFontSize, pm.showLabelBackground); err != nil {
slog.Warn("write text on image", "error", err) slog.Warn("write text on image", "error", err)
pm.deck.FillImage(idx, img) pm.deck.FillImage(idx, img)
} }
@ -237,7 +238,7 @@ func (pm *PageManager) renderKey(idx int, k *config.KeyConfig) {
ks := pm.deck.KeySize() ks := pm.deck.KeySize()
img := image.NewRGBA(image.Rect(0, 0, ks, ks)) img := image.NewRGBA(image.Rect(0, 0, ks, ks))
draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.Point{}, draw.Src) draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.Point{}, draw.Src)
if err := pm.deck.WriteTextOnImage(idx, img, k.Label, fontName, fontSize); err != nil { if err := pm.deck.WriteTextOnImage(idx, img, k.Label, fontName, fontSize, pm.showLabelBackground); err != nil {
slog.Warn("write text on image", "error", err) slog.Warn("write text on image", "error", err)
pm.deck.FillImage(idx, img) pm.deck.FillImage(idx, img)
} }
@ -469,7 +470,7 @@ func (pm *PageManager) renderKeyOutput(idx int, d *config.DisplayCfg, output str
} }
} }
composite := renderUnicodeTextOnImage(img, text, fontSize, pm.deck.KeySize(), fg) composite := renderUnicodeTextOnImage(img, text, fontSize, pm.deck.KeySize(), fg, pm.showLabelBackground)
if err := pm.deck.FillImage(idx, composite); err != nil { if err := pm.deck.FillImage(idx, composite); err != nil {
slog.Warn("fill image", "error", err) slog.Warn("fill image", "error", err)
} }
@ -667,7 +668,7 @@ func renderUnicodeText(text string, fontSize float64, keySize int, bg, fg color.
return rgba return rgba
} }
func renderUnicodeTextOnImage(baseImg image.Image, text string, fontSize float64, keySize int, fg color.Color) *image.RGBA { func renderUnicodeTextOnImage(baseImg image.Image, text string, fontSize float64, keySize int, fg color.Color, showLabelBackground bool) *image.RGBA {
g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling)) g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize)) rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
g.Draw(rgba, baseImg) g.Draw(rgba, baseImg)
@ -676,8 +677,10 @@ func renderUnicodeTextOnImage(baseImg image.Image, text string, fontSize float64
if keySize < 72 { if keySize < 72 {
barHeight = 16 barHeight = 16
} }
barRect := image.Rect(0, keySize-barHeight, keySize, keySize) if showLabelBackground {
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over) barRect := image.Rect(0, keySize-barHeight, keySize, keySize)
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
}
if text == "" || fg == nil { if text == "" || fg == nil {
return rgba return rgba
@ -872,6 +875,34 @@ func loadImage(path string, targetSize int, faScale float64) (image.Image, error
return nil, fmt.Errorf("decode: %w", err) return nil, fmt.Errorf("decode: %w", err)
} }
// For raster images, apply scaling and centering when targetSize > 0
if targetSize > 0 && faScale > 0 {
displaySize := int(float64(targetSize) * faScale)
if displaySize > targetSize {
displaySize = targetSize
}
if displaySize < 1 {
displaySize = 1
}
// Resize to displaySize
if img.Bounds().Dx() != displaySize || img.Bounds().Dy() != displaySize {
g := gift.New(gift.Resize(displaySize, displaySize, gift.LanczosResampling))
scaled := image.NewRGBA(image.Rect(0, 0, displaySize, displaySize))
g.Draw(scaled, img)
img = scaled
}
// Center on targetSize canvas
if displaySize < targetSize {
canvas := image.NewRGBA(image.Rect(0, 0, targetSize, targetSize))
offX := (targetSize - displaySize) / 2
offY := (targetSize - displaySize) / 2
draw.Draw(canvas, image.Rect(offX, offY, offX+displaySize, offY+displaySize), img, image.Point{}, draw.Over)
img = canvas
}
}
imageCache.Store(key, img) imageCache.Store(key, img)
return img, nil return img, nil
} }

View File

@ -13,7 +13,7 @@ import (
"streamdeck-lets-go/internal/config" "streamdeck-lets-go/internal/config"
) )
func RenderKeyToImage(k *config.KeyConfig, keySize int) image.Image { func RenderKeyToImage(k *config.KeyConfig, keySize int, showLabelBackground bool) image.Image {
if k == nil { if k == nil {
return blankImage(keySize, color.RGBA{0, 0, 0, 255}) return blankImage(keySize, color.RGBA{0, 0, 0, 255})
} }
@ -46,7 +46,7 @@ func RenderKeyToImage(k *config.KeyConfig, keySize int) image.Image {
if k.FontSize != nil { if k.FontSize != nil {
fontSize = *k.FontSize fontSize = *k.FontSize
} }
return composeImageWithLabel(img, k.Label, keySize, fontSize) return composeImageWithLabel(img, k.Label, keySize, fontSize, showLabelBackground)
} }
g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling)) g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize)) rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
@ -79,7 +79,7 @@ func blankImage(size int, c color.Color) image.Image {
return img return img
} }
func composeImageWithLabel(src image.Image, text string, keySize int, fontSize float64) image.Image { func composeImageWithLabel(src image.Image, text string, keySize int, fontSize float64, showLabelBackground bool) image.Image {
g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling)) g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize)) rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
g.Draw(rgba, src) g.Draw(rgba, src)
@ -89,7 +89,9 @@ func composeImageWithLabel(src image.Image, text string, keySize int, fontSize f
barHeight = 18 barHeight = 18
} }
barRect := image.Rect(0, keySize-barHeight, keySize, keySize) barRect := image.Rect(0, keySize-barHeight, keySize, keySize)
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over) if showLabelBackground {
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
}
if text != "" { if text != "" {
face, err := parseDisplayFace(fontSize) face, err := parseDisplayFace(fontSize)

View File

@ -119,6 +119,7 @@ document.addEventListener('alpine:init', () => {
this.settingsForm = { this.settingsForm = {
brightness: this.getDeviceConfig(this.activeDeckSerial)?.brightness ?? 75, brightness: this.getDeviceConfig(this.activeDeckSerial)?.brightness ?? 75,
font: this.config.font || 'medium', font: this.config.font || 'medium',
show_label_background: this.config.show_label_background ?? true,
screensaver_enabled: this.config.screensaver?.enabled || false, screensaver_enabled: this.config.screensaver?.enabled || false,
screensaver_idle: this.config.screensaver?.idle_seconds || 30, screensaver_idle: this.config.screensaver?.idle_seconds || 30,
screensaver_brightness: this.config.screensaver?.brightness || 10, screensaver_brightness: this.config.screensaver?.brightness || 10,
@ -598,6 +599,7 @@ document.addEventListener('alpine:init', () => {
} }
dev.brightness = parseInt(this.settingsForm.brightness) dev.brightness = parseInt(this.settingsForm.brightness)
this.config.font = this.settingsForm.font || 'medium' this.config.font = this.settingsForm.font || 'medium'
this.config.show_label_background = this.settingsForm.show_label_background
this.config.screensaver = { this.config.screensaver = {
enabled: this.settingsForm.screensaver_enabled, enabled: this.settingsForm.screensaver_enabled,
idle_seconds: parseInt(this.settingsForm.screensaver_idle) || 30, idle_seconds: parseInt(this.settingsForm.screensaver_idle) || 30,

View File

@ -122,6 +122,10 @@
<option value="regular">Regular</option> <option value="regular">Regular</option>
</select> </select>
</div> </div>
<div class="checkbox-row">
<input type="checkbox" id="show-label-bg" x-model="settingsForm.show_label_background">
<label for="show-label-bg">Show label background</label>
</div>
</div> </div>
<!-- Screensaver --> <!-- Screensaver -->

2
web.go
View File

@ -404,7 +404,7 @@ func (s *WebServer) handleRender(w http.ResponseWriter, r *http.Request) {
kc.Background = bg kc.Background = bg
} }
img := RenderKeyToImage(kc, keySize) img := RenderKeyToImage(kc, keySize, s.cfg.ShowLabelBackground)
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Cache-Control", "no-cache")