1. Get the screen zoom ratio
from ctypes import wintypes import ctypes HORZRES = 8 LOGPIXELSX = 118 def get_scale_factor() -> float: user32 = .user32 gdi32 = .gdi32 # Define HDC and UINT types HDC = UINT = # Define the parameter type and return type of GetDC and GetDeviceCaps = [] = HDC = [HDC, UINT] = # Get the device context dc = (None) widthScale = (dc, HORZRES) width = (dc, LOGPIXELSX) scale = width / widthScale return scale
2. Get the pixel color at the specified coordinates of the screen
import ctypes from ctypes import wintypes from typing import Sequence, Generator user32 = .user32 gdi32 = .gdi32 # Define the typeHWND = HDC = HBITMAP = class BITMAPINFOHEADER(): _fields_ = [ ("biSize", ), ("biWidth", ), ("biHeight", ), ("biPlanes", ), ("biBitCount", ), ("biCompression", ), ("biSizeImage", ), ("biXPelsPerMeter", ), ("biYPelsPerMeter", ), ("biClrUsed", ), ("biClrImportant", ) ] class BITMAPINFO(): _fields_ = [ ("bmiHeader", BITMAPINFOHEADER), ("bmiColors", * 3) ] def get_pixel_color(coords: Sequence[tuple[int, int]], hwnd: HWND) -> Generator[tuple[int, int, int], None, None]: rect = () (hwnd, (rect)) width = - height = - # Create a memory device context hdc_src = (hwnd) hdc_dst = (hdc_src) bmp = (hdc_src, width, height) (hdc_dst, bmp) # Use BitBlt to copy window content to memory device context (hdc_dst, 0, 0, width, height, hdc_src, 0, 0, 0x00CC0020) # SRCCOPY # Get bitmap information bmi = BITMAPINFO() = (BITMAPINFOHEADER) = width = -height # Negative value indicates bottom-up = 1 = 32 = 0 # Create a buffer and get bitmap data buffer = ctypes.create_string_buffer(width * height * 4) (hdc_dst, bmp, 0, height, buffer, (bmi), 0) # Free up resources (bmp) (hdc_dst) (hwnd, hdc_src) # traverse the specified coordinates and return the pixel color 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. A simple use case
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 = .user32 gdi32 = .gdi32 HWND = HDC = HBITMAP = class BITMAPINFOHEADER(): _fields_ = [ ("biSize", ), ("biWidth", ), ("biHeight", ), ("biPlanes", ), ("biBitCount", ), ("biCompression", ), ("biSizeImage", ), ("biXPelsPerMeter", ), ("biYPelsPerMeter", ), ("biClrUsed", ), ("biClrImportant", ) ] class BITMAPINFO(): _fields_ = [ ("bmiHeader", BITMAPINFOHEADER), ("bmiColors", * 3) ] def get_pixel_color(coords: Sequence[Tuple[int, int]], hwnd: HWND) -> Generator[Tuple[int, int, int], None, None]: rect = () (hwnd, (rect)) width = - height = - hdc_src = (hwnd) hdc_dst = (hdc_src) bmp = (hdc_src, width, height) (hdc_dst, bmp) (hdc_dst, 0, 0, width, height, hdc_src, 0, 0, 0x00CC0020) # SRCCOPY bmi = BITMAPINFO() = (BITMAPINFOHEADER) = width = -height # Negative value indicates bottom-up = 1 = 32 = 0 buffer = ctypes.create_string_buffer(width * height * 4) (hdc_dst, bmp, 0, height, buffer, (bmi), 0) (bmp) (hdc_dst) (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 = .(None, ()) if not hwnd: raise ValueError("Cannot get the window handle.") return hwnd def download_image(url): response = (url) if response.status_code == 200: return (BytesIO()) else: raise Exception(f"Failed to download image: HTTP {response.status_code}") def display_image_in_label(image): photo = (image) label = (root, image=photo) = photo # Keep references to PhotoImage to prevent garbage collection () def show_color(event): hwnd = get_window_handle(root) x, y = , # Note that the coordinates here are relative to the window's coordinates, and the sequence of multiple coordinate points passed in get_pixel_color should be # In addition, in order to efficiently obtain the colors of multiple points in the same picture, I used the generator to lazy load here, so please traverse the iterator completely when obtaining data result = get_pixel_color([(x, y)], hwnd) colors = [i for i in result] print(f"{, }: {colors}") if __name__ == "__main__": root = () 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) ("Test Sample") (geometry) ("<Motion>", show_color) image_url = "/th/id/R-C.475631ce281b88c3cd465761b37c5256?rik=ZFMiTYFwaPypTQ&riu=http%3a%2f%%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}") (root, text="Failed to load image.").pack() ()
4. Summary
The above method is much more efficient than the usual method of using PIL, because it is based on IO screenshot operations, and frequent IO operations make it very inefficient to simply access the screen pixels.
The above method uses BitBlt. BitBlt is an efficient bitmap operation method that can copy the contents of the window into the context of the memory device and then get the pixel color through GetPixel or directly accessing the bitmap data. Just like Su Access, its performance is significantly stronger than the former. For more details on Window API operations, please refer to the official documentation:
Windows GDI) (Bitmap Functions - Win32 apps | Microsoft Learn
This is the article about Python using its own module to achieve efficient operation of screen pixels. For more related content on Python screen pixels, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!