目的
pyautoguiをマルチディスプレイ環境下でも使いたい、より正確にはマルチディスプレイ環境下のサブディスプレイ内で画像認識させたい。
環境
python 3.8.5
pyautogui 0.9.50
pyscreez 0.1.26
OS windows限定
調査内容
参考:pyautoguiのlocateOnScreen()がマルチディスプレイ非対応な理由
上記記事にて、以下の記述を発見。
PILのimageGrab.grab()がデュアルディスプレイに対応していない
これを読んでおや?と思い、imageGrab.grab()の定義を確認してみた。
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
all_screens=False
それっぽい引数を確認、いつからかは知らないが少なくともpython3.8.5ではgrab関数も変更されているっぽい。
#実装内容
OSがWindowsの時、pyautoguiのlocateOnScreen()内でimage.grab()を呼び出しているのは以下の関数。
def _screenshot_win32(imageFilename=None, region=None):
"""
TODO
"""
# TODO - Use the winapi to get a screenshot, and compare performance with ImageGrab.grab()
# https://stackoverflow.com/a/3586280/1893164
im = ImageGrab.grab()
if region is not None:
assert len(region) == 4, 'region argument must be a tuple of four ints'
region = [int(x) for x in region]
im = im.crop((region[0], region[1], region[2] + region[0], region[3] + region[1]))
if imageFilename is not None:
im.save(imageFilename)
return im
なので、imageGrab.grab()に引数を渡してやる、それだけ。
def _screenshot_win32(imageFilename=None, region=None):
"""
TODO
"""
# TODO - Use the winapi to get a screenshot, and compare performance with ImageGrab.grab()
# https://stackoverflow.com/a/3586280/1893164
#im = ImageGrab.grab()
im = ImageGrab.grab(all_screens=True)
if region is not None:
assert len(region) == 4, 'region argument must be a tuple of four ints'
region = [int(x) for x in region]
im = im.crop((region[0], region[1], region[2] + region[0], region[3] + region[1]))
if imageFilename is not None:
im.save(imageFilename)
return im
結果
サブディスプレイでも画像認識ができるようになった。
問題点
ディスプレイの並びが
[1][2][3]
や
[1][2]
[3][4]
のように行儀よく並んでいるならばよいのだが
[2][1][3] (我が家の配置)
のように並んでいると、locateOnScreenが返すのは[2]のLeftTopを(0,0)とした座標なのに対し、windows側が要求するのは[1]のLeftTopを(0,0)とした座標となってしまう。
この問題はwin32apiにモニターを列挙して座標を返すapiがあるので、次回それを利用して解決したいと思う。
今回はここまで。