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]
|
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 {
|
||||||
|
|||||||
@ -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": "",
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
7
deck.go
7
deck.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if showLabelBackground {
|
||||||
barHeight := 20
|
barHeight := 20
|
||||||
if ks < 72 {
|
if ks < 72 {
|
||||||
barHeight = 18
|
barHeight = 18
|
||||||
}
|
}
|
||||||
barRect := image.Rect(0, ks-barHeight, ks, ks)
|
barRect := image.Rect(0, ks-barHeight, ks, ks)
|
||||||
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
|
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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
8
icons.go
8
icons.go
@ -153,13 +153,13 @@ 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 {
|
if renderSize < 1 {
|
||||||
renderSize = 1
|
renderSize = 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("rsvg-convert",
|
cmd := exec.Command("rsvg-convert",
|
||||||
"-w", strconv.Itoa(renderSize),
|
"-w", strconv.Itoa(renderSize),
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type Config struct {
|
|||||||
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"`
|
||||||
|
ShowLabelBackground bool `json:"show_label_background"`
|
||||||
Devices []DeviceConfig `json:"devices"`
|
Devices []DeviceConfig `json:"devices"`
|
||||||
Pages []PageConfig `json:"pages"`
|
Pages []PageConfig `json:"pages"`
|
||||||
AutoSwitch []SwitchRule `json:"auto_switch"`
|
AutoSwitch []SwitchRule `json:"auto_switch"`
|
||||||
@ -116,6 +117,7 @@ func DefaultConfig() *Config {
|
|||||||
Version: 1,
|
Version: 1,
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
DefaultPage: "default",
|
DefaultPage: "default",
|
||||||
|
ShowLabelBackground: true,
|
||||||
Pages: []PageConfig{
|
Pages: []PageConfig{
|
||||||
{
|
{
|
||||||
Name: "default",
|
Name: "default",
|
||||||
|
|||||||
39
page.go
39
page.go
@ -49,6 +49,7 @@ type PageManager struct {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
if showLabelBackground {
|
||||||
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)
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
if showLabelBackground {
|
||||||
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
|
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)
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
2
web.go
@ -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")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user