如何用纯ctypes实现Python截图的位图磁盘保存功能?
用纯ctypes实现Windows位图保存功能
你之前用pywin32的SaveBitmapFile实现截图保存,但这个并非原生WinAPI,纯ctypes环境下需要手动构造BMP文件结构并写入磁盘。下面是完整的实现方案:
完整代码实现
import ctypes from ctypes import wintypes # 绑定Windows系统库 user32 = ctypes.windll.user32 gdi32 = ctypes.windll.gdi32 kernel32 = ctypes.windll.kernel32 # 定义WinAPI常用类型 HANDLE = wintypes.HANDLE HWND = wintypes.HWND HDC = wintypes.HDC HBITMAP = wintypes.HBITMAP DWORD = wintypes.DWORD LONG = wintypes.LONG WORD = wintypes.WORD BYTE = wintypes.BYTE LPVOID = wintypes.LPVOID # 定义BMP文件头结构体 class BITMAPFILEHEADER(ctypes.Structure): _pack_ = 2 # 按2字节对齐,符合BMP文件头要求 _fields_ = [ ("bfType", WORD), ("bfSize", DWORD), ("bfReserved1", WORD), ("bfReserved2", WORD), ("bfOffBits", DWORD) ] # 定义BMP信息头结构体 class BITMAPINFOHEADER(ctypes.Structure): _fields_ = [ ("biSize", DWORD), ("biWidth", LONG), ("biHeight", LONG), ("biPlanes", WORD), ("biBitCount", WORD), ("biCompression", DWORD), ("biSizeImage", DWORD), ("biXPelsPerMeter", LONG), ("biYPelsPerMeter", LONG), ("biClrUsed", DWORD), ("biClrImportant", DWORD) ] # 定义BMP信息结构体 class BITMAPINFO(ctypes.Structure): _fields_ = [ ("bmiHeader", BITMAPINFOHEADER), ("bmiColors", DWORD * 3) # 24位真彩色无需调色板,仅占位 ] def saveBitmapToDisk(width, height, hdc, hbitmap, file_path): # 初始化BMP信息头 bmi = BITMAPINFO() bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biWidth = width bmi.bmiHeader.biHeight = -height # 负值表示图像从上到下存储(BMP标准) bmi.bmiHeader.biPlanes = 1 bmi.bmiHeader.biBitCount = 24 # 24位真彩色 bmi.bmiHeader.biCompression = 0 # BI_RGB无压缩格式 # 计算对齐后的图像数据大小(每行像素需为4字节倍数) bmi.bmiHeader.biSizeImage = ((width * 3 + 3) // 4) * 4 * height # 创建像素数据缓冲区 buffer_size = bmi.bmiHeader.biSizeImage buffer = ctypes.create_string_buffer(buffer_size) # 从HBITMAP中提取像素数据 gdi32.GetDIBits(hdc, hbitmap, 0, height, buffer, ctypes.byref(bmi), 0) # 构造BMP文件头 bmfh = BITMAPFILEHEADER() bmfh.bfType = 0x4D42 # "BM"标识,BMP文件特征码 bmfh.bfSize = ctypes.sizeof(BITMAPFILEHEADER) + ctypes.sizeof(BITMAPINFOHEADER) + buffer_size bmfh.bfOffBits = ctypes.sizeof(BITMAPFILEHEADER) + ctypes.sizeof(BITMAPINFOHEADER) # 写入文件 with open(file_path, "wb") as f: f.write(bmfh) f.write(bmi.bmiHeader) f.write(buffer.raw) def screenshot(): # 自动获取屏幕分辨率,替代硬编码 w = user32.GetSystemMetrics(0) h = user32.GetSystemMetrics(1) image_path = "out.bmp" hwnd = user32.GetDesktopWindow() wDC = user32.GetWindowDC(hwnd) # 修正:CreateCompatibleDC需传入DC句柄而非窗口句柄 cDC = gdi32.CreateCompatibleDC(wDC) dataBitMap = gdi32.CreateCompatibleBitmap(wDC, w, h) old_bitmap = gdi32.SelectObject(cDC, dataBitMap) # 修正:BitBlt源DC应为wDC,不是窗口句柄 gdi32.BitBlt(cDC, 0, 0, w, h, wDC, 0, 0, 0x00CC0020) # SRCCOPY模式复制图像 saveBitmapToDisk(w, h, cDC, dataBitMap, image_path) # 恢复DC中原位图,避免资源泄漏 gdi32.SelectObject(cDC, old_bitmap) gdi32.DeleteDC(cDC) user32.ReleaseDC(hwnd, wDC) gdi32.DeleteObject(dataBitMap) return image_path if __name__ == "__main__": screenshot()
关键说明
- 结构体定义:严格遵循Windows API的BMP文件结构规范,注意字节对齐要求。
- GetDIBits:原生WinAPI函数,负责从HBITMAP句柄中提取原始像素数据,这里使用无压缩的24位真彩色格式。
- 图像对齐处理:BMP文件要求每行像素数据长度为4的倍数,计算
biSizeImage时需处理该对齐逻辑。 - 原代码修复:
- 修正
CreateCompatibleDC参数错误,需传入已有DC句柄而非窗口句柄。 - 修正
BitBlt源DC参数错误,需使用窗口DC而非窗口句柄。 - 添加
SelectObject恢复DC中原位图,避免GDI资源泄漏。
- 修正
内容的提问来源于stack exchange,提问作者agrivts
相关产品推荐
相关产品推荐

