Python利用自帶模塊實(shí)現(xiàn)屏幕像素高效操作
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 的參數(shù)類型和返回類型 user32.GetDC.argtypes = [wintypes.HWND] user32.GetDC.restype = HDC gdi32.GetDeviceCaps.argtypes = [HDC, UINT] gdi32.GetDeviceCaps.restype = wintypes.INT # 獲取設(shè)備上下文 dc = user32.GetDC(None) widthScale = gdi32.GetDeviceCaps(dc, HORZRES) width = gdi32.GetDeviceCaps(dc, LOGPIXELSX) scale = width / widthScale return scale
2、獲取屏幕指定坐標(biāo)處像素顏色
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 # 創(chuàng)建內(nèi)存設(shè)備上下文 hdc_src = user32.GetDC(hwnd) hdc_dst = gdi32.CreateCompatibleDC(hdc_src) bmp = gdi32.CreateCompatibleBitmap(hdc_src, width, height) gdi32.SelectObject(hdc_dst, bmp) # 使用 BitBlt 復(fù)制窗口內(nèi)容到內(nèi)存設(shè)備上下文 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 # 負(fù)值表示自底向上 bmi.bmiHeader.biPlanes = 1 bmi.bmiHeader.biBitCount = 32 bmi.bmiHeader.biCompression = 0 # 創(chuàng)建緩沖區(qū)并獲取位圖數(shù)據(jù) 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) # 遍歷指定坐標(biāo)并返回像素顏色 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、一個(gè)簡(jiǎn)單的使用案例
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 # 負(fù)值表示自底向上 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 # 保持對(duì) PhotoImage 的引用,防止被垃圾回收 label.pack() def show_color(event): hwnd = get_window_handle(root) x, y = event.x, event.y # 注意這里的坐標(biāo)是相對(duì)于窗口的坐標(biāo),且傳入get_pixel_color的應(yīng)該是包含多個(gè)坐標(biāo)點(diǎn)的序列 # 此外,為了高效獲取同一個(gè)畫面多個(gè)點(diǎn)的顏色,此處我使用了生成器進(jìn)行懶加載,因此獲取數(shù)據(jù)時(shí)請(qǐng)完整遍歷迭代器 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("測(cè)試樣例") 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、總結(jié)
上述方法比通常使用PIL的Image.ImageGrab方法要高效非常多,因?yàn)镮mage.ImageGrab是基于IO截屏操作的,頻繁的IO操作使單純進(jìn)行屏幕像素訪問十分低效。
而上述方法采用的是BitBlt。BitBlt 是一種高效的位圖操作方法,可以將窗口的內(nèi)容復(fù)制到內(nèi)存設(shè)備上下文中,然后通過 GetPixel 或直接訪問位圖數(shù)據(jù)來獲取像素顏色。就像素訪問而言其性能顯著強(qiáng)于前者。更多關(guān)于Window的API操作詳見官方文檔:
Windows GDI) (位圖函數(shù) - Win32 apps | Microsoft Learn
到此這篇關(guān)于Python利用自帶模塊實(shí)現(xiàn)屏幕像素高效操作的文章就介紹到這了,更多相關(guān)Python屏幕像素操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python實(shí)現(xiàn)嵌套列表平鋪的兩種方法
今天小編就為大家分享一篇python實(shí)現(xiàn)嵌套列表平鋪的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-11-11夯實(shí)基礎(chǔ)Python列表的索引和切片使用示例
這篇文章主要為大家介紹了Python列表的索引和切片使用示例基礎(chǔ)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10pandas重復(fù)行刪除操作df.drop_duplicates和df.duplicated的區(qū)別
本文主要介紹了pandas重復(fù)行刪除操作df.drop_duplicates和df.duplicated的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Pandas實(shí)現(xiàn)列(column)排序的幾種方法
Pandas是一種高效的數(shù)據(jù)處理庫(kù),在數(shù)據(jù)處理過程中,咱們經(jīng)常需要將列按照一定的要求進(jìn)行排序,本文就來介紹一下Pandas實(shí)現(xiàn)列(column)排序的幾種方法,感興趣的可以了解一下2023-11-11