Compare commits
3 Commits
af7774c14f
...
56a5cb5bba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56a5cb5bba | ||
|
|
cdb30bec3f | ||
|
|
95e96e7514 |
5
cbdt.go
5
cbdt.go
@ -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 {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"log_level": "info",
|
||||
"default_page": "main",
|
||||
"font": "medium",
|
||||
"show_label_background": true,
|
||||
"devices": [
|
||||
{
|
||||
"serial": "",
|
||||
|
||||
@ -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
17
deck.go
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
12
icons.go
12
icons.go
@ -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",
|
||||
|
||||
@ -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
59
page.go
@ -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
|
||||
}
|
||||
|
||||
10
render.go
10
render.go
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
2
web.go
@ -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")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user