Winapi GetDIBits访问冲突

时间:2014-06-26 20:48:00

标签: python winapi ctypes

我想在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中的WORDRGBQUAD中的空白指针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。这是真的吗?

1 个答案:

答案 0 :(得分:2)

我错了:

  • 结构(thanks David):
    • BITMAP没有DWORD s;它有WORD s。
    • RGBQUAD s rgbReservedBYTE而非空指针。
    • BITMAPINFO对于每像素32位的位图不需要RGBQUAD数组。
  • 指针(thanks Roger,我来自python:P):
    • 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图像。

唯一的问题是颜色是...... 很奇怪?我会想到另一次。