我想在python中获取BITMAPINFO
的原始字节。这是我的完整代码:
import ctypes
from ctypes import wintypes
windll = ctypes.windll
user32 = windll.user32
gdi32 = windll.gdi32
class RECT(ctypes.Structure):
_fields_ = [
('left', ctypes.c_long),
('top', ctypes.c_long),
('right', ctypes.c_long),
('bottom', ctypes.c_long)
]
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", wintypes.DWORD),
("biWidth", ctypes.c_long),
("biHeight", ctypes.c_long),
("biPlanes", wintypes.WORD),
("biBitCount", wintypes.WORD),
("biCompression", wintypes.DWORD),
("biSizeImage", wintypes.DWORD),
("biXPelsPerMeter", ctypes.c_long),
("biYPelsPerMeter", ctypes.c_long),
("biClrUsed", wintypes.DWORD),
("biClrImportant", wintypes.DWORD)
]
class RGBQUAD(ctypes.Structure):
_fields_ = [
("rgbBlue", wintypes.BYTE),
("rgbGreen", wintypes.BYTE),
("rgbRed", wintypes.BYTE),
("rgbReserved", ctypes.c_void_p)
]
class BITMAP(ctypes.Structure):
_fields_ = [
("bmType", ctypes.c_long),
("bmWidth", ctypes.c_long),
("bmHeight", ctypes.c_long),
("bmWidthBytes", ctypes.c_long),
("bmPlanes", wintypes.DWORD),
("bmBitsPixel", wintypes.DWORD),
("bmBits", ctypes.c_void_p)
]
whandle = 327756 # Just a handle of an open application
rect = RECT()
user32.GetClientRect(whandle, ctypes.byref(rect))
# bbox = (rect.left, rect.top, rect.right, rect.bottom)
hdcScreen = user32.GetDC(None)
hdc = gdi32.CreateCompatibleDC(hdcScreen)
hbmp = gdi32.CreateCompatibleBitmap(
hdcScreen,
rect.right - rect.left,
rect.bottom - rect.top
)
gdi32.SelectObject(hdc, hbmp)
PW_CLIENTONLY = 1
if not user32.PrintWindow(whandle, hdc, PW_CLIENTONLY):
raise Exception("PrintWindow failed")
bmap = BITMAP()
if not gdi32.GetObjectW(hbmp, ctypes.sizeof(BITMAP), ctypes.byref(bmap)):
raise Exception("GetObject failed")
class BITMAPINFO(ctypes.Structure):
_fields_ = [
("BITMAPINFOHEADER", BITMAPINFOHEADER),
("RGBQUAD", RGBQUAD * 1000)
]
bminfo = BITMAPINFO()
bminfo.BITMAPINFOHEADER.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bminfo.BITMAPINFOHEADER.biWidth = bmap.bmWidth
bminfo.BITMAPINFOHEADER.biHeight = bmap.bmHeight
bminfo.BITMAPINFOHEADER.biPlanes = bmap.bmPlanes
bminfo.BITMAPINFOHEADER.biBitCount = bmap.bmBitsPixel
bminfo.BITMAPINFOHEADER.biCompression = 0
bminfo.BITMAPINFOHEADER.biClrImportant = 0
out = ctypes.create_string_buffer(1000)
if not gdi32.GetDIBits(hdc, hbmp, 0, bmap.bmHeight, None, bminfo, 0):
raise Exception("GetDIBits failed")
我需要一种方法来了解RGBQUADS
结构中BITMAPINFO
数组的长度以及out
缓冲区的长度。 1000
作为占位符存在。
gdi32.GetDIBits
因访问冲突而失败。我猜它是因为我必须拥有正确长度的数组和缓冲区。
我发布了整个来源,因为我不知道什么是失败的。任何帮助表示赞赏。
DWORD
BITMAP
中的WORD
和RGBQUAD
中的空白指针BYTE
获取图像数据的大小:
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
data_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel) * bmap.bmHeight
仍然违规访问。
我也看到there is no RGBQUAD array for 32-bit-per-pixel bitmaps。这是真的吗?
答案 0 :(得分:2)
BITMAP
没有DWORD
s;它有WORD
s。RGBQUAD
s rgbReserved
是BYTE
而非空指针。BITMAPINFO
对于每像素32位的位图不需要RGBQUAD
数组。GetDIBits
参数lpvBits
需要指向缓冲区的指针GetDIBits
参数lpbi
需要一个指向struct 缓冲区必须有多大。引用Jonathan:
位图的每一行的大小为bmWidth * bmBitsPixel位,向上舍入为32位的下一个倍数。将行长度乘以bmHeight以计算图像数据的总大小。
我想出了这个:
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
scanline_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel)
data_len = scanline_len * bmap.bmHeight
然后 data_len
用于初始化ctypes.create_string_buffer()
。
GetDIBits
只返回像素数据,因此我必须构建标题。
完成所有这些更改后,没有任何失败,但图像被反转。我发现GetDIBits
因兼容原因而返回了反转的扫描线。我从字节中创建了一个新的PIL图像,然后将其翻转。
完整的消息来源如下:
import struct
from PIL import Image
from PIL.ImageOps import flip
import ctypes
from ctypes import wintypes
windll = ctypes.windll
user32 = windll.user32
gdi32 = windll.gdi32
class RECT(ctypes.Structure):
_fields_ = [
('left', ctypes.c_long),
('top', ctypes.c_long),
('right', ctypes.c_long),
('bottom', ctypes.c_long)
]
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", wintypes.DWORD),
("biWidth", ctypes.c_long),
("biHeight", ctypes.c_long),
("biPlanes", wintypes.WORD),
("biBitCount", wintypes.WORD),
("biCompression", wintypes.DWORD),
("biSizeImage", wintypes.DWORD),
("biXPelsPerMeter", ctypes.c_long),
("biYPelsPerMeter", ctypes.c_long),
("biClrUsed", wintypes.DWORD),
("biClrImportant", wintypes.DWORD)
]
class BITMAPINFO(ctypes.Structure):
_fields_ = [
("bmiHeader", BITMAPINFOHEADER)
]
class BITMAP(ctypes.Structure):
_fields_ = [
("bmType", ctypes.c_long),
("bmWidth", ctypes.c_long),
("bmHeight", ctypes.c_long),
("bmWidthBytes", ctypes.c_long),
("bmPlanes", wintypes.WORD),
("bmBitsPixel", wintypes.WORD),
("bmBits", ctypes.c_void_p)
]
def get_window_image(whandle):
def round_up32(n):
multiple = 32
while multiple < n:
multiple += 32
return multiple
rect = RECT()
user32.GetClientRect(whandle, ctypes.byref(rect))
bbox = (rect.left, rect.top, rect.right, rect.bottom)
hdcScreen = user32.GetDC(None)
hdc = gdi32.CreateCompatibleDC(hdcScreen)
hbmp = gdi32.CreateCompatibleBitmap(
hdcScreen,
bbox[2] - bbox[0],
bbox[3] - bbox[1]
)
gdi32.SelectObject(hdc, hbmp)
PW_CLIENTONLY = 1
if not user32.PrintWindow(whandle, hdc, PW_CLIENTONLY):
raise Exception("PrintWindow failed")
bmap = BITMAP()
if not gdi32.GetObjectW(hbmp, ctypes.sizeof(BITMAP), ctypes.byref(bmap)):
raise Exception("GetObject failed")
if bmap.bmBitsPixel != 32:
raise Exception("WTF")
scanline_len = round_up32(bmap.bmWidth * bmap.bmBitsPixel)
data_len = scanline_len * bmap.bmHeight
# http://msdn.microsoft.com/en-us/library/ms969901.aspx
bminfo = BITMAPINFO()
bminfo.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bminfo.bmiHeader.biWidth = bmap.bmWidth
bminfo.bmiHeader.biHeight = bmap.bmHeight
bminfo.bmiHeader.biPlanes = 1
bminfo.bmiHeader.biBitCount = 24 # bmap.bmBitsPixel
bminfo.bmiHeader.biCompression = 0
data = ctypes.create_string_buffer(data_len)
DIB_RGB_COLORS = 0
get_bits_success = gdi32.GetDIBits(
hdc, hbmp,
0, bmap.bmHeight,
ctypes.byref(data), ctypes.byref(bminfo),
DIB_RGB_COLORS
)
if not get_bits_success:
raise Exception("GetDIBits failed")
# http://msdn.microsoft.com/en-us/library/dd183376%28v=vs.85%29.aspx
bmiheader_fmt = "LllHHLLllLL"
unpacked_header = [
bminfo.bmiHeader.biSize,
bminfo.bmiHeader.biWidth,
bminfo.bmiHeader.biHeight,
bminfo.bmiHeader.biPlanes,
bminfo.bmiHeader.biBitCount,
bminfo.bmiHeader.biCompression,
bminfo.bmiHeader.biSizeImage,
bminfo.bmiHeader.biXPelsPerMeter,
bminfo.bmiHeader.biYPelsPerMeter,
bminfo.bmiHeader.biClrUsed,
bminfo.bmiHeader.biClrImportant
]
# Indexes: biXPelsPerMeter = 7, biYPelsPerMeter = 8
# Value from https://stackoverflow.com/a/23982267/2065904
unpacked_header[7] = 3779
unpacked_header[8] = 3779
image_header = struct.pack(bmiheader_fmt, *unpacked_header)
image = image_header + data
return flip(Image.frombytes("RGB", (bmap.bmWidth, bmap.bmHeight), image))
将窗口句柄(int)传递给get_window_image()
,然后返回PIL图像。
唯一的问题是颜色是...... 很奇怪?我会想到另一次。