创建图像文件 & header (python + ctypes)

Creating an image file & header (python + ctypes)

经过大量的来回编辑,这就是我的位置(请记住,我是菜鸟):

现在我只是想检测一个 window 并截取那个 window 的屏幕截图(稍后我将解析屏幕截图以获取信息)。我一直在谷歌上广泛搜索 ctypes、DLL 函数,并在 python 中截取屏幕截图...我发现了一些我重复使用的代码(在搜索每个函数时,它的参数和语法以确保我理解所有内容) 但现在我被难住了。

我的功能有效,我的 screenshot.bmp 文件实际上包含数据,但在 Windows 中打开它时出现错误...(不是有效的位图文件):

我认为这与 bmp_header 或 c_bits 行有关,但我找不到很多...我查看了 header 格式,它对我来说似乎是正确的 (windows BITMAPINFOHEADER)。我不确定的是我们如何计算缓冲区的大小,也许我在那里做错了?

非常感谢有人向我解释这一点或提供 link 一篇可以帮助我的文章...

非常感谢!

#!/usr/bin/env python3

import ctypes
from struct import calcsize, pack
from ctypes.wintypes import RECT, DWORD

EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsPointer = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible
GetWindowRect = ctypes.windll.user32.GetWindowRect
GetWindowDC = ctypes.windll.user32.GetWindowDC
CreateCompatibleDC = ctypes.windll.gdi32.CreateCompatibleDC
CreateCompatibleBitmap = ctypes.windll.gdi32.CreateCompatibleBitmap
SelectObject = ctypes.windll.gdi32.SelectObject
BitBlt = ctypes.windll.gdi32.BitBlt
GetDIBits = ctypes.windll.gdi32.GetDIBits
CreateFile = ctypes.windll.kernel32.CreateFileW
WriteFile = ctypes.windll.kernel32.WriteFile

#Here we find HWND and LPARAM passed by EnumWindows to the callback
def CaptureWindow(hwnd, lParam):
    #Restrict to visible windows
    if IsWindowVisible(hwnd):
        #Get title of the specific window
        length = GetWindowTextLength(hwnd)
        buff = ctypes.create_unicode_buffer(length + 1)
        GetWindowText(hwnd, buff, length + 1)
        #Verify match with our window
        if "(Mode Virtuel)" in buff.value:
            r = ctypes.wintypes.RECT()
            GetWindowRect(hwnd,ctypes.byref(r))
            rWidth = r.right-r.left
            rHeight = r.bottom-r.top
            srcdc = GetWindowDC(hwnd)
            memdc = CreateCompatibleDC(srcdc)
            bmp = CreateCompatibleBitmap(srcdc, rWidth, rHeight)
            SelectObject(memdc, bmp)
            BitBlt(memdc, 0, 0, rWidth, rHeight, srcdc, r.left, r.top, "SRCCOPY")

            #Create a BMP header (Windows BitmapInfoHeader format)
            bmp_header = pack('LLLHHLLLLLL', calcsize('LLLHHLLLLLL'), rWidth, rHeight, 1, 24, 0, 0, 0, 0, 0, 0)
            c_bmp_header = ctypes.c_buffer(bmp_header) 

            #Create buffer for BMP data (H * W * 3 = RGB for each pixel) + (40 bytes for the header)
            c_bits = ctypes.c_buffer(b' ' * ((rHeight * rWidth * 3) + 40))
            GetDIBits(memdc, bmp, 0, rHeight, ctypes.byref(c_bits), ctypes.byref(c_bmp_header), "DIB_RGB_COLORS")

            #Create the file
            screen = CreateFile("screenshot.bmp", 3, 4, 0, 2, 128, 0)
            #Write the contents of my BMP buffer in the file
            c_written = DWORD()
            success = WriteFile(screen, c_bits, len(c_bits), ctypes.byref(c_written), 0)
            #Info
            print("Screenshot saved for : {0}".format(buff.value))
            print("{0} bytes were written...".format(c_written))
    return True

EnumWindows(EnumWindowsPointer(CaptureWindow), 0)

好的,我找到了解决方案,但我很失望,因为它实际上并没有解决我之前代码中的问题。

为了回复评论,我喜欢了解事物的工作原理 "under the hood",并且我喜欢使用 ctypes,因为它教会了我一些 C 并允许我使用系统库(这是一个非常有趣的知识我相信在任何编码工作中都取得了进展)。此外,我总是更喜欢使用本机 python 而不是导入别人的代码(我不完全理解)。我不觉得下面的解决方案是完整的 "my own".....

无论如何,这是我的新代码,它可以完美运行并每 0.5 秒检查我的 window 以检查是否需要执行操作(通过解析特定像素的颜色变化):

#!/usr/bin/env python3
import time
import ctypes
import pyautogui
from ctypes.wintypes import RECT

EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsPointer = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible
GetWindowRect = ctypes.windll.user32.GetWindowRect
SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow
screenshot = pyautogui.screenshot

#Here we find HWND and LPARAM passed by EnumWindows to the callback
def CaptureWindow(hwnd, lParam):
    #Restrict to visible windows
    if IsWindowVisible(hwnd):
        #Get title of the specific window
        length = GetWindowTextLength(hwnd)
        buff = ctypes.create_unicode_buffer(length + 1)
        GetWindowText(hwnd, buff, length + 1)
        #Verify match with our window
        if "(Mode Virtuel)" in buff.value:
            #Get dimensions
            r = ctypes.wintypes.RECT()
            GetWindowRect(hwnd,ctypes.byref(r))
            rWidth = r.right-r.left
            rHeight = r.bottom-r.top
            SetForegroundWindow(hwnd)
            screen = screenshot(region=(r.left, r.top, rWidth, rHeight))
            if screen.getpixel((444,412)) == (255, 145, 45):
                print("Action needed !!!")
            else:
                print("No action needed !!!")
    return True


while True:
    EnumWindows(EnumWindowsPointer(CaptureWindow), 0)
    time.sleep(.5)