-
Notifications
You must be signed in to change notification settings - Fork 28
Add Image to AutoHotkey GUI
Default AutoHotkey example using Gui.AddPicture:
Animations with ImagePut: [code + image]
Note: Supports WEBP and HEIC. Does not support animated AVIF.
Wow! What a difference.
Note: If you'd like to enable the ability to drag the image and tooltips, just remove the
WS_DISABLED := 0x08000000
window style in the code below.
This example shows the inner workings of how ImagePut can take over an AutoHotkey GUI control.
#Requires AutoHotkey v2.0
#include ImagePut.ahk
image := FileSelect(,, "Select an image:", "Images (*.bmp; *.dib; *.rle; *.jpg; *.jpeg; *.jpe; *.jfif; *.gif; *.emf; *.wmf; *.tif; *.tiff; *.png; *.ico; *.heic; *.hif; *.webp; *avif; *avifs)")
; Make the GUI resizable and not affected by DPI scaling.
app := Gui("-DPIScale +Resize")
; Sets the image filepath as the window title.
app.Title := image
; Create a dummy text control to repurpose for ImagePut's functionality.
display := app.Add("Text", "xm+0")
; Must resize the viewable area of the control.
display.move(,,ImageWidth(image), ImageHeight(image))
; Use ImagePut to create a child window, and set the parent as the text control.
image_hwnd := ImageShow(image,, [0, 0], 0x40000000 | 0x10000000 | 0x8000000,, display.hwnd)
; Show the image
app.Show("xCenter y0 AutoSize")
Looking closely at ImageShow we see that:
image_hwnd := ImageShow(
image, ; The input image of any type
"", ; Window Title can never be seen
[0, 0], ; Sets the [x, y, w, h, r] coordinate to top-left.
0x40000000 | 0x10000000, ; WS_CHILD and WS_VISIBLE (Note: Add WS_DISABLED := 0x8000000)
0x80000, ; WS_EX_LAYERED
display.hwnd, ; Sets the GuiControl as the parent
True) ; Start Animation?
First, the x
, y
coordinates must be set to (0, 0) to position at the top-left of the GuiControl. Forgetting to do this would default to the middle of the screen, which would lead to a large offset like (960, 540) on a 1920 × 1080 monitor.
An additional desired width or height can be set in w
and h
. You may choose to omit either the width or height to preserve the existing aspect ratio. E.g. [0,0,100,100]
or [0,0,"auto",100]
(Specifying auto
is optional.)
If a r
parameter is specified, the x
and y
coordinates will be offset relative to that window. For example, [30, 30, 100, 100, "ahk_class notepad"]
would show the window 30 pixels inside the notepad window.
The window style WS_CHILD
signals that this is a child window and that there will be a parent parameter passed through. Attempting to pass a parent parameter without setting WS_CHILD
is probably an error. See Raymond Chen's blog post for an explanation on parent vs owner.
Setting WS_DISABLED := 0x08000000
will disable Left Click to drag and Middle Click for tooltip and Right Click to delete.
The WS_EX_LAYERED
value must be set or ImagePut will forcibly set this value for you. Otherwise the call to UpdateLayeredWindow
wouldn't work!
Now we have all the pieces in place for making a better Gui.AddPicture()
function.
AddImageToControl(image, GuiControl) {
return ImageShow(image,, [0, 0], 0x40000000 | 0x10000000,, GuiControl.hwnd, True)
}
Can we make it so that the dummy text box is created as well?
#Requires AutoHotkey v2.0
#include ImagePut.ahk
image := FileSelect(,, "Select an image:", "Images (*.bmp; *.dib; *.rle; *.jpg; *.jpeg; *.jpe; *.jfif; *.gif; *.emf; *.wmf; *.tif; *.tiff; *.png; *.ico; *.heic; *.hif; *.webp; *avif; *avifs)")
; Make the GUI resizable and not affected by DPI scaling.
; Sets the image filepath as the window title.
app := Gui("-DPIScale +Resize", image)
; Uses ImagePut to load an image into the dummy text control.
AddImageToGui(app, image, "xm+0")
app.Show("xCenter y0 AutoSize")
; User Interaction with the image is disabled by default.
AddImageToGui(gui, image, options, text:="") {
static WS_CHILD := 0x40000000 ; Creates a child window.
static WS_VISIBLE := 0x10000000 ; Show on creation.
static WS_DISABLED := 0x8000000 ; Disables Left Click to drag.
ImagePut.gdiplusStartup()
pBitmap := ImagePutBitmap(image)
DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", &width:=0)
DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", &height:=0)
display := Gui.Add("Text", options " w" width " h" height, text)
display.imagehwnd := ImagePut.show(pBitmap,, [0, 0], WS_CHILD | WS_VISIBLE | WS_DISABLED,, display.hwnd)
ImagePut.gdiplusShutdown()
return display
}
Now the above version is perfect. It uses ImagePut's internal functions to load GDI+. This makes raw pointers to bitmaps pBitmap
valid within the context of this function. Unlike Example #1, which loads the image 3 times for ImageWidth
, ImageHeight
, and ImageShow
, this function converts the image only once.
Can we do better?
#Requires AutoHotkey v2.0
#include ImagePut.ahk
class Gui2 extends Gui {
AddImage(image, options, text:="") {
static WS_CHILD := 0x40000000 ; Creates a child window.
static WS_VISIBLE := 0x10000000 ; Show on creation.
static WS_DISABLED := 0x8000000 ; Disables Left Click to drag.
ImagePut.gdiplusStartup()
pBitmap := ImagePutBitmap(image)
DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", &width:=0)
DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", &height:=0)
display := this.Add("Text", options " w" width " h" height, text)
display.imagehwnd := ImagePut.show(pBitmap,, [0, 0], WS_CHILD | WS_VISIBLE | WS_DISABLED,, display.hwnd)
ImagePut.gdiplusShutdown()
return display
}
}
image := FileSelect(,, "Select an image:", "Images (*.bmp; *.dib; *.rle; *.jpg; *.jpeg; *.jpe; *.jfif; *.gif; *.emf; *.wmf; *.tif; *.tiff; *.png; *.ico; *.heic; *.hif; *.webp; *avif; *avifs)")
; Make the GUI resizable and not affected by DPI scaling.
; Sets the image filepath as the window title.
app := Gui2("-DPIScale +Resize", image)
; Uses ImagePut to load an image into the dummy text control.
app.AddImage(image, "xm+0")
app.Show("xCenter y0 AutoSize")
Creates an extended version of the Gui class. The only thing to remember is to use Gui2
instead of Gui
for the method AddImage
.
Image: https://github.com/iseahound/ImagePut/assets/9779668/aee6c301-44f1-4fad-8afb-fffef4f90df8
The reason why 3 separate methods are shown is because there is not a single ideal method of extending the native AutoHotkey GUI. Instead, you may choose to use any of the above 3 methods according to your own desires.
Finally, here is the code for the dancing GIF above:
#Requires AutoHotkey v2.0
#include ImagePut.ahk
MyGui := Gui("-DPIScale +Resize")
MyBtn := MyGui.Add("Button", "default", "&Load New Image")
MyBtn.OnEvent("Click", LoadNewImage)
MyRadio := MyGui.Add("Radio", "ym+5 x+10 checked", "Load &actual size")
MyGui.Add("Radio", "ym+5 x+10", "Load to &fit screen")
MyPic := MyGui.Add("Text", "xm")
MyGui.Show()
LoadNewImage(*)
{
Image := FileSelect(,, "Select an image:", "Images (*.bmp; *.dib; *.rle; *.jpg; *.jpeg; *.jpe; *.jfif; *.gif; *.emf; *.wmf; *.tif; *.tiff; *.png; *.ico; *.heic; *.hif; *.webp; *avif; *avifs)")
if Image = ""
return
if (MyRadio.Value) ; Display image at its actual size.
{
Width := 0
Height := 0
}
else ; Second radio is selected: Resize the image to fit the screen.
{
Width := A_ScreenWidth - 28 ; Minus 28 to allow room for borders and margins inside.
Height := -1 ; "Keep aspect ratio" seems best.
}
static imagehwnd := 0
if imagehwnd
ImageDestroy(imagehwnd)
MyGui.Title := Image
imagehwnd := ImageShow(Image,, [0, 0, Width, Height], 0x58000000,, MyPic.hwnd)
MyPic.move(,, ImageWidth(imagehwnd), ImageHeight(imagehwnd))
MyGui.Show("xCenter y0 AutoSize") ; Resize the window to match the picture size.
}
To synchronize animations, set the last parameter of ImageShow
to False. Then, send message 0x8001
to all the frozen image handles.
#Requires AutoHotkey v2.0
#include ImagePut.ahk
image := FileSelect(,, "Select an image:", "Images (*.bmp; *.dib; *.rle; *.jpg; *.jpeg; *.jpe; *.jfif; *.gif; *.emf; *.wmf; *.tif; *.tiff; *.png; *.ico; *.heic; *.hif; *.webp; *avif; *avifs)")
; Make the GUI resizable and not affected by DPI scaling.
app := Gui("-DPIScale +Resize")
; Sets the image filepath as the window title.
app.Title := image
; Create a dummy text control to repurpose for ImagePut's functionality.
display := app.Add("Text", "xm+0")
; Get the width and height.
width := ImageWidth(image)
height := ImageHeight(image)
; Must resize the viewable area of the control.
display.move(,, 3*width, height) ; Note: Extend viewable width by 3!!!
; Use ImagePut to create a child window, and set the parent as the text control.
image_hwnd1 := ImageShow(image,, [0, 0], 0x40000000 | 0x10000000 | 0x8000000,, display.hwnd, False)
image_hwnd2 := ImageShow(image,, [width, 0], 0x40000000 | 0x10000000 | 0x8000000,, display.hwnd, False)
image_hwnd3 := ImageShow(image,, [2*width, 0], 0x40000000 | 0x10000000 | 0x8000000,, display.hwnd, False)
; Show the image
app.Show("xCenter y0 AutoSize")
; Some useful functions.
Play(hwnd) => PostMessage(0x8001,,,, hwnd)
Restart(hwnd) => PostMessage(0x8001, 1,,, hwnd)
Pause(hwnd) => PostMessage(0x8002,,,, hwnd)
Stop(hwnd) => PostMessage(0x8002, 1,,, hwnd)
PlayPause(hwnd) => PostMessage(0x202,,,, hwnd)
RestartStop(hwnd) => PostMessage(0x202, 1,,, hwnd)
IsPlaying(hwnd) => DllCall("GetWindowLong", "ptr", hwnd, "int", 4*A_PtrSize, "ptr")
Step(hwnd, n) => PostMessage(0x8000, n,,, hwnd)
NextFrame(hwnd) => Step(hwnd, 1)
PrevFrame(hwnd) => Step(hwnd, -1)
; Use PostMessage to asynchronously start playback!
Play("ahk_id" image_hwnd1)
Play("ahk_id" image_hwnd2)
Play("ahk_id" image_hwnd3)
; Press F1 to Play, F2 to Pause, and F3 to Stop.
F1:: Play("ahk_id" image_hwnd1)
F2:: Pause("ahk_id" image_hwnd1)
F3:: Stop("ahk_id" image_hwnd1)
F4:: PrevFrame("ahk_id" image_hwnd1)
F5:: NextFrame("ahk_id" image_hwnd1)