使用 cpython 将位图保存到磁盘

问题描述 投票:0回答:1

一直在尝试用Python代码截屏。我让它与 pywin32 库一起工作,但现在需要调整它以仅使用 ctypes。我使用的是 SaveBitmapFile 函数,但这不是本机 WinAPI 函数

如何实现 saveBitmapToDisk 中的代码?

import ctypes

user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32

def saveBitmapToDisk(w, h, cDC, dataBitMap, file_path):
    print("help!")

def screenshot():
    w = 3840
    h = 2400
    image_path = "out.bmp"

    hwnd = user32.GetDesktopWindow()
    wDC = user32.GetWindowDC(hwnd)

    cDC = gdi32.CreateCompatibleDC(hwnd)

    dataBitMap = gdi32.CreateCompatibleBitmap(hwnd, w, h)
    gdi32.SelectObject(cDC, dataBitMap)
    gdi32.BitBlt(cDC, 0, 0, w, h, hwnd, 0, 0, 0x00CC0020)

    saveBitmapToDisk(w, h, cDC, dataBitMap, image_path)

    gdi32.DeleteDC(cDC)
    user32.ReleaseDC(hwnd, wDC)
    gdi32.DeleteObject(dataBitMap)

    return image_path
python winapi bitmap ctypes
1个回答
0
投票

提供的

screenshot
功能有一些错误(错误使用
hwnd
/
wDC
/
cDC
)。为所有函数定义
.argtypes
/
.restype
并提供
.errcheck
处理程序可确保错误不会悄无声息地传递。

这是基于 MSDN 的

Capturing Image
的完整ctypes实现:

import ctypes as ct
import ctypes.wintypes as w

HGDI_ERROR = w.HANDLE(-1).value
NO_ERROR = 0
ERROR_INVALID_PARAMETER = 87

# GlobalAlloc flags
GHND          = 0x0042  # Combines GMEM_MOVEABLE and GMEM_ZEROINIT.
GMEM_FIXED    = 0x0000  # Allocates fixed memory. The return value is a pointer.
GMEM_MOVEABLE = 0x0002  # Allocates movable memory. Memory blocks are never moved in physical memory, but they can be moved within the default heap.
                        # The return value is a handle to the memory object. To translate the handle into a pointer, use the GlobalLock function.
                        # This value cannot be combined with GMEM_FIXED.
GMEM_ZEROINIT = 0x0040  # Initializes memory contents to zero.
GPTR          = 0x0040  # Combines GMEM_FIXED and GMEM_ZEROINIT.

# region flags for SelectObject
NULLREGION    = 1
SIMPLEREGION  = 2
COMPLEXREGION = 3

# DIB color table identifiers for GetDIBits
DIB_RGB_COLORS = 0  # color table in RGBs
DIB_PAL_COLORS = 1  # color table in palette indices

# raster-operation code for BitBlt
SRCCOPY        = 0x00CC0020  # dest = source
SRCPAINT       = 0x00EE0086  # dest = source OR dest
SRCAND         = 0x008800C6  # dest = source AND dest
SRCINVERT      = 0x00660046  # dest = source XOR dest
SRCERASE       = 0x00440328  # dest = source AND (NOT dest )
NOTSRCCOPY     = 0x00330008  # dest = (NOT source)
NOTSRCERASE    = 0x001100A6  # dest = (NOT src) AND (NOT dest)
MERGECOPY      = 0x00C000CA  # dest = (source AND pattern)
MERGEPAINT     = 0x00BB0226  # dest = (NOT source) OR dest
PATCOPY        = 0x00F00021  # dest = pattern
PATPAINT       = 0x00FB0A09  # dest = DPSnoo
PATINVERT      = 0x005A0049  # dest = pattern XOR dest
DSTINVERT      = 0x00550009  # dest = (NOT dest)
BLACKNESS      = 0x00000042  # dest = BLACK
WHITENESS      = 0x00FF0062  # dest = WHITE
NOMIRRORBITMAP = 0x80000000  # Do not Mirror the bitmap in this call
CAPTUREBLT     = 0x40000000  # Include layered windows

# constants for the biCompression field of BITMAPINFOHEADER
BI_RGB       = 0
BI_RLE8      = 1
BI_RLE4      = 2
BI_BITFIELDS = 3
BI_JPEG      = 4
BI_PNG       = 5

# function error handlers to raise exceptions on various error conditions

def boolcheck(result, func, args):
    if not result:
        raise ct.WinError(ct.get_last_error())
    return None

def nullcheck(result, func, args):
    if result is None:
        raise ct.WinError(ct.get_last_error())
    return result

def nonnullcheck(result, func, args):
    if not result is None:
        raise ct.WinError(ct.get_last_error())
    return result

def null_or_hgdi_errorcheck(result, func, args):
    if result is None:
        raise WindowsError('None')
    if result == HGDI_ERROR:
        raise WindowsError('HGDI_ERROR')
    return result

def zeroerrorcheck(result, func, args):
    if not result:
        err = ct.get_last_error()
        if err != NO_ERROR:
            raise ct.WinError(err)
    return result

def bmicheck(result, func, args):
    if result == ERROR_INVALID_PARAMETER:
        raise ct.WinError(ERROR_INVALID_PARAMETER)
    if not result:
        raise WindowsError('returned 0 (fail)')
    return result

# Windows structures not defined in ctypes.wintypes

class BITMAP(ct.Structure):
    _fields_ = (('bmType', w.LONG),
                ('bmWidth', w.LONG),
                ('bmHeight', w.LONG),
                ('bmWidthBytes', w.LONG),
                ('bmPlanes', w.WORD),
                ('bmBitsPixel', w.WORD),
                ('bmBits', w.LPVOID))
    def __repr__(self):
        return f'BITMAP(bmType={self.bmType}, bmWidth={self.bmWidth}, ' \
               f'bmHeight={self.bmHeight}, bmWidthBytes={self.bmWidthBytes}, ' \
               f'bmPlanes={self.bmPlanes}, bmBitsPixel={self.bmBitsPixel}, ' \
               f'bmBits={self.bmBits})'

class BITMAPFILEHEADER(ct.Structure):
    _pack_ = 2
    _fields_ = (('bfType', w.WORD),
                ('bfSize', w.DWORD),
                ('bfReserved1', w.WORD),
                ('bfReserved2', w.WORD),
                ('bfOffBits', w.DWORD))
    def __repr__(self):
        return f'BITMAPFILEHEADER(bfType={self.bfType}, bfSize={self.bfSize}, ' \
               f'bfReserved1={self.bfReserved1}, bfReserved2={self.bfReserved2}, ' \
               f'bfOffBits={self.bfOffBits})'

class BITMAPINFOHEADER(ct.Structure):
    _fields_ = (('biSize', w.DWORD),
                ('biWidth', w.LONG),
                ('biHeight', w.LONG),
                ('biPlanes', w.WORD),
                ('biBitCount', w.WORD),
                ('biCompression', w.DWORD),
                ('biSizeImage', w.DWORD),
                ('biXPelsPerMeter', w.LONG),
                ('biYPelsPerMeter', w.LONG),
                ('biClrUsed', w.DWORD),
                ('biClrImportant', w.DWORD))
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.biSize = ct.sizeof(self)
    def __repr__(self):
        return f'BITMAPINFOHEADER(biSize={self.biSize}, biWidth={self.biWidth}, ' \
               f'biHeight={self.biHeight}, biPlanes={self.biPlanes}, ' \
               f'biBitCount={self.biBitCount}, biCompression={self.biCompression}, ' \
               f'biSizeImage={self.biSizeImage}, biXPelsPerMeter={self.biXPelsPerMeter}, ' \
               f'biYPelsPerMeter={self.biYPelsPerMeter}, biClrUsed={self.biClrUsed}, ' \
               f'biClrImportant={self.biClrImportant})'

class RGBQUAD(ct.Structure):
    _fields_ = (('rgbBlue', w.BYTE),
                ('rgbGreen', w.BYTE),
                ('rgbRed', w.BYTE),
                ('rgbReserved', w.BYTE))
    def __repr__(self):
        return f'RGBQUAD(rgbBlue={self.rgbBlue}, rgbGreen={self.rgbGreen}, ' \
               f'rgbRed={self.rgbRed}, rgbReserved={self.rgbReserved})'

class BITMAPINFO(ct.Structure):
    _fields_ = (('bmiHeader', BITMAPINFOHEADER),
                ('bmiColors', RGBQUAD * 1))
    def __repr__(self):
        return f'BITMAPINFO(bmiHeader={self.bmiHeader}, bmiColors[0]={self.bmiColors[0]})'

# Extend RECT for display and comparison support
class RECT(w.RECT):
    def __repr__(self):
        return f'RECT(left={self.left}, top={self.top}, right={self.right}, bottom={self.bottom})'
    def __eq__(self, other):
        return bytes(self) == bytes(other)

LPBITMAPINFO = ct.POINTER(BITMAPINFO)
SIZE_T = ct.c_size_t

# Kernel32 APIs with full argtypes/restype and exception-on-error

kernel32 = ct.WinDLL('kernel32', use_last_error=True)
GlobalAlloc = kernel32.GlobalAlloc
GlobalAlloc.argtypes = w.UINT, SIZE_T
GlobalAlloc.restype = w.HGLOBAL
GlobalAlloc.errcheck = nullcheck
GlobalLock = kernel32.GlobalLock
GlobalLock.argtypes = w.HGLOBAL,
GlobalLock.restype = w.LPVOID
GlobalLock.errcheck = nullcheck
GlobalFree = kernel32.GlobalFree
GlobalFree.argtypes = w.HGLOBAL,
GlobalFree.restype = w.HGLOBAL
GlobalFree.errcheck = nonnullcheck
GlobalUnlock = kernel32.GlobalUnlock
GlobalUnlock.argtypes = w.HGLOBAL,
GlobalUnlock.restype = w.BOOL
GlobalUnlock.errcheck = zeroerrorcheck

# User32 APIs with full argtypes/restype and exception-on-error

user32 = ct.WinDLL('user32', use_last_error=True)
GetDesktopWindow = user32.GetDesktopWindow
GetDesktopWindow.argtypes = ()
GetDesktopWindow.restype = w.HWND
GetWindowDC = user32.GetWindowDC
GetWindowDC.argtypes = w.HWND,
GetWindowDC.restype = w.HDC
GetWindowDC.errcheck = nullcheck
GetClientRect = user32.GetClientRect
GetClientRect.argtypes = w.HWND, w.LPRECT
GetClientRect.restype = w.BOOL
GetClientRect.errcheck = boolcheck
ReleaseDC = user32.ReleaseDC
ReleaseDC.argtypes = w.HWND, w.HDC
ReleaseDC.restype = ct.c_int
ReleaseDC.errcheck = boolcheck

# GDI32 APIs with full argtypes/restype and exception-on-error

gdi32 = ct.WinDLL('gdi32', use_last_error=True)
CreateCompatibleDC = gdi32.CreateCompatibleDC
CreateCompatibleDC.argtypes = w.HDC,
CreateCompatibleDC.restype = w.HDC
CreateCompatibleDC.errcheck = nullcheck
CreateCompatibleBitmap = gdi32.CreateCompatibleBitmap
CreateCompatibleBitmap.argtypes = w.HDC, ct.c_int, ct.c_int
CreateCompatibleBitmap.restype = w.HBITMAP
CreateCompatibleBitmap.errcheck = nullcheck
SelectObject = gdi32.SelectObject
SelectObject.argtypes = w.HDC, w.HGDIOBJ
SelectObject.restype = w.HGDIOBJ
SelectObject.errcheck = null_or_hgdi_errorcheck
BitBlt = gdi32.BitBlt
BitBlt.argtypes = w.HDC, ct.c_int, ct.c_int, ct.c_int, ct.c_int, w.HDC, ct.c_int, ct.c_int, w.DWORD
BitBlt.restype = w.BOOL
BitBlt.errcheck = boolcheck
DeleteDC = gdi32.DeleteDC
DeleteDC.argtypes = w.HDC,
DeleteDC.restype = w.BOOL
DeleteDC.errcheck = boolcheck
DeleteObject = gdi32.DeleteObject
DeleteObject.argtypes = w.HGDIOBJ,
DeleteObject.restype = w.BOOL
DeleteObject.errcheck = boolcheck
GetObject = gdi32.GetObjectW
GetObject.argtypes = w.HANDLE, ct.c_int, w.LPVOID
GetObject.restype = ct.c_int
GetObject.errcheck = boolcheck
GetDIBits = gdi32.GetDIBits
GetDIBits.argtypes = w.HDC, w.HBITMAP, w.UINT, w.UINT, w.LPVOID, LPBITMAPINFO, w.UINT
GetDIBits.restype = ct.c_int
GetDIBits.errcheck = bmicheck

# Implementation.  APIs that needed cleanup all handled with try/finally blocks.

def saveBitmapToDisk(w, h, wDC, dataBitMap, file_path):
    bmpScreen = BITMAP()
    GetObject(dataBitMap, ct.sizeof(BITMAP), ct.byref(bmpScreen))
    bmfHeader = BITMAPFILEHEADER()
    bi = BITMAPINFOHEADER()
    bi.biSize = ct.sizeof(bi)
    bi.biWidth = bmpScreen.bmWidth
    bi.biHeight = bmpScreen.bmHeight
    bi.biPlanes = 1
    bi.biBitCount = 32
    bi.biCompression = BI_RGB
    dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) // 32) * 4 * bmpScreen.bmHeight
    hDIB = GlobalAlloc(GHND, dwBmpSize)
    try:
        lpbitmap = GlobalLock(hDIB)
        try:
            GetDIBits(wDC, dataBitMap, 0, bmpScreen.bmHeight, lpbitmap, ct.cast(ct.byref(bi), LPBITMAPINFO), DIB_RGB_COLORS)
            dwSizeofDIB = dwBmpSize + ct.sizeof(BITMAPFILEHEADER) + ct.sizeof(BITMAPINFOHEADER)
            bmfHeader.bfSize = dwSizeofDIB
            bmfHeader.bfType = 0x4D42  # ASCII 'BM'
            with open(file_path, 'wb') as f:
                f.write(bytes(bmfHeader))
                f.write(bytes(bi))
                f.write(ct.string_at(lpbitmap, dwBmpSize))
        finally:
            GlobalUnlock(hDIB)
    finally:
        GlobalFree(hDIB)

def screenshot(image_path):
    hwnd = GetDesktopWindow()
    wDC = GetWindowDC(hwnd)
    try:
        cDC = CreateCompatibleDC(wDC)
        try:
            r = RECT()
            GetClientRect(hwnd, ct.byref(r))
            width = r.right - r.left
            height = r.bottom - r.top
            dataBitMap = CreateCompatibleBitmap(wDC, width, height)
            try:
                orig = SelectObject(cDC, dataBitMap)
                try:
                    BitBlt(cDC, 0, 0, width, height, wDC, 0, 0, SRCCOPY)
                    saveBitmapToDisk(width, height, wDC, dataBitMap, image_path)
                finally:
                    SelectObject(cDC, orig)
            finally:
                DeleteObject(dataBitMap)
        finally:
            DeleteDC(cDC)
    finally:
        ReleaseDC(hwnd, wDC)

screenshot('out.bmp')
© www.soinside.com 2019 - 2024. All rights reserved.