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]
displaySize := int(float64(targetSize) * scale)
if displaySize < 1 {
if displaySize > targetSize {
displaySize = targetSize
}
if displaySize < 1 {
displaySize = 1
}
scaled := raw
if raw.Bounds().Dx() != displaySize || raw.Bounds().Dy() != displaySize {

View File

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

View File

@ -57,6 +57,7 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
for i, d := range decks {
pageMgrs[i] = NewPageManager(d)
pageMgrs[i].defaultFont = cfg.Font
pageMgrs[i].showLabelBackground = cfg.ShowLabelBackground
pageMgrs[i].LoadPages(cfg.Pages)
}
primaryPM := pageMgrs[0]
@ -230,6 +231,7 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
activePages = append(activePages, pm.ActivePageName())
pm.stopPeriodicKeys()
pm.defaultFont = cfg.Font
pm.showLabelBackground = cfg.ShowLabelBackground
pm.LoadPages(cfg.Pages)
}
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 {
*pm = NewPageManager(newDeck)
(*pm).defaultFont = cfg.Font
(*pm).showLabelBackground = cfg.ShowLabelBackground
(*pm).LoadPages(cfg.Pages)
(*pm).ActivatePage(cfg.DefaultPage)
(*pm).startPeriodicKeys()

17
deck.go
View File

@ -8,6 +8,7 @@ import (
"log/slog"
"sync"
"time"
"unicode/utf8"
"github.com/bearsh/hid"
"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
g := gift.New(
@ -338,12 +339,14 @@ func (d *Deck) WriteTextOnImage(keyIndex int, img image.Image, text, fontName st
font = streamdeck.MonoMedium
}
barHeight := 20
if ks < 72 {
barHeight = 18
if showLabelBackground {
barHeight := 20
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
baselineY := ks - 6
@ -353,7 +356,7 @@ func (d *Deck) WriteTextOnImage(keyIndex int, img image.Image, text, fontName st
}
charW := fontSize * 0.55
textW := int(float64(len(text)) * charW)
textW := int(float64(utf8.RuneCountInString(text)) * charW)
posX := (ks - textW) / 2
if 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))
offX := (size - int(fontSize)) / 2
adv := font.MeasureString(face, string(cp)).Ceil()
offX := (size - adv) / 2
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{
Dst: rgba,

View File

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

View File

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

59
page.go
View File

@ -41,14 +41,15 @@ type DisplayOutput struct {
}
type PageManager struct {
mu sync.RWMutex
pages map[string]*config.PageConfig
active string
deck *Deck
keyStates map[int]*keyState
displayOutputs map[int]*DisplayOutput
displayMu sync.RWMutex
defaultFont string
mu sync.RWMutex
pages map[string]*config.PageConfig
active string
deck *Deck
keyStates map[int]*keyState
displayOutputs map[int]*DisplayOutput
displayMu sync.RWMutex
defaultFont string
showLabelBackground bool
}
func NewPageManager(deck *Deck) *PageManager {
@ -221,7 +222,7 @@ func (pm *PageManager) renderKey(idx int, k *config.KeyConfig) {
}
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)
pm.deck.FillImage(idx, img)
}
@ -237,7 +238,7 @@ func (pm *PageManager) renderKey(idx int, k *config.KeyConfig) {
ks := pm.deck.KeySize()
img := image.NewRGBA(image.Rect(0, 0, ks, ks))
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)
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 {
slog.Warn("fill image", "error", err)
}
@ -667,7 +668,7 @@ func renderUnicodeText(text string, fontSize float64, keySize int, bg, fg color.
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))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
g.Draw(rgba, baseImg)
@ -676,8 +677,10 @@ func renderUnicodeTextOnImage(baseImg image.Image, text string, fontSize float64
if keySize < 72 {
barHeight = 16
}
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 {
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 {
return rgba
@ -872,6 +875,34 @@ func loadImage(path string, targetSize int, faScale float64) (image.Image, error
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)
return img, nil
}

View File

@ -13,7 +13,7 @@ import (
"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 {
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 {
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))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
@ -79,7 +79,7 @@ func blankImage(size int, c color.Color) image.Image {
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))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
g.Draw(rgba, src)
@ -89,7 +89,9 @@ func composeImageWithLabel(src image.Image, text string, keySize int, fontSize f
barHeight = 18
}
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 != "" {
face, err := parseDisplayFace(fontSize)

View File

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

View File

@ -122,6 +122,10 @@
<option value="regular">Regular</option>
</select>
</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>
<!-- Screensaver -->

2
web.go
View File

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