Skip to content

Instantly share code, notes, and snippets.

@59de44955ebd
Last active October 21, 2025 13:51
Show Gist options
  • Select an option

  • Save 59de44955ebd/203c43b5e02e15a7d955a237a231b32b to your computer and use it in GitHub Desktop.

Select an option

Save 59de44955ebd/203c43b5e02e15a7d955a237a231b32b to your computer and use it in GitHub Desktop.
A faster way to display ImageMagick/Wand images in Windows x64 without creating temporary files
__all__ = ["display"]
"""
A faster way to display ImageMagick/Wand images in Windows,
without creating temporary files.
Usage:
======
from wand.image import Image
import sys
if sys.platform == "win32":
from wand_win_display import display
else:
from wand.display import display
with Image(filename="lena.png") as img:
display(img)
"""
from ctypes import *
from ctypes.wintypes import *
import io
# Config
MIN_WIN_SIZE = 240
########################################
# Used Winapi structs
########################################
# https://learn.microsoft.com/sr-latn-rs/windows/win32/api/winuser/ns-winuser-paintstruct
class PAINTSTRUCT(Structure):
_fields_ = [
("hdc", HDC),
("fErase", BOOL),
("rcPaint", RECT),
("fRestore", BOOL),
("fIncUpdate", BOOL),
("rgbReserved", BYTE * 32),
]
LONG_PTR = c_longlong # for x64 only!
WNDPROC = WINFUNCTYPE(LONG_PTR, HWND, UINT, WPARAM, LPARAM)
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexw
class WNDCLASSEXW(Structure):
def __init__(self, *args, **kwargs):
super(WNDCLASSEXW, self).__init__(*args, **kwargs)
self.cbSize = sizeof(self)
_fields_ = [
("cbSize", UINT),
("style", UINT),
("lpfnWndProc", WNDPROC),
("cbClsExtra", INT),
("cbWndExtra", INT),
("hInstance", HANDLE),
("hIcon", HANDLE),
("hCursor", HANDLE),
("hBrush", HANDLE),
("lpszMenuName", LPCWSTR),
("lpszClassName", LPCWSTR),
("hIconSm", HANDLE)
]
# https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
class BITMAPINFOHEADER(Structure):
def __init__(self, *args, **kwargs):
super(BITMAPINFOHEADER, self).__init__(*args, **kwargs)
self.biSize = sizeof(self)
_fields_ = [
("biSize", DWORD),
("biWidth", LONG),
("biHeight", LONG),
("biPlanes", WORD),
("biBitCount", WORD),
("biCompression", DWORD),
("biSizeImage", DWORD),
("biXPelsPerMeter", LONG),
("biYPelsPerMeter", LONG),
("biClrUsed", DWORD),
("biClrImportant", DWORD)
]
LPBITMAPINFOHEADER = POINTER(BITMAPINFOHEADER)
# https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-minmaxinfo
class MINMAXINFO(Structure):
_fields_ = [
("ptReserved", POINT),
("ptMaxSize", POINT),
("ptMaxPosition", POINT),
("ptMinTrackSize", POINT),
("ptMaxTrackSize", POINT),
]
########################################
# Used Winapi functions
########################################
gdi32 = windll.Gdi32
gdi32.CreateCompatibleDC.argtypes = (HDC,)
gdi32.CreateCompatibleDC.restype = HDC
gdi32.CreateDIBSection.argtypes = (HDC, LPVOID, UINT, LPVOID, HANDLE, DWORD)
gdi32.CreateDIBSection.restype = HBITMAP
gdi32.DeleteDC.argtypes = (HDC,)
gdi32.DeleteObject.argtypes = (HANDLE,)
gdi32.GetStockObject.restype = HANDLE
gdi32.SelectObject.argtypes = (HDC, HANDLE)
gdi32.SelectObject.restype = HANDLE
gdi32.SetDIBits.argtypes = (HDC, HBITMAP, UINT, UINT, LPVOID, LPVOID, UINT)
gdi32.SetStretchBltMode.argtypes = (HDC, INT)
gdi32.StretchBlt.argtypes = (HDC, INT, INT, INT, INT, HDC, INT, INT, INT, INT, DWORD)
kernel32 = windll.Kernel32
kernel32.GetModuleHandleW.argtypes = (LPCWSTR,)
kernel32.GetModuleHandleW.restype = HINSTANCE
user32 = windll.user32
user32.BeginPaint.argtypes = (HWND, POINTER(PAINTSTRUCT))
user32.CreateWindowExW.argtypes = (DWORD, LPCWSTR, LPCWSTR, DWORD, INT, INT, INT, INT, HWND, HMENU, HINSTANCE, LPVOID)
user32.DefWindowProcW.argtypes = (HWND, UINT, WPARAM, LPARAM)
user32.DestroyWindow.argtypes = (HWND,)
user32.DispatchMessageW.argtypes = (POINTER(MSG),)
user32.EndPaint.argtypes = (HWND, POINTER(PAINTSTRUCT))
user32.GetMessageW.argtypes = (POINTER(MSG),HWND,UINT,UINT)
user32.LoadCursorW.argtypes = (HINSTANCE, LPVOID)
user32.LoadCursorW.restype = HANDLE
user32.LoadIconW.argtypes = (HINSTANCE, LPCWSTR)
user32.LoadIconW.restype = HICON
user32.PostMessageW.argtypes = (HWND, UINT, LPVOID, LPVOID)
user32.SystemParametersInfoA.argtypes = (UINT, UINT, LPVOID, UINT)
user32.TranslateMessage.argtypes = (POINTER(MSG),)
########################################
# Used Winapi constants
########################################
BI_RGB = 0
BLACK_BRUSH = 4
CS_HREDRAW = 2
CS_VREDRAW = 1
DIB_RGB_COLORS = 0
HALFTONE = 4
IDC_ARROW = 32512
SM_CYCAPTION = 4
SPI_GETWORKAREA = 48
SRCCOPY = 13369376
WM_CLOSE = 16
WM_GETMINMAXINFO = 36
WM_PAINT = 15
WM_QUIT = 18
WS_OVERLAPPEDWINDOW = 13565952
WS_VISIBLE = 268435456
def display(img, title = None):
""" Displays ImageMagick/Wand image in a native window, without creating any temporary file. """
def _image_to_hbitmap(img):
""" Convert ImageMagick image to HBITMAP """
img = img.clone()
img.format = "dib"
img.type = "truecolor" # Force RGB
f = io.BytesIO()
img.save(file=f)
data = f.getvalue()
bmih = cast(data, LPBITMAPINFOHEADER)
h_bitmap = gdi32.CreateDIBSection(None, bmih, DIB_RGB_COLORS, None, None, 0)
gdi32.SetDIBits(
None, h_bitmap,
0, img.height,
data[sizeof(BITMAPINFOHEADER):], # Skip the BITMAPINFOHEADER (40 bytes)
bmih,
DIB_RGB_COLORS
)
return h_bitmap
def _get_win_rect_for_image(im):
"""
Show window centered on screen and never bigger than
the actual work area (desktop minus taskbar)
"""
rc_desktop = RECT()
user32.SystemParametersInfoA(SPI_GETWORKAREA, 0, byref(rc_desktop), 0)
rc_desktop.right -= 32 # Windows 11 DWM fix
caption_height = user32.GetSystemMetrics(SM_CYCAPTION)
win_width, win_height = im.width, im.height + caption_height
if win_width > rc_desktop.right or win_height > rc_desktop.bottom:
desktop_ratio = rc_desktop.right / (rc_desktop.bottom - caption_height)
if desktop_ratio > im_ratio:
win_height = rc_desktop.bottom
win_width = round(win_height * desktop_ratio)
else:
win_width = rc_desktop.right
win_height = round(win_width / desktop_ratio)
x = (rc_desktop.right - win_width) // 2 + 16
y = (rc_desktop.bottom - win_height) // 2
return x, y, win_width, win_height
h_bitmap = _image_to_hbitmap(img)
img_ratio = img.width / img.height
def _on_WM_PAINT(hwnd, wparam, lparam):
""" Shows image centered and resized to window while keeping its aspect ratio. """
rc = RECT()
user32.GetClientRect(hwnd, byref(rc))
width, height = rc.right, rc.bottom
if width / height > img_ratio:
dest_width = round(height * img_ratio)
dest_height = height
x = (width - dest_width) // 2
y = 0
else:
dest_width = width
dest_height = round(width / img_ratio)
x = 0
y = (height - dest_height) // 2
ps = PAINTSTRUCT()
hdc = user32.BeginPaint(hwnd, byref(ps))
gdi32.SetStretchBltMode(hdc, HALFTONE)
hdc_mem = gdi32.CreateCompatibleDC(hdc)
gdi32.SelectObject(hdc_mem, h_bitmap)
gdi32.StretchBlt(
hdc, x, y, dest_width, dest_height, # dest
hdc_mem, 0, 0, img.width, img.height, # scr
SRCCOPY
)
gdi32.DeleteDC(hdc_mem)
user32.EndPaint(hwnd, byref(ps))
return 0
def _window_proc_callback(hwnd, msg, wparam, lparam):
if msg == WM_CLOSE:
user32.PostMessageW(hwnd, WM_QUIT, 0, 0)
elif msg == WM_GETMINMAXINFO:
mmi = cast(lparam, POINTER(MINMAXINFO))
mmi.contents.ptMinTrackSize = POINT(MIN_WIN_SIZE, MIN_WIN_SIZE)
return 0
elif msg == WM_PAINT:
return _on_WM_PAINT(hwnd, wparam, lparam)
return user32.DefWindowProcW(hwnd, msg, wparam, lparam)
wndclass = WNDCLASSEXW()
wndclass.lpfnWndProc = WNDPROC(_window_proc_callback)
wndclass.style = CS_VREDRAW | CS_HREDRAW
wndclass.lpszClassName = "WandWinDisplay"
wndclass.hBrush = gdi32.GetStockObject(BLACK_BRUSH)
wndclass.hCursor = user32.LoadCursorW(0, IDC_ARROW)
wndclass.hIcon = user32.LoadIconW(kernel32.GetModuleHandleW(None), LPCWSTR(1)) # Python icon
wndclass.hIconSm = wndclass.hIcon
user32.RegisterClassExW(byref(wndclass))
hwnd = user32.CreateWindowExW(
0,
wndclass.lpszClassName,
title or f"{img.format} - {img.type} - {img.width} x {img.height}",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
*_get_win_rect_for_image(img),
None, None, None, None
)
msg = MSG()
while user32.GetMessageW(byref(msg), 0, 0, 0) > 0:
user32.TranslateMessage(byref(msg))
user32.DispatchMessageW(byref(msg))
user32.DestroyWindow(hwnd)
gdi32.DeleteObject(h_bitmap)
@59de44955ebd
Copy link
Author

Usage in Windows 11
grafik

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment