PyAutoGUI?pywinauto?
任意の画面領域でキャプチャを手軽に取れないかなと思っていた所,以下の記事をみつけた.
[Python][Windows] Pythonでスクリーンキャプチャを行う-Qiita
「あー,pywinautoね」と思ったけど,よく考えたら知っているのは「PyAutoGUI」だった.
似た名前で機能もほぼ似ている,となると,気になるのは,じゃあ何が違うの?という.
物凄くざっくりとした理解だけど,PyAutoGUIはクロスプラットフォームなUI自動化モジュールなのに対して,pywinautoはWindowsに最適化された自動化モジュールの様だ.pywinautoがWin32APIを使いやすくラッピングしてくれたモジュールだとしたら,PyAutoGUIはGUI処理を一つずつインスタンス化して処理していかないといけない的な(この例えは適切ではないかもしれないけど).
Q: Can PyAutoGUI figure out where windows are or which windows are visible? Can it focus, maximize, minimize windows? Can it read the window titles?
A: Unfortunately not, but these are the next features planned for PyAutoGUI. This functionality is being implemented in a Python package named PyGetWindow, which will be included in PyAutoGUI when complete. [Frequently Asked Questions- PyAutoGUI より引用]
Windowsでの作業を自動化したいなら,ドキュメントをみる限り,pywinautoの方が断然楽そうだし,良さそう.
まあ,スクリーンショット程度なら,
・PyAutoGUI[4]
>>> import pyautogui
>>> pyautogui.screenshot(‘foo.png’) # returns a Pillow/PIL Image object, and saves it to a file
・pywinauto[5]
>>> from pywinauto import application
>>> app = application.Application().start(“notepad.exe”)
>>> app.Untitled_Notepad.CaptureAsImage().save(‘window.png’)
という感じであまり変わらないかな.
PyAutoGUIについて,これは結局PILのImageGrab.grab().save(‘foo.png’)っぽい.とすると,領域を指定してあげれば,その部分をキャプチャしてくれるので(それが引数「region=」か),一番簡単なのはマウスカーソルが合わさっているWindowをアクティブと判断してキャプチャするのが楽なんだろうな.と思ったけど,そのフォーカスされているウィンドウをみる方法がPyautoGUIには今の所無さそう.カーソル位置自体は
>> x, y = pyautogui.position()
で取得できるけど,仮にカーソル座標からフォーカスされているウィンドウを取得しようと思うと,win32apiやwin32guiとかを使う必要があって,じゃあカーソル座標も「win32api.GetCursorPos()」とか「win32gui.GetCursorInfo()」で良いじゃんっていう.現状,「キーボード操作やマウス操作」はPyAutoGUIが便利だけど,それ以外の処理は「うーん……」という感じなのかな.
—— pywin32, win32gui, win32api ——
取り敢えず,pywinautoを入れるのはまたいつかにして,PILとwin32gui or win32api(基本的にwin32guiを使っておけば良いっぽい?)を利用してみる.
%matplotlib inline from PIL import ImageGrab import pyautogui import win32gui #import win32api #Screen shot pyautogui.screenshot('screenshot001.png') # = pyautogui.screenshot('screenshot001.png') img = ImageGrab.grab() img.save('screenshot002.png') #active window screenshot(GetWindowRect) hwnd = win32gui.GetForegroundWindow() w0, h0, w1, h1 = win32gui.GetWindowRect(hwnd) img = ImageGrab.grab(bbox=(w0, h0, w1, h1)) img.save('screenshot003.png') #active window screenshot(GetClientRect) hwnd = win32gui.GetForegroundWindow() w0, h0, w1, h1 = win32gui.GetClientRect(hwnd) img = ImageGrab.grab(bbox=(w0, h0, w1, h1)) img.save('screenshot004.png') #focused window screenshot #x, y = win32api.GetCursorPos() #hwnd = win32gui.WindowFromPoint(win32api.GetCursorPos()) #_, _, (x, y) = win32gui.GetCursorInfo() #hwnd = win32gui.WindowFromPoint((x, y)) hwnd = win32gui.WindowFromPoint(win32gui.GetCursorPos()) w0, h0, w1, h1 = win32gui.GetWindowRect(hwnd) img = ImageGrab.grab(bbox=(w0, h0, w1, h1)) img.save('screenshot005.png')
目的は(ある程度)達成できたけど,マルチモニタ(デュアルディスプレイ)なんだけど,サブモニタの方を処理できない.モニタのハンドルを適切に処理してやれば良いんだろうか.モニタの情報を取得するのは簡単で,
>> win32api.GetMonitorInfo(win32api.MonitorFromPoint(win32api.GetCursorPos()))
でdict型データが得られる.全モニタの情報を得たい場合は,
>> win32api.EnumDisplayMonitors()
じゃあ,どうしたら良いか.この先がさっぱり.
(WinApi) ChangeDisplaySettingsEx does not work-StackOverflow
これが参考になるんだけど,もっとシンプルにできないものか.取り敢えず,試しにプライマリモニタを変更して,と思ったらうまくいかない.
import win32con #focused window screenshot with multi display(Failed) if win32api.GetMonitorInfo(win32api.MonitorFromPoint(win32api.GetCursorPos())).get('Flags'): hwnd = win32gui.WindowFromPoint(win32gui.GetCursorPos()) w0, h0, w1, h1 = win32gui.GetWindowRect(hwnd) img = ImageGrab.grab(bbox=(w0, h0, w1, h1)) img.save('screenshot006.png') else: win32api.ChangeDisplaySettingsEx( win32api.GetMonitorInfo(win32api.MonitorFromPoint(win32api.GetCursorPos())).get('Device'), None, win32con.CDS_SET_PRIMARY | win32con.CDS_UPDATEREGISTRY ) hwnd = win32gui.WindowFromPoint(win32gui.GetCursorPos()) w0, h0, w1, h1 = win32gui.GetWindowRect(hwnd) img = ImageGrab.grab(bbox=(w0, h0, w1, h1)) img.save('screenshot006_2.png')
参考:
[1] PyAutoGUI
[3] Windows Desktop GUI Automation using Python – Sleep vs tight loop-StackOverflow
[4] Screenshot Functions-PyAutoGUI
[5] Python Screenshot application window at any size-StackOverflow
関連:
Pythonでカーソルポジションやクリックアクションの取得や操作
追記:
・マルチモニタ画面全体スクリーンショット
win32ui vs wxPy screen capture multi monitor-BYTES
import win32gui import win32api import win32ui import win32con hwnd = win32gui.GetDesktopWindow() # get complete virtual screen including all monitors SM_XVIRTUALSCREEN = 76 SM_YVIRTUALSCREEN = 77 SM_CXVIRTUALSCREEN = 78 SM_CYVIRTUALSCREEN = 79 w = vscreenwidth = win32api.GetSystemMetrics(SM_CXVIRTUALSCREEN) h = vscreenheigth = win32api.GetSystemMetrics(SM_CYVIRTUALSCREEN) l = vscreenx = win32api.GetSystemMetrics(SM_XVIRTUALSCREEN) t = vscreeny = win32api.GetSystemMetrics(SM_YVIRTUALSCREEN) r = l + w b = t + h hwndDC = win32gui.GetWindowDC(hwnd) mfcDC = win32ui.CreateDCFromHandle(hwndDC) saveDC = mfcDC.CreateCompatibleDC() saveBitMap = win32ui.CreateBitmap() saveBitMap.CreateCompatibleBitmap(mfcDC, w, h) saveDC.SelectObject(saveBitMap) saveDC.BitBlt((0, 0), (w, h), mfcDC, (l, t), win32con.SRCCOPY) saveBitMap.SaveBitmapFile(saveDC, 'screencapture.bmp')
PAGE_BREAK: PageBreak
・マウスカーソルが合わさっているウィンドウだけスクリーンショット
import win32gui import win32api import win32ui import win32con hwnd = win32gui.WindowFromPoint(win32gui.GetCursorPos()) # get complete virtual screen including all monitors SM_XVIRTUALSCREEN = 76 SM_YVIRTUALSCREEN = 77 SM_CXVIRTUALSCREEN = 78 SM_CYVIRTUALSCREEN = 79 w = vscreenwidth = win32api.GetSystemMetrics(SM_CXVIRTUALSCREEN) h = vscreenheigth = win32api.GetSystemMetrics(SM_CYVIRTUALSCREEN) l = vscreenx = win32api.GetSystemMetrics(SM_XVIRTUALSCREEN) t = vscreeny = win32api.GetSystemMetrics(SM_YVIRTUALSCREEN) r = l + w b = t + h hwndDC = win32gui.GetWindowDC(hwnd) mfcDC = win32ui.CreateDCFromHandle(hwndDC) saveDC = mfcDC.CreateCompatibleDC() saveBitMap = win32ui.CreateBitmap() saveBitMap.CreateCompatibleBitmap(mfcDC, w, h) saveDC.SelectObject(saveBitMap) saveDC.BitBlt((0, 0), (w, h), mfcDC, (l, t), win32con.SRCCOPY) saveBitMap.SaveBitmapFile(saveDC, 'screenshot007.bmp')
PAGE_BREAK: PageBreak
import win32gui import win32api import win32ui import win32con from PIL import Image hwnd = win32gui.WindowFromPoint(win32gui.GetCursorPos()) # get complete virtual screen including all monitors SM_XVIRTUALSCREEN = 76 SM_YVIRTUALSCREEN = 77 SM_CXVIRTUALSCREEN = 78 SM_CYVIRTUALSCREEN = 79 w = vscreenwidth = win32api.GetSystemMetrics(SM_CXVIRTUALSCREEN) h = vscreenheigth = win32api.GetSystemMetrics(SM_CYVIRTUALSCREEN) l = vscreenx = win32api.GetSystemMetrics(SM_XVIRTUALSCREEN) t = vscreeny = win32api.GetSystemMetrics(SM_YVIRTUALSCREEN) r = l + w b = t + h hwndDC = win32gui.GetWindowDC(hwnd) mfcDC = win32ui.CreateDCFromHandle(hwndDC) saveDC = mfcDC.CreateCompatibleDC() saveBitMap = win32ui.CreateBitmap() saveBitMap.CreateCompatibleBitmap(mfcDC, w, h) saveDC.SelectObject(saveBitMap) saveDC.BitBlt((0, 0), (w, h), mfcDC, (l, t), win32con.SRCCOPY) #saveBitMap.SaveBitmapFile(saveDC, 'screenshot007.bmp') bmpinfo = saveBitMap.GetInfo() bmpstr = saveBitMap.GetBitmapBits(True) img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1) img.save('screenshot007.png')
PAGE_BREAK: PageBreak
・マウスカーソルが合わさっているウィンドウだけスクリーンショット(完成版)
import win32gui import win32ui import win32con from PIL import Image hwnd = win32gui.WindowFromPoint(win32gui.GetCursorPos()) w0, h0, w1, h1 = win32gui.GetWindowRect(hwnd) hwndDC = win32gui.GetWindowDC(hwnd) mfcDC = win32ui.CreateDCFromHandle(hwndDC) saveDC = mfcDC.CreateCompatibleDC() saveBitMap = win32ui.CreateBitmap() saveBitMap.CreateCompatibleBitmap(mfcDC, w1-w0, h1-h0) saveDC.SelectObject(saveBitMap) saveDC.BitBlt((0, 0), (w1-w0, h1-h0), mfcDC, (0, 0), win32con.SRCCOPY) bmpinfo = saveBitMap.GetInfo() bmpstr = saveBitMap.GetBitmapBits(True) img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1) img.save('screenshot008.png')
結局,ImageGrabの問題なんだなあ……(´・ω・`)
タイトルからだいぶかけ離れてしまったけど.
ピンバック: テンプレートマッチングした座標にマウスカーソルを自動で移動させたい的な? | 粉末@それは風のように (日記)
ピンバック: ウィンドウタイトルを指定してその領域だけを定期的に保存する - TECHBIRD | TECHBIRD - プログラミングを楽しく学ぼう