Final edit at the top for visibility:
Thanks to IInspectable for the suggestion, targeting the specific window DC is much faster, less than a second.
def get_win_px(x=0, y=0, name="Device Manager"):
hwnd = win32gui.FindWindow(None, name) # warning no error handling
hdc = win32gui.GetWindowDC(hwnd)
color = win32gui.GetPixel(hdc, x, y)
win32gui.ReleaseDC(0, hdc)
return color
Using timeit to get an idea of how long each approach takes to execute.
Testing shows win32 is fastest on my PC, as you've also found:
get_px_ctypes: 16.759 ms
get_px_win32: 16.689 ms
get_px_pya: 16.798 ms
get_win_px: 0.970 ms
press_win32: 4.701 ms
press_pya: 104.358 ms
dpress_win32: 54.339 ms
pressed_win32: 0.031 ms
pressed_kbd: 5.729 ms
Note that press_win32 has no delay between key down and up events, dpress_win32 has a 50ms delay after the key down event. The delays after the key down event are probably not impacting the key detection in your game and might prevent duplicate key presses for the same colour bar.
Interestingly the get pixel functions are comparable.
So I would recommend an approach without the check for Q to exit, use Ctrl+C instead, keboard.pressed() adds ~6ms per loop.
It's not clear what benefit looking at multiple pixels provides; it opens the possibility of missing the first location check when checking the second if the bar moves fast enough.
Profiling indicates most of the time is spent in threads waiting for a lock or the response to be added to a queue. I've excluded calls below whose total time is less than a millisecond. This is not exciting or actionable information, other than to guide us to seek an alternative avoiding the full desktop.
>>> import cProfile
>>> cProfile.run("detect_and_press()", sort='time')
47528 function calls (46822 primitive calls) in 657.508 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
4941/4236 637.206 0.129 637.384 0.150 {method 'acquire' of '_thread.lock' objects}
705 19.867 0.028 657.260 0.932 threading.py:323(wait)
2/1 0.180 0.090 0.017 0.017 {built-in method builtins.exec}
706 0.096 0.000 0.160 0.000 _winkeyboard.py:498(process_key)
705 0.020 0.000 677.175 0.961 queue.py:154(get)
706 0.019 0.000 0.162 0.000 _winkeyboard.py:531(low_level_keyboard_handler)
1 0.017 0.017 0.017 0.017 test_px_access.py:33(detect_and_press)
706 0.015 0.000 0.051 0.000 __init__.py:222(direct_callback)
1412 0.010 0.000 0.010 0.000 {method 'release' of '_thread.lock' objects}
706 0.008 0.000 0.008 0.000 {built-in method _thread.allocate_lock}
706 0.007 0.000 0.010 0.000 __init__.py:211(pre_process_event)
706 0.007 0.000 0.027 0.000 queue.py:122(put)
1412 0.006 0.000 0.006 0.000 {built-in method builtins.sorted}
2118 0.005 0.000 0.020 0.000 threading.py:394(notify)
706 0.004 0.000 0.013 0.000 _keyboard_event.py:24(__init__)
706 0.004 0.000 0.008 0.000 _canonical_names.py:1233(normalize_name)
706 0.003 0.000 0.009 0.000 queue.py:57(task_done)
2118 0.003 0.000 0.005 0.000 threading.py:302(__exit__)
2824 0.003 0.000 0.003 0.000 {built-in method builtins.len}
3530 0.002 0.000 0.002 0.000 {method '__exit__' of '_thread.lock' objects}
2824 0.002 0.000 0.006 0.000 threading.py:314(_is_owned)
706 0.002 0.000 0.002 0.000 {built-in method builtins.all}
2118 0.002 0.000 0.003 0.000 threading.py:299(__enter__)
1412 0.002 0.000 0.003 0.000 queue.py:209(_qsize)
2118 0.002 0.000 0.002 0.000 {method '__enter__' of '_thread.lock' objects}
706 0.002 0.000 0.002 0.000 {method 'get' of 'dict' objects}
706 0.002 0.000 0.003 0.000 threading.py:311(_acquire_restore)
706 0.001 0.000 0.001 0.000 {built-in method time.time}
706 0.001 0.000 0.003 0.000 threading.py:424(notify_all)
1190 0.001 0.000 0.001 0.000 {built-in method builtins.isinstance}
484 0.001 0.000 0.002 0.000 __init__.py:135(is_modifier)
706 0.001 0.000 0.002 0.000 queue.py:217(_get)
706 0.001 0.000 0.001 0.000 queue.py:213(_put)
1412 0.001 0.000 0.001 0.000 {method 'append' of 'collections.deque' objects}
372 0.001 0.000 0.001 0.000 {method 'lower' of 'str' objects}
706 0.001 0.000 0.001 0.000 {method 'popleft' of 'collections.deque' objects}
706 0.001 0.000 0.001 0.000 threading.py:308(_release_save)
706 0.001 0.000 0.001 0.000 {method 'remove' of 'collections.deque' objects}
Full Code listing, edited to include release of device context handle:
import ctypes
import time
import win32api, win32con, win32gui
import pyautogui
import keyboard
def get_px_ctypes(x=0, y=0):
hdc = ctypes.windll.user32.GetDC(0)
color = ctypes.windll.gdi32.GetPixel(hdc, x, y) # & 0xFF # AND to isolate red portion
ctypes.windll.user32.ReleaseDC(0, hdc)
return color
def get_px_win32(x=0, y=0):
hdc = win32gui.GetDC(0)
color = win32gui.GetPixel(hdc, x, y) # & 0xFF # AND to isolate red portion
win32gui.ReleaseDC(0, hdc)
return color
def get_win_px(x=0, y=0, name="Device Manager"):
hwnd = win32gui.FindWindow(None, name) # warning no error handling
hdc = win32gui.GetWindowDC(hwnd)
color = win32gui.GetPixel(hdc, x, y)
win32gui.ReleaseDC(0, hdc)
return color
def press_win32(key_code=0x20):
win32api.keybd_event(key_code, 0, 0, 0)
win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_KEYUP, 0)
def dpress_win32(key_code=0x20):
"""Include a delay between key down and key up events"""
win32api.keybd_event(key_code, 0, 0, 0)
time.sleep(0.05)
win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_KEYUP, 0)
def pressed_win32(key_code=0x51): # 0x51 == q
return win32api.GetAsyncKeyState(key_code) & 0x8000
def get_px_pya(x=0, y=0):
return pyautogui.pixel(x, y) # [0] # (R, G, B)
def press_pya(key="space"):
pyautogui.press(key)
def pressed_kbd(key="q"):
return keyboard.is_pressed("q")
def detect_and_press(x=0, y=0):
try:
hdc = ctypes.windll.user32.GetDC(0)
while True:
if ctypes.windll.gdi32.GetPixel(hdc, x, y) & 0xFF > 150: # compare red value
win32api.keybd_event(0x20, 0, 0, 0)
win32api.keybd_event(0x20, 0, win32con.KEYEVENTF_KEYUP, 0)
except KeyboardInterrupt:
return
finally:
ctypes.windll.user32.ReleaseDC(0, hdc)
if __name__ == "__main__":
import timeit
loops = 100 # how many times to repeat the function call
multiplier = 1_000 / loops # convert to ms
for function in get_px_ctypes, get_px_win32, get_px_pya, get_win_px, press_win32, press_pya, dpress_win32, pressed_win32, pressed_kbd:
fn = function.__name__
print(f"{fn}:\t{timeit.timeit(f"{fn}()", setup=f"from __main__ import {fn}", number=loops)*multiplier:8,.3f} ms")
sleep()in yourpress_spacebarfunction? How did you pick those values?