11; LibreDeckButtonImage.ahk
22; AutoHotkey v1 library for generating configurable PNG button images.
33; Self-contained: uses Windows GDI+ directly, no Gdip_All.ahk required.
4- ; Version: 0.1.1
4+ ; Version: 0.1.3
55
66; -----------------------------------------------------------------------------
77; Public API
@@ -40,6 +40,7 @@ LD_ButtonImage_Render(outputPath, cfg := "") {
4040 valueColor := LD_Color(LD_Cfg(cfg, " valueColor" , style.valueColor))
4141 borderColor := LD_Color(LD_Cfg(cfg, " borderColor" , style.borderColor))
4242 shadowColor := LD_Color(LD_Cfg(cfg, " shadowColor" , style.shadowColor))
43+ transparentOutside := LD_Cfg(cfg, " transparentOutside" , style.transparentOutside)
4344
4445 font := LD_Cfg(cfg, " font" , " Segoe UI" )
4546 titleSize := LD_Cfg(cfg, " titleSize" , style.titleSize)
@@ -57,6 +58,13 @@ LD_ButtonImage_Render(outputPath, cfg := "") {
5758 value := LD_Cfg(cfg, " value" , "" )
5859 subtitle := LD_Cfg(cfg, " subtitle" , "" )
5960 iconPath := LD_Cfg(cfg, " iconPath" , "" )
61+ imagePath := LD_Cfg(cfg, " imagePath" , LD_Cfg(cfg, " backgroundImagePath" , "" ))
62+ imageFit := StrLower(Trim (LD_Cfg(cfg, " imageFit" , style.imageFit))) ; cover | contain | stretch
63+ imageOpacity := LD_Cfg(cfg, " imageOpacity" , style.imageOpacity)
64+ overlayColor := LD_Color(LD_Cfg(cfg, " overlayColor" , style.overlayColor))
65+ textShadowColor := LD_Color(LD_Cfg(cfg, " textShadowColor" , style.textShadowColor))
66+ textBoxColor := LD_Color(LD_Cfg(cfg, " textBoxColor" , style.textBoxColor))
67+ textBoxPadding := LD_Cfg(cfg, " textBoxPadding" , style.textBoxPadding)
6068 progress := LD_Cfg(cfg, " progress" , "" )
6169
6270 pBitmap := LD_Gdip_CreateBitmap(width, height)
@@ -66,14 +74,32 @@ LD_ButtonImage_Render(outputPath, cfg := "") {
6674 G := LD_Gdip_GraphicsFromImage(pBitmap)
6775 LD_Gdip_SetSmoothingMode(G, 4 )
6876 LD_Gdip_SetInterpolationMode(G, 7 )
69- LD_Gdip_GraphicsClear(G, bgColor)
7077
71- if (styleName = " glass" )
72- LD_DrawGlassLayer(G, width, height)
78+ contentX := 4
79+ contentY := 4
80+ contentW := width - 8
81+ contentH := height - 8
82+
83+ ; If transparentOutside is enabled, the PNG canvas outside the rounded
84+ ; button remains transparent instead of being filled with bgColor.
85+ LD_Gdip_GraphicsClear(G, transparentOutside ? 0x00000000 : bgColor)
7386
7487 if (shadowColor)
7588 LD_FillRoundedRect(G, 7 , 8 , width - 14 , height - 14 , radius, shadowColor)
7689
90+ ; Paint the actual button background only inside the rounded rectangle.
91+ LD_FillRoundedRect(G, contentX, contentY, contentW, contentH, radius, bgColor)
92+
93+ if (imagePath ! = "" && FileExist (imagePath)) {
94+ LD_Gdip_SetClipRoundedRect(G, contentX, contentY, contentW, contentH, radius)
95+ LD_DrawImageFileFit(G, imagePath, contentX, contentY, contentW, contentH, imageFit, imageOpacity)
96+ LD_Gdip_ResetClip(G)
97+ if (overlayColor)
98+ LD_FillRoundedRect(G, contentX, contentY, contentW, contentH, radius, overlayColor)
99+ } else if (styleName = " glass" ) {
100+ LD_DrawGlassLayer(G, width, height)
101+ }
102+
77103 if (border > 0 )
78104 LD_DrawRoundedRect(G, borderColor, border , 4 , 4 , width - 8 , height - 8 , radius)
79105
@@ -88,14 +114,29 @@ LD_ButtonImage_Render(outputPath, cfg := "") {
88114 LD_DrawImageFile(G, iconPath, iconX, iconY, iconW, iconH)
89115 }
90116
91- if (title ! = "" )
117+ if (textBoxColor) {
118+ boxY := LD_Cfg(cfg, " textBoxY" , titleY - textBoxPadding)
119+ boxH := LD_Cfg(cfg, " textBoxH" , height - boxY - 12 )
120+ LD_FillRoundedRect(G, 10 , boxY, width - 20 , boxH, LD_Min(radius, 10 ), textBoxColor)
121+ }
122+
123+ if (title ! = "" ) {
124+ if (textShadowColor)
125+ LD_DrawText(G, title, font , titleSize, textShadowColor, 9 , titleY + 2 , width - 16 , 26 , 1 , 1 , 1 )
92126 LD_DrawText(G, title, font , titleSize, textColor, 8 , titleY, width - 16 , 26 , 1 , 1 , 1 )
127+ }
93128
94- if (value ! = "" )
129+ if (value ! = "" ) {
130+ if (textShadowColor)
131+ LD_DrawText(G, value, font , valueSize, textShadowColor, 5 , valueY + 2 , (valueX ? valueX : width - 8 ), 54 , 1 , 1 , 1 )
95132 LD_DrawText(G, value, font , valueSize, valueColor, 4 , valueY, (valueX ? valueX : width - 8 ), 54 , 1 , 1 , 1 )
133+ }
96134
97- if (subtitle ! = "" )
135+ if (subtitle ! = "" ) {
136+ if (textShadowColor)
137+ LD_DrawText(G, subtitle, font , subtitleSize, textShadowColor, 9 , subtitleY + 2 , width - 16 , 22 , 1 , 1 , 0 )
98138 LD_DrawText(G, subtitle, font , subtitleSize, textColor, 8 , subtitleY, width - 16 , 22 , 1 , 1 , 0 )
139+ }
99140
100141 if (progress ! = "" )
101142 LD_DrawProgress(G, progress , 14 , height - 17 , width - 28 , 6 , accentColor, 0x55333333 )
@@ -120,6 +161,16 @@ LD_ButtonImage_RenderInventorySlot(outputPath, itemName, amount := "", iconPath
120161 return LD_ButtonImage_Render(outputPath, cfg)
121162}
122163
164+ LD_ButtonImage_RenderImageButton (outputPath, imagePath, title := "", subtitle := "", cfg := "" ) {
165+ if (! IsObject (cfg))
166+ cfg := {}
167+ cfg.style := LD_Cfg(cfg, " style" , " image" )
168+ cfg.imagePath := imagePath
169+ cfg.title := title
170+ cfg.subtitle := subtitle
171+ return LD_ButtonImage_Render(outputPath, cfg)
172+ }
173+
123174; -----------------------------------------------------------------------------
124175; Styles
125176; -----------------------------------------------------------------------------
@@ -145,6 +196,13 @@ LD_ButtonImage_GetStyle(name) {
145196 s.iconH := 76
146197 s.iconY := 28
147198 s.accentStrip := true
199+ s.imageFit := " cover"
200+ s.imageOpacity := 100
201+ s.overlayColor := " 0x00000000"
202+ s.textShadowColor := " 0x00000000"
203+ s.textBoxColor := " 0x00000000"
204+ s.textBoxPadding := 8
205+ s.transparentOutside := true
148206
149207 if (name = " minimal" ) {
150208 s.bgColor := " 0xFF202020"
@@ -198,6 +256,31 @@ LD_ButtonImage_GetStyle(name) {
198256 s.titleSize := 15
199257 s.valueSize := 30
200258 s.accentStrip := true
259+ } else if (name = " image" ) {
260+ s.bgColor := " 0xFF000000"
261+ s.accentColor := " 0xFFFFFFFF"
262+ s.textColor := " 0xFFFFFFFF"
263+ s.valueColor := " 0xFFFFFFFF"
264+ s.borderColor := " 0x99FFFFFF"
265+ s.shadowColor := " 0x77000000"
266+ s.titleSize := 18
267+ s.valueSize := 28
268+ s.subtitleSize := 12
269+ s.border := 2
270+ s.radius := 14
271+ s.titleY := 84
272+ s.valueY := 54
273+ s.iconW := 0
274+ s.iconH := 0
275+ s.iconY := 0
276+ s.accentStrip := false
277+ s.imageFit := " cover"
278+ s.imageOpacity := 100
279+ s.overlayColor := " 0x33000000"
280+ s.textShadowColor := " 0xCC000000"
281+ s.textBoxColor := " 0x66000000"
282+ s.textBoxPadding := 8
283+ s.transparentOutside := true
201284 } else if (name = " dark" ) {
202285 s.bgColor := " 0xFF050505"
203286 s.accentColor := " 0xFF666666"
@@ -242,6 +325,40 @@ LD_DrawImageFile(G, path, x, y, w, h) {
242325 return true
243326}
244327
328+ LD_DrawImageFileFit (G, path, x, y, w, h, fit := "cover", opacity := 100 ) {
329+ pImg := LD_Gdip_CreateBitmapFromFile(path)
330+ if (! pImg)
331+ return false
332+
333+ imgW := LD_Gdip_GetImageWidth(pImg)
334+ imgH := LD_Gdip_GetImageHeight(pImg)
335+ if (imgW <= 0 || imgH <= 0 ) {
336+ LD_Gdip_DisposeImage(pImg)
337+ return false
338+ }
339+
340+ fit := StrLower(Trim (fit))
341+ if (fit = " stretch" ) {
342+ dx := x, dy := y, dw := w, dh := h
343+ } else {
344+ scaleX := w / imgW
345+ scaleY := h / imgH
346+ scale := (fit = " contain" ) ? LD_Min(scaleX, scaleY) : LD_Max(scaleX, scaleY)
347+ dw := Round (imgW * scale)
348+ dh := Round (imgH * scale)
349+ dx := x + Round ((w - dw) / 2 )
350+ dy := y + Round ((h - dh) / 2 )
351+ }
352+
353+ if (opacity >= 100 )
354+ LD_Gdip_DrawImage(G, pImg, dx, dy, dw, dh)
355+ else
356+ LD_Gdip_DrawImageOpacity(G, pImg, dx, dy, dw, dh, opacity)
357+
358+ LD_Gdip_DisposeImage(pImg)
359+ return true
360+ }
361+
245362LD_DrawText (G, text, fontName, size, color, x, y, w, h, align := 1, vAlign := 1, bold := 0 ) {
246363 pBrush := LD_Gdip_BrushCreateSolid(color )
247364 pFamily := LD_Gdip_FontFamilyCreate(fontName)
@@ -287,6 +404,14 @@ LD_Gdip_CreateRoundedRectPath(x, y, w, h, r) {
287404 return pPath
288405}
289406
407+ LD_Min (a, b ) {
408+ return (a < b) ? a : b
409+ }
410+
411+ LD_Max (a, b ) {
412+ return (a > b) ? a : b
413+ }
414+
290415; -----------------------------------------------------------------------------
291416; Config helpers
292417; -----------------------------------------------------------------------------
@@ -410,6 +535,56 @@ LD_Gdip_DrawImage(G, pBitmap, x, y, w, h) {
410535 return DllCall (" gdiplus\GdipDrawImageRectI" , " UPtr" , G, " UPtr" , pBitmap, " Int" , x, " Int" , y, " Int" , w, " Int" , h)
411536}
412537
538+ LD_Gdip_DrawImageOpacity (G, pBitmap, x, y, w, h, opacity := 100 ) {
539+ if (opacity < 0 )
540+ opacity := 0
541+ if (opacity > 100 )
542+ opacity := 100
543+ alpha := opacity / 100.0
544+
545+ VarSetCapacity (matrix, 100 , 0 )
546+ NumPut (1.0 , matrix, 0 , " Float" )
547+ NumPut (1.0 , matrix, 24 , " Float" )
548+ NumPut (1.0 , matrix, 48 , " Float" )
549+ NumPut (alpha , matrix, 72 , " Float" )
550+ NumPut (1.0 , matrix, 96 , " Float" )
551+
552+ imgAttr := 0
553+ DllCall (" gdiplus\GdipCreateImageAttributes" , " UPtrP" , imgAttr)
554+ DllCall (" gdiplus\GdipSetImageAttributesColorMatrix" , " UPtr" , imgAttr, " Int" , 1 , " Int" , 1 , " UPtr" , & matrix, " UPtr" , 0 , " Int" , 0 )
555+
556+ DllCall (" gdiplus\GdipDrawImageRectRectI"
557+ , " UPtr" , G, " UPtr" , pBitmap
558+ , " Int" , x, " Int" , y, " Int" , w, " Int" , h
559+ , " Int" , 0 , " Int" , 0 , " Int" , LD_Gdip_GetImageWidth(pBitmap), " Int" , LD_Gdip_GetImageHeight(pBitmap)
560+ , " Int" , 2 , " UPtr" , imgAttr, " UPtr" , 0 , " UPtr" , 0 )
561+
562+ return DllCall (" gdiplus\GdipDisposeImageAttributes" , " UPtr" , imgAttr)
563+ }
564+
565+ LD_Gdip_GetImageWidth (pBitmap ) {
566+ w := 0
567+ DllCall (" gdiplus\GdipGetImageWidth" , " UPtr" , pBitmap, " UIntP" , w)
568+ return w
569+ }
570+
571+ LD_Gdip_GetImageHeight (pBitmap ) {
572+ h := 0
573+ DllCall (" gdiplus\GdipGetImageHeight" , " UPtr" , pBitmap, " UIntP" , h)
574+ return h
575+ }
576+
577+ LD_Gdip_SetClipRoundedRect (G, x, y, w, h, r ) {
578+ pPath := LD_Gdip_CreateRoundedRectPath(x, y, w, h, r)
579+ result := DllCall (" gdiplus\GdipSetClipPath" , " UPtr" , G, " UPtr" , pPath, " Int" , 0 )
580+ LD_Gdip_DeletePath(pPath)
581+ return result
582+ }
583+
584+ LD_Gdip_ResetClip (G ) {
585+ return DllCall (" gdiplus\GdipResetClip" , " UPtr" , G)
586+ }
587+
413588LD_Gdip_SaveBitmapToFile (pBitmap, path ) {
414589 VarSetCapacity (clsid, 16 , 0 )
415590 ; PNG encoder CLSID: {557CF406-1A04-11D3-9A73-0000F81EF32E}
0 commit comments