如何用ctypes将HBITMAP句柄转换为PIL或其他格式图像?
将HBITMAP转换为PIL图像
你拿到的整数是Windows的HBITMAP句柄,要转换成PIL图像,需要调用Windows GDI API提取位图的像素数据,再转成PIL支持的格式。以下是完整实现代码:
import ctypes from ctypes import wintypes from PIL import Image # 定义Windows API所需的结构体 class BITMAP(ctypes.Structure): _fields_ = [ ("bmType", wintypes.LONG), ("bmWidth", wintypes.LONG), ("bmHeight", wintypes.LONG), ("bmWidthBytes", wintypes.LONG), ("bmPlanes", wintypes.WORD), ("bmBitsPixel", wintypes.WORD), ("bmBits", wintypes.LPVOID) ] class BITMAPINFOHEADER(ctypes.Structure): _fields_ = [ ("biSize", wintypes.DWORD), ("biWidth", wintypes.LONG), ("biHeight", wintypes.LONG), ("biPlanes", wintypes.WORD), ("biBitCount", wintypes.WORD), ("biCompression", wintypes.DWORD), ("biSizeImage", wintypes.DWORD), ("biXPelsPerMeter", wintypes.LONG), ("biYPelsPerMeter", wintypes.LONG), ("biClrUsed", wintypes.DWORD), ("biClrImportant", wintypes.DWORD) ] class BITMAPINFO(ctypes.Structure): _fields_ = [ ("bmiHeader", BITMAPINFOHEADER), ("bmiColors", wintypes.DWORD * 3) # 24位真彩色无需调色板,此处占位 ] # 加载Windows系统库 gdi32 = ctypes.WinDLL("gdi32") user32 = ctypes.WinDLL("user32") # 声明函数参数与返回值类型 gdi32.GetObjectW.argtypes = [wintypes.HBITMAP, wintypes.INT, ctypes.LPVOID] gdi32.GetObjectW.restype = wintypes.INT gdi32.GetDIBits.argtypes = [ wintypes.HDC, wintypes.HBITMAP, wintypes.UINT, wintypes.UINT, ctypes.LPVOID, ctypes.LPVOID, wintypes.UINT ] gdi32.GetDIBits.restype = wintypes.INT gdi32.DeleteObject.argtypes = [wintypes.HGDIOBJ] gdi32.DeleteObject.restype = wintypes.BOOL user32.GetDC.argtypes = [wintypes.HWND] user32.GetDC.restype = wintypes.HDC user32.ReleaseDC.argtypes = [wintypes.HWND, wintypes.HDC] user32.ReleaseDC.restype = wintypes.INT # 加载目标DLL并调用接口 dllPath = "你的DLL路径" # 替换为实际DLL路径 dll = ctypes.windll.LoadLibrary(dllPath) dll.lmc1_GetPrevBitmap2.restype = wintypes.HBITMAP hbitmap = dll.lmc1_GetPrevBitmap2(400, 400) if not hbitmap: raise RuntimeError("获取位图失败") try: # 获取位图基础信息 bmp = BITMAP() if gdi32.GetObjectW(hbitmap, ctypes.sizeof(BITMAP), ctypes.byref(bmp)) == 0: raise RuntimeError("无法读取位图信息") width = bmp.bmWidth height = bmp.bmHeight bit_count = bmp.bmBitsPixel # 配置BITMAPINFO结构体 bmi = BITMAPINFO() bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biWidth = width bmi.bmiHeader.biHeight = -height # 设为负数让像素数据从上到下排列,匹配PIL格式 bmi.bmiHeader.biPlanes = 1 bmi.bmiHeader.biBitCount = bit_count bmi.bmiHeader.biCompression = 0 # BI_RGB无压缩格式 # 计算像素数据缓冲区大小 buffer_size = ((width * bit_count + 31) // 32) * 4 * height pixel_buffer = ctypes.create_string_buffer(buffer_size) # 获取屏幕DC(GetDIBits需要DC上下文) hdc = user32.GetDC(None) if not hdc: raise RuntimeError("无法获取设备上下文") try: # 提取位图像素数据 bits_copied = gdi32.GetDIBits( hdc, hbitmap, 0, height, pixel_buffer, ctypes.byref(bmi), 0 ) if bits_copied == 0: raise RuntimeError("无法提取像素数据") # 根据位深转换为PIL图像 if bit_count == 24: # Windows 24位位图为BGR格式,需转换为PIL的RGB格式 img = Image.frombuffer("RGB", (width, height), pixel_buffer, "raw", "BGR", 0, 1) elif bit_count == 32: # 32位位图为BGRA格式,转换为PIL的RGBA格式 img = Image.frombuffer("RGBA", (width, height), pixel_buffer, "raw", "BGRA", 0, 1) else: raise NotImplementedError(f"暂不支持{bit_count}位的位图格式") # 可直接显示或保存图像 img.show() # img.save("output.png") finally: # 释放DC上下文 user32.ReleaseDC(None, hdc) finally: # 释放HBITMAP句柄,避免内存泄漏 gdi32.DeleteObject(hbitmap)
关键说明
- 通过
GetObjectW获取位图的宽、高、位深等核心参数 - 使用
GetDIBits将HBITMAP中的像素数据提取到内存缓冲区 - Windows位图默认采用BGR/BGRA颜色顺序,而PIL使用RGB/RGBA,转换时需指定格式
- 必须调用
DeleteObject释放HBITMAP句柄,防止内存泄漏 - 若位图为8位灰度等其他位深,需调整
BITMAPINFO的调色板配置及PIL转换逻辑
内容的提问来源于stack exchange,提问作者jett chen
相关产品推荐
相关产品推荐

