18

I need to get the color of some pixels on the screen or from the active window, and I need to do so quickly. I've tried using win32gui and ctypes/windll, but they're much too slow. Each of these programs gets the color of 100 pixels:

import win32gui
import time
time.clock()
for y in range(0, 100, 10):
    for x in range(0, 100, 10):
        color = win32gui.GetPixel(win32gui.GetDC(win32gui.GetActiveWindow()), x , y)
print(time.clock())

and

from ctypes import windll
import time
time.clock()
hdc = windll.user32.GetDC(0)
for y in range(0, 100, 10):
    for x in range(0, 100, 10):
        color = windll.gdi32.GetPixel(hdc, x, y)
print(time.clock())

Each of these takes about 1.75 seconds. I need a program like this to take less than 0.1 seconds. What's making it so slow?

I'm working with Python 3.x and Windows 7. If your solution requires I use Python 2.x, please link me to an article showing how to have Python 3.x and 2.x both installed. I looked, but couldn't figure out how to do this.

user202729
  • 3,358
  • 3
  • 25
  • 36
dln385
  • 11,630
  • 12
  • 48
  • 58

5 Answers5

15

This is better than using getpixel all the time and works faster.

import ImageGrab

px = ImageGrab.grab().load()
for y in range(0, 100, 10):
    for x in range(0, 100, 10):
        color = px[x, y]

Reference: Image.load

Oleh Prypin
  • 33,184
  • 10
  • 89
  • 99
12

Thanks to Margus' direction, I focused on getting the image before extracting the pixel information. Here's a workable solution using the Python Imaging Library (PIL), which requires Python 2.x.

import ImageGrab
import time
time.clock()
image = ImageGrab.grab()
for y in range(0, 100, 10):
    for x in range(0, 100, 10):
        color = image.getpixel((x, y))
print(time.clock())

I don't think it gets any simpler than that. This takes (on average) 0.1 seconds, which is a little slower than I'd like but fast enough.

As for having Python 3.x and 2.x both installed, I separated that into a new question. I'm still having some trouble with it, but it's generally working.

Community
  • 1
  • 1
dln385
  • 11,630
  • 12
  • 48
  • 58
  • 1
    PIL is abandoned (its main developer died), Pillow is a maintained fork of PIL, and the Pillow equivalent is called `PIL.ImageGrab.grab` – hanshenrik Sep 25 '22 at 18:21
8

Disabling Windows Desktop Composition speeds pixel up reading A LOT.

Computer -> Properties -> Advanced system settings -> Performance -> desktop composition [ ] (warning this disables Windows's transparency effects)

Python 2.7 (Should be same for 3.x)

win32gui.GetPixel()     #1.75s => 20ms
ctypes.windll.gdi32.GetPixel() #1.75s => 3ms (fastest)
image.getpixel()        # 0.1s => 50ms
px[]                    # 0.1s => 50ms

AutoIt for comparison

$timer = TimerInit()

For $x = 0 To 100 Step 10
    For $y = 0 To 100 Step 10
        PixelGetColor($x,$y) ;slow => 1ms
    Next
Next

ConsoleWrite("Time: " & TimerDiff($timer)/1000 & @CRLF)
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
Jaakko
  • 4,674
  • 2
  • 26
  • 20
3

I had this same exact problem, and solved it (in Java, in C#). The main idea behind the solution is GetPixel from screen is slow, and you can't fix that. But as you need some pixels, you can get a bunch of them all at once.

The time that it took to get 64 pixels was 98 times faster.

Community
  • 1
  • 1
Margus
  • 19,694
  • 14
  • 55
  • 103
  • So it sounds like you're suggesting that I capture the whole screen (say as a bitmap) then get the pixel values from that. This sounds like it might be a job for Python Imaging Library (PIL) (which requires python 2.x) or ImageMagick (which I'm not sure what what version of python it requires). If anyone has suggestions, feel free to chime in. – dln385 Sep 27 '10 at 18:45
  • Yes, basically you need CopyFromScreen method equivalent from .net, with Bitmap class where you can access single pixel by its coordinates. And yes, people have done this before and there are lots of libraries that you can import, choose what fits you best. – Margus Sep 27 '10 at 19:21
0

try using the pyautogui library

import pyautogui

r, g, b = pyautogui.pixel(x, y)
print("The cursor is currently at: " + str(x) + ", " + str(y))