前回の記事:pyautoguiを【超適当に】マルチディスプレイ環境に対応させる Part1
目的
pyautogui.locateOnScreen()で得た座標を、そのままpyautogui.click()に渡したい。
環境
python 3.8.5
pyautogui 0.9.50
pyscreez 0.1.26
OS windows限定
win32api
上記はpipからインストールできないので、リンクから自分の環境にあったものをダウンロードしてインストールする必要がある。
調査
locateOnXXX()系の関数は大体locateOnScreen()を呼び出しているので、この関数を修正する。
def locateOnScreen(image, minSearchTime=0, **kwargs):
"""TODO - rewrite this
minSearchTime - amount of time in seconds to repeat taking
screenshots and trying to locate a match. The default of 0 performs
a single search.
"""
start = time.time()
while True:
try:
screenshotIm = screenshot(region=None) # the locateAll() function must handle cropping to return accurate coordinates, so don't pass a region here.
retVal = locate(image, screenshotIm, **kwargs)
try:
screenshotIm.fp.close()
except AttributeError:
# Screenshots on Windows won't have an fp since they came from
# ImageGrab, not a file. Screenshots on Linux will have fp set
# to None since the file has been unlinked
pass
if retVal or time.time() - start > minSearchTime:
return retVal
except ImageNotFoundException:
if time.time() - start > minSearchTime:
if USE_IMAGE_NOT_FOUND_EXCEPTION:
raise
else:
return None
実装内容
win32apiをインポート
インポート箇所でプラットフォームがwin32と判定されてる場所ならどこでも良いです。
私は一番最初の25行目以降の箇所に記載しました。
from PIL import Image
from PIL import ImageOps
from PIL import ImageDraw
if sys.platform == 'win32': # TODO - Pillow now supports ImageGrab on macOS.
# win32apiのインポートを追加
import win32api
from PIL import ImageGrab
_PILLOW_UNAVAILABLE = False
locateOnScreen()の修正
retValに画像認識とマッチしたleft,top,width,heightの座標及び値を格納しているので、戻り値として返す前にretValの中身をちょちょいと書き換える。
def locateOnScreen(image, minSearchTime=0, **kwargs):
"""TODO - rewrite this
minSearchTime - amount of time in seconds to repeat taking
screenshots and trying to locate a match. The default of 0 performs
a single search.
"""
start = time.time()
while True:
try:
screenshotIm = screenshot(region=None) # the locateAll() function must handle cropping to return accurate coordinates, so don't pass a region here.
retVal = locate(image, screenshotIm, **kwargs)
# ここから
if not(retVal == None) and sys.platform == 'win32':
displays = win32api.EnumDisplayMonitors()
left_min = min([display[2][0] for display in displays])
top_min = min([display[2][1] for display in displays])
retVal = Box(
left = retVal[0] + left_min,
top = retVal[1] + top_min,
width = retVal[2],
height = retVal[3]
)
# ここまで追加分
try:
screenshotIm.fp.close()
except AttributeError:
# Screenshots on Windows won't have an fp since they came from
# ImageGrab, not a file. Screenshots on Linux will have fp set
# to None since the file has been unlinked
pass
if retVal or time.time() - start > minSearchTime:
return retVal
except ImageNotFoundException:
if time.time() - start > minSearchTime:
if USE_IMAGE_NOT_FOUND_EXCEPTION:
raise
else:
return None
_screenshot_win32()の修正内容の確認
前回の記事で修正した_screenshot_win32()も、念の為確認。
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
結果
pyautogui.locateOnScreen()で取得した座標をpyautogui.click()で使えるようになった。
問題点
画像認識速度が落ちる
当然といえば当然ではあるのですが、2つなら画像サイズは倍3つなら3倍になるので、それに比例するように画像認識速度が落ちる。
pyautoguiが公式にマルチディスプレイに対応しないのも、この速度の問題が大きいんじゃないかなというのが個人的な感想。
pyscreezを参照する他のパッケージに影響が出る
他に参照しているパッケージがあるのかどうかは知りませんが、直接書き換える関係上現行の環境に影響がないとは言い切れません。
お遊び環境で使うに留めましょう、間違っても現状運用している重要なシステム環境等でやることではないです。
そんな人はいないと思いますが。
最後に
速度低下の問題が大きいので、allscreensよりは特定のdisplayを指定出来るような改修の方が運用には現実的というのが結論です。
というわけで超適当なマルチディスプレイ対応でした。
暇があればそっちの方もやってみたいですね。