DEV Community

Lucas H.
Lucas H.

Posted on

Detecting colors

Hey everyone! I'm currently working on a script which detects the color of a few pixels on the screen about 30-60 times a second, to detect game states (health bar), live.

I'm doing this with Python, to integrate with OBS (a recording program).
I'm pretty new to Python, I've only worked with it for very short amounts of time.

I've looked on Stackoverflow for a small while and found someone using this script:
from PIL import ImageGrab
import time
image = ImageGrab.grab()
for y in range(0, 100, 10):
for x in range(0, 100, 10):
color = image.getpixel((x, y))


Here, they use the 'for' loop in Python, with variable y and x, with Imagegrab imported.
I do not understand how 'for y in range(0,100,10)', or 'for x in range [...]' works. How does this notation work? How would I customize it to fit the pixels I want to track, which are specific to only like three, all on different locations? (I don't need more, I only need to track one because one is more than enough to see the color, right?)

Thank you guys in advance for your answers, you're awesome.

-- Lucas.

Discussion (1)

ahferroin7 profile image
Austin S. Hemmelgarn

In Python, a for loop always takes a sequence to iterate over. This allows for a couple of optimizations in the language itself, but is mostly to allow for a concise expression of the very common case of looping over all the elements in a list (or all the key/value pairs in a dict). As a result, the only syntax for a for loop in Python is for <var> in <sequence>:, where <sequence> is the sequence to iterate over (or a generator, but that's not exactly relevant here). You can pass in literally anything that implements the collections.ABC.Sequence base class, which includes lists, dictionaries (each element in the sequence evalues to one key-value pair from the dictionary), tuples, strings (each element in the sequence is a character in the string), bytes objects, and a couple of other native types (plus any class that implements the above mentioned base class).

Obviously, this leads to complications when you just need to loop over a set of numbers. For that reason, Python (and most other languages that support this type of for loop) provides a native sequence type representing a range (in the mathematical sense) of numbers. The way you create an object of this type is with the range() function. The full syntax for invoking this function is range(start, stop, step), where start is the value to start with (defaults to 0 if both it and step are omitted), stop is an exclusive upper limit, and step is how much to add to start on each iteration (defaults to 1 if it is omitted).

Thus, range(10) expressed as a list would be all integers from 0 to 9. range(1, 5) would be the numbers 1 through 4, and range(0, 100, 10) would be every multiple of 10 from 0 through 90.

Ranges can also count downwards by specifying a negative step value and appropriate start and stop values.

Ultimately, this means that the following Python code:

for i in range(x, y):
    # Do something

is functionally equivalent to the following JavaScript (or C, or Java, or any of a number of other languages) code:

for (let i = x; i < y; i++) {
    // Do something

And this:

for item in items:
    # Do something

is equivalent to:

for (let i = 0; i < items.length; i++) {
    item = items[i]
    // Do something

Putting this together, the code you posted will look at every tenth pixel on the X axis at Y=0 up to and including X=90, then do the same for every tenth Y value up to and including 90. If you're just looking to get a rough estimate of the colors within a box of pixels starting at 0,0 and ending at 90,90, this is reasonably effective. You can tweak the start, stop, and step values on each loop to check any rectangle you want on the screen with any granularity you want.

However, this won't work if you need to check an oddly shaped region, like the health bars present in a number of more 'modern' games (like the Borderlands series for example, where the 'high' end of the health bar is at an angle, and the 'top' of the health indicator in the bar matches that angle, which would require you to only look at the bottom few rows of pixels of the health bar to get an accurate reading). In those cases, you'll have to get more creative about how you manipulate the coordinates, either by directly creating a list of the coordinates you want to check, or using some math to manipulate them inside the loops.