当前位置: 代码网 > it编程>前端脚本>Python > Python利用自带模块实现屏幕像素高效操作

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

2025年02月10日 Python 我要评论
1、获取屏幕放缩比例from ctypes import wintypesimport ctypes horzres = 8logpixelsx = 118 def get_scale_facto

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

到此这篇关于python利用自带模块实现屏幕像素高效操作的文章就介绍到这了,更多相关python屏幕像素操作内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com