【Python】スクリーンショットをマルチモニタ対応化
PILがマルチディスプレイに非対応
pythonのPILを使ってスクリーンショットを取るプログラムを作ったのだけど、メインディスプレイの範囲しか保存対象にならない!!
なぜかと思って色々調べてみたところ、PILモジュールのImageGrab.grab()自体がマルチモニタをサポートしてないようでした。
マルチディスプレイに対応させよう
対応させる方法はいろんなところに色々書かれてるけど、ぶっちゃけ素人だからもっとわかりやすく書いてくれ!!(記事は本当にありがとうございます!!!)
と思ったので、ないなら備忘録にもなるし自分でわかりやすいように書けば自分と同じ困っている初心者にはきっとわかりやすいはず。
ただし、この修正で他の部分に影響が出たらごめんなさい。
修正はたった2カ所!!
1カ所目変更箇所:
C:\Users\【ユーザー名】\AppData\Local\Programs\Python\Python38\Lib\site-packages\pyscreeze
ファイル名:【__init__.py】
記述箇所:450行目付近
変更後の記述内容
@requiresPillow
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(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
中央付近にある
im = ImageGrab.grab()
を
im = ImageGrab.grab(all_screens=True)
に書き換えただけです。
2カ所目変更箇所:
C:\Users\【ユーザー名】\AppData\Local\Programs\Python\Python38\Lib\site-packages\PIL
ファイル名:ImageGrab.py
記述箇所:28行目付近
変更後の記述内容
def grab(bbox=None, include_layered_windows=False, all_screens=True, xdisplay=None):
if xdisplay is None:
if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
subprocess.call(["screencapture", "-x", filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
if bbox:
im_cropped = im.crop(bbox)
im.close()
return im_cropped
return im
elif sys.platform == "win32":
offset, size, data = Image.core.grabscreen_win32(
include_layered_windows, all_screens
)
im = Image.frombytes(
"RGB",
size,
data,
# RGB, 32-bit line padding, origin lower left corner
"raw",
"BGR",
(size[0] * 3 + 3) & -4,
-1,
)
if bbox:
x0, y0 = offset
left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
return im
# use xdisplay=None for default display on non-win32/macOS systems
if not Image.core.HAVE_XCB:
raise OSError("Pillow was built without XCB support")
size, data = Image.core.grabscreen_x11(xdisplay)
im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
if bbox:
im = im.crop(bbox)
return im
最上段にある
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
を
def grab(bbox=None, include_layered_windows=False, all_screens=True, xdisplay=None):
に書き換えただけです。
実際に作ったコード
大したものではありませんが、実際に作って思うように動いてくれたコードです
import tkinter as tk
from PIL import ImageGrab
import os
# キャプチャ対象のウィンドウを作成
root = tk.Tk()
root.title("Capture Window")
root.wm_attributes("-transparentcolor", "red")
root.wm_attributes("-topmost", 1)
root.geometry("500x500")
# キャプチャ対象のフレームを作成
capture_frame = tk.Frame(root, bg="red")
capture_frame.pack(fill=tk.BOTH, expand=tk.YES)
# capture_frameの背景を透明にする
if tk.TkVersion >= 8.6:
capture_frame.configure(bg='red')
else:
capture_frame.configure(bg='red')
# スクリーンショットを撮る関数
def capture_screenshot():
# キャプチャする領域を指定
x = capture_frame.winfo_rootx()
y = capture_frame.winfo_rooty()
width = capture_frame.winfo_width()
height = capture_frame.winfo_height()
box = (x, y, x + width, y + height)
# スクリーンショットを撮る
image = ImageGrab.grab(bbox=box)
# 保存するファイル名を決定する
folder = os.path.join(os.path.expanduser('~'), 'Pictures', 'screenshots')
if not os.path.exists(folder):
os.makedirs(folder)
num_existing_files = len(os.listdir(folder))
file_name = "screenshot"+str(num_existing_files + 1 ) + ".png"
for num in range(num_existing_files+5):
if not file_name in os.listdir(folder):
break
else:
file_name = "screenshot"+str(num_existing_files+ 1 + num )
file_path = os.path.join(folder, f"screenshot{num_existing_files + 1 + num}.png")
# スクリーンショットを保存する
image.save(file_path)
# 保存するファイル名を指定して保存
#image.save("capture.png")
def open_my_documents():
os.startfile(os.path.expanduser("~/Pictures/Screenshots"))
button1 = tk.Button(root, text="保存先を開く", command=open_my_documents)
button1.pack(side="right")
# スクリーンショットボタンを作成
capture_button = tk.Button(root, text=" 画像保存 ", command=capture_screenshot, font=("Arial", 13))
capture_button.pack(side="right")
root.mainloop()
動きとしては、
tkinterで枠を作成
枠内を透明化することで疑似的に後ろに表示されているものをtkinter内に表示
ボタンを押すことでmypicture内のscreenshotsフォルダへ連番で画像を保存
連番が中抜けになっていても上書きではない追加になるように(連番自体は無茶苦茶になってもいい)
おまけで保存先を開くコマンドも追加
アクティブではない時も常に前面に表示
なぜかわかりませんが、tkinterの透明化と常に前面表示をすることでtkinter枠内のクリックが貫通してオーバーレイのような動きになってしまいました。
使い勝手が良いためとりあえずそのままにしています。
もしかしたら
pyautoguiの画像認識でマルチディスプレイ環境下のサブディスプレイ側で動かない問題も解決するかもしれない
なんかあれが動かないのもImageGrabがメインディスプレイ側しか画像取得してないからとか書かれていた人がいた気がします。
ただ試せる環境と時間がないので、どなたか試していただければ幸いです
さて、今日も金型設計頑張ろう