当前位置: 首页 > article >正文

Python自带模块实现屏幕像素高效操作

1、获取屏幕放缩比例:

from ctypes import wintypes
import ctypes


HORZRES = 8
LOGPIXELSX = 118


def get_scale_factor() -> float:
    user32 = ctypes.windll.user32
    gdi32 = ctypes.windll.gdi32

    # 定义 HDC 和 UINT 类型
    HDC = wintypes.HDC
    UINT = wintypes.UINT

    # 定义 GetDC 和 GetDeviceCaps 的参数类型和返回类型
    user32.GetDC.argtypes = [wintypes.HWND]
    user32.GetDC.restype = HDC

    gdi32.GetDeviceCaps.argtypes = [HDC, UINT]
    gdi32.GetDeviceCaps.restype = wintypes.INT

    # 获取设备上下文
    dc = user32.GetDC(None)
    widthScale = gdi32.GetDeviceCaps(dc, HORZRES)
    width = gdi32.GetDeviceCaps(dc, LOGPIXELSX)
    scale = width / widthScale
    return scale

2、获取屏幕指定坐标处像素颜色:
 

import ctypes
from ctypes import wintypes
from typing import Sequence, Generator


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

# 定义类型
HWND = wintypes.HWND
HDC = wintypes.HDC
HBITMAP = wintypes.HBITMAP


class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ("biSize", wintypes.DWORD),
        ("biWidth", wintypes.LONG),
        ("biHeight", wintypes.LONG),
        ("biPlanes", wintypes.WORD),
        ("biBitCount", wintypes.WORD),
        ("biCompression", wintypes.DWORD),
        ("biSizeImage", wintypes.DWORD),
        ("biXPelsPerMeter", wintypes.LONG),
        ("biYPelsPerMeter", wintypes.LONG),
        ("biClrUsed", wintypes.DWORD),
        ("biClrImportant", wintypes.DWORD)
    ]

class BITMAPINFO(ctypes.Structure):
    _fields_ = [
        ("bmiHeader", BITMAPINFOHEADER),
        ("bmiColors", wintypes.DWORD * 3)
    ]


def get_pixel_color(coords: Sequence[tuple[int, int]], hwnd: HWND) -> Generator[tuple[int, int, int], None, None]:
    rect = wintypes.RECT()
    user32.GetClientRect(hwnd, ctypes.byref(rect))
    width = rect.right - rect.left
    height = rect.bottom - rect.top

    # 创建内存设备上下文
    hdc_src = user32.GetDC(hwnd)
    hdc_dst = gdi32.CreateCompatibleDC(hdc_src)
    bmp = gdi32.CreateCompatibleBitmap(hdc_src, width, height)
    gdi32.SelectObject(hdc_dst, bmp)

    # 使用 BitBlt 复制窗口内容到内存设备上下文
    gdi32.BitBlt(hdc_dst, 0, 0, width, height, hdc_src, 0, 0, 0x00CC0020)  # SRCCOPY

    # 获取位图信息
    bmi = BITMAPINFO()
    bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
    bmi.bmiHeader.biWidth = width
    bmi.bmiHeader.biHeight = -height  # 负值表示自底向上
    bmi.bmiHeader.biPlanes = 1
    bmi.bmiHeader.biBitCount = 32
    bmi.bmiHeader.biCompression = 0

    # 创建缓冲区并获取位图数据
    buffer = ctypes.create_string_buffer(width * height * 4)
    gdi32.GetDIBits(hdc_dst, bmp, 0, height, buffer, ctypes.byref(bmi), 0)

    # 释放资源
    gdi32.DeleteObject(bmp)
    gdi32.DeleteDC(hdc_dst)
    user32.ReleaseDC(hwnd, hdc_src)

    # 遍历指定坐标并返回像素颜色
    for x, y in coords:
        if 0 <= x < width and 0 <= y < height:
            offset = (y * width + x) * 4
            color = buffer[offset:offset + 4]
            yield color[2], color[1], color[0]  # BGR -> RGB
        else:
            yield (0, 0, 0)

 

3、一个简单的使用案例

from typing import Sequence, Generator, Tuple
from tkinter import ttk
import tkinter as tk
from ctypes import wintypes
import ctypes
import requests
from io import BytesIO
from PIL import Image, ImageTk





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

HWND = wintypes.HWND
HDC = wintypes.HDC
HBITMAP = wintypes.HBITMAP


class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ("biSize", wintypes.DWORD),
        ("biWidth", wintypes.LONG),
        ("biHeight", wintypes.LONG),
        ("biPlanes", wintypes.WORD),
        ("biBitCount", wintypes.WORD),
        ("biCompression", wintypes.DWORD),
        ("biSizeImage", wintypes.DWORD),
        ("biXPelsPerMeter", wintypes.LONG),
        ("biYPelsPerMeter", wintypes.LONG),
        ("biClrUsed", wintypes.DWORD),
        ("biClrImportant", wintypes.DWORD)
    ]


class BITMAPINFO(ctypes.Structure):
    _fields_ = [
        ("bmiHeader", BITMAPINFOHEADER),
        ("bmiColors", wintypes.DWORD * 3)
    ]


def get_pixel_color(coords: Sequence[Tuple[int, int]], hwnd: HWND) -> Generator[Tuple[int, int, int], None, None]:
    rect = wintypes.RECT()
    user32.GetClientRect(hwnd, ctypes.byref(rect))
    width = rect.right - rect.left
    height = rect.bottom - rect.top

    hdc_src = user32.GetDC(hwnd)
    hdc_dst = gdi32.CreateCompatibleDC(hdc_src)
    bmp = gdi32.CreateCompatibleBitmap(hdc_src, width, height)
    gdi32.SelectObject(hdc_dst, bmp)

    gdi32.BitBlt(hdc_dst, 0, 0, width, height, hdc_src, 0, 0, 0x00CC0020)  # SRCCOPY

    bmi = BITMAPINFO()
    bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
    bmi.bmiHeader.biWidth = width
    bmi.bmiHeader.biHeight = -height  # 负值表示自底向上
    bmi.bmiHeader.biPlanes = 1
    bmi.bmiHeader.biBitCount = 32
    bmi.bmiHeader.biCompression = 0

    buffer = ctypes.create_string_buffer(width * height * 4)
    gdi32.GetDIBits(hdc_dst, bmp, 0, height, buffer, ctypes.byref(bmi), 0)

    gdi32.DeleteObject(bmp)
    gdi32.DeleteDC(hdc_dst)
    user32.ReleaseDC(hwnd, hdc_src)

    for x, y in coords:
        print(x, y, width, height)
        if 0 <= x < width and 0 <= y < height:
            offset = (y * width + x) * 4
            color = buffer[offset:offset + 4]
            yield color[2], color[1], color[0]  # BGR -> RGB
        else:
            yield (0, 0, 0)


def get_window_handle(window):
    window_name = window._w
    if not window_name.startswith("."):
        window_name = "." + window_name
    
    hwnd = ctypes.windll.user32.FindWindowW(None, window.title())
    if not hwnd:
        raise ValueError("Cannot get the window handle.")
    return hwnd

def download_image(url):
    response = requests.get(url)
    if response.status_code == 200:
        return Image.open(BytesIO(response.content))
    else:
        raise Exception(f"Failed to download image: HTTP {response.status_code}")

def display_image_in_label(image):
    photo = ImageTk.PhotoImage(image)
    label = ttk.Label(root, image=photo)
    label.image = photo  # 保持对 PhotoImage 的引用,防止被垃圾回收
    label.pack()


def show_color(event):
    hwnd = get_window_handle(root)
    x, y = event.x, event.y
    # 注意这里的坐标是相对于窗口的坐标,且传入get_pixel_color的应该是包含多个坐标点的序列
    # 此外,为了高效获取同一个画面多个点的颜色,此处我使用了生成器进行懒加载,因此获取数据时请完整遍历迭代器
    result = get_pixel_color([(x, y)], hwnd)
    colors = [i for i in result]
    print(f"{event.x, event.y}: {colors}")


if __name__ == "__main__":
    root = tk.Tk()
    width, height = 900, 500
    screenwidth = root.winfo_screenwidth()
    screenheight = root.winfo_screenheight()
    geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
    root.title("测试样例")
    root.geometry(geometry)
    root.bind("<Motion>", show_color)
 
    image_url = "https://ts1.cn.mm.bing.net/th/id/R-C.475631ce281b88c3cd465761b37c5256?rik=ZFMiTYFwaPypTQ&riu=http%3a%2f%2fpic.ntimg.cn%2ffile%2f20180102%2f21532952_215949247000_2.jpg&ehk=9NnCJ9JG44zfdF2%2fr373s25s68H9vxLvyfMsKgEzAwc%3d&risl=&pid=ImgRaw&r=0"
    try:
        img = download_image(image_url)
        display_image_in_label(img)
    except Exception as e:
        print(f"Error: {e}")
        ttk.Label(root, text="Failed to load image.").pack()

    root.mainloop()

 

4、总结

上述方法比通常使用PIL的Image.ImageGrab方法要高效非常多,因为Image.ImageGrab是基于IO截屏操作的,频繁的IO操作使单纯进行屏幕像素访问十分低效。

而上述方法采用的是BitBlt。BitBlt 是一种高效的位图操作方法,可以将窗口的内容复制到内存设备上下文中,然后通过 GetPixel 或直接访问位图数据来获取像素颜色。就像素访问而言其性能显著强于前者。更多关于Window的API操作详见官方文档:

Windows GDI) (位图函数 - Win32 apps | Microsoft Learn

 


http://www.kler.cn/a/516291.html

相关文章:

  • vs code为不同项目设置不同的背景图
  • 电子应用设计方案102:智能家庭AI鱼缸系统设计
  • Spring Boot整合Thymeleaf、JDBC Template与MyBatis配置详解
  • HackTheBox靶机:Sightless;NodeJS模板注入漏洞,盲XSS跨站脚本攻击漏洞实战
  • 国产工作平替软件推荐
  • vscode导入模块不显示类型注解
  • VUE对接deepseekAPI调用
  • 【LeetCode 刷题】二叉树-深度优先遍历
  • Chromium 132 编译指南 Mac 篇(二)- 安装 Xcode
  • WPF-系统资源
  • 豆包MarsCode:小C点菜问题
  • 左/右侧边栏布局(Semi design)
  • FPGA实现任意角度视频旋转(二)视频90度/270度无裁剪旋转
  • react antd点击table单元格文字下载指定的excel路径
  • Conmi的正确答案——Rider中引入WebView2包(C#)
  • Django 日志配置实战指南
  • .NET 9 微软官方推荐使用 Scalar 替代传统的 Swagger
  • 【项目初始化】自定义异常处理
  • 终极的复杂,是简单
  • PVE 虚拟机安装 Debian 无图形化界面服务器
  • 【后端开发】字节跳动青训营之Go语言进阶与依赖管理
  • Elementor Pro 3.27 汉化版 2100套模板 安装教程 wordpress主题中文编辑器插件免费下载
  • 缓存-Redis-数据结构-redis哪些数据结构是跳表实现的?
  • Node.js的解释
  • Charles 4.6.7 浏览器网络调试指南:基本界面与操作(二)
  • Vue 全局自适应大小:使用 postcss-pxtorem