import win32gui
import win32ui
from ctypes import windll
from contextlib import contextmanager
from collections import namedtuple
class NoSuchWindowError(Exception):
"ウィンドウが見つからなかった場合に投げる例外"
class PrintScreenError(Exception):
"PrintScreenが失敗した場合に投げる例外"
class WindowChapter():
def __init__(self):
self.hwnd_dc = None
self.mem_dc = None
self.src_dc = None
self.bmp = None
def __del__(self):
if self.src_dc is not None:
self.src_dc.DeleteDC()
self.src_dc = None
if self.mem_dc is not None:
self.mem_dc.DeleteDC()
self.mem_dc = None
if self.bmp is not None:
win32gui.DeleteObject(self.bmp.GetHandle())
self.bmp = None
if self.hwnd_dc is not None:
win32gui.ReleaseDC(self.hwnd, self.hwnd_dc)
self.hwnd_dc = None
@contextmanager
def capture_window(self, window_name):
self.hwnd = win32gui.FindWindow(None, window_name)
if self.hwnd == 0:
raise NoSuchWindowError
# ウィンドウサイズを取得
left, top, right, bottom = win32gui.GetWindowRect(self.hwnd)
width = right - left
height = bottom - top
# デバイスコンテキストを取得
self.hwnd_dc = win32gui.GetWindowDC(self.hwnd)
self.src_dc = win32ui.CreateDCFromHandle(self.hwnd_dc)
self.mem_dc = self.src_dc.CreateCompatibleDC()
# ビットマップを作成
self.bmp = win32ui.CreateBitmap()
self.bmp.CreateCompatibleBitmap(self.src_dc, width, height)
# ビットマップをデバイスコンテキストに関連付け
self.mem_dc.SelectObject(self.bmp)
result = windll.user32.PrintWindow(
self.hwnd, self.mem_dc.GetSafeHdc(), 0)
if result == 0:
raise PrintScreenError
buf = self.bmp.GetBitmapBits(True)
Bitmap = namedtuple('Bitmap', ['data', 'width', 'height'])
yield Bitmap(buf, width, height)
def main():
wc = WindowChapter()
with wc.capture_window('NoxPlayer') as bmp:
# PILを使う場合
from PIL import Image
im = Image.frombuffer(
'RGB', (bmp.width, bmp.height), bmp.data, 'raw', 'BGRX', 0, 1)
im.save("test.png")
# Numpyを使う場合(OpenCVはこっち)
import numpy as np
import matplotlib.pyplot as plt
data = np.frombuffer(bmp.data, dtype=np.uint8)
data = data.reshape(bmp.height, bmp.width, 4)
data = data[:, :, [2, 1, 0, 3]] # BGR->RGB
plt.imshow(data)
plt.show()
if __name__ == "__main__":
main()
aW1wb3J0IHdpbjMyZ3VpCmltcG9ydCB3aW4zMnVpCmZyb20gY3R5cGVzIGltcG9ydCB3aW5kbGwKZnJvbSBjb250ZXh0bGliIGltcG9ydCBjb250ZXh0bWFuYWdlcgpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBuYW1lZHR1cGxlCgoKY2xhc3MgTm9TdWNoV2luZG93RXJyb3IoRXhjZXB0aW9uKToKICAgICLjgqbjgqPjg7Pjg4njgqbjgYzopovjgaTjgYvjgonjgarjgYvjgaPjgZ/loLTlkIjjgavmipXjgZLjgovkvovlpJYiCgoKY2xhc3MgUHJpbnRTY3JlZW5FcnJvcihFeGNlcHRpb24pOgogICAgIlByaW50U2NyZWVu44GM5aSx5pWX44GX44Gf5aC05ZCI44Gr5oqV44GS44KL5L6L5aSWIgoKCmNsYXNzIFdpbmRvd0NoYXB0ZXIoKToKICAgIGRlZiBfX2luaXRfXyhzZWxmKToKICAgICAgICBzZWxmLmh3bmRfZGMgPSBOb25lCiAgICAgICAgc2VsZi5tZW1fZGMgPSBOb25lCiAgICAgICAgc2VsZi5zcmNfZGMgPSBOb25lCiAgICAgICAgc2VsZi5ibXAgPSBOb25lCgogICAgZGVmIF9fZGVsX18oc2VsZik6CiAgICAgICAgaWYgc2VsZi5zcmNfZGMgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIHNlbGYuc3JjX2RjLkRlbGV0ZURDKCkKICAgICAgICAgICAgc2VsZi5zcmNfZGMgPSBOb25lCiAgICAgICAgaWYgc2VsZi5tZW1fZGMgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIHNlbGYubWVtX2RjLkRlbGV0ZURDKCkKICAgICAgICAgICAgc2VsZi5tZW1fZGMgPSBOb25lCiAgICAgICAgaWYgc2VsZi5ibXAgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIHdpbjMyZ3VpLkRlbGV0ZU9iamVjdChzZWxmLmJtcC5HZXRIYW5kbGUoKSkKICAgICAgICAgICAgc2VsZi5ibXAgPSBOb25lCiAgICAgICAgaWYgc2VsZi5od25kX2RjIGlzIG5vdCBOb25lOgogICAgICAgICAgICB3aW4zMmd1aS5SZWxlYXNlREMoc2VsZi5od25kLCBzZWxmLmh3bmRfZGMpCiAgICAgICAgICAgIHNlbGYuaHduZF9kYyA9IE5vbmUKCiAgICBAY29udGV4dG1hbmFnZXIKICAgIGRlZiBjYXB0dXJlX3dpbmRvdyhzZWxmLCB3aW5kb3dfbmFtZSk6CiAgICAgICAgc2VsZi5od25kID0gd2luMzJndWkuRmluZFdpbmRvdyhOb25lLCB3aW5kb3dfbmFtZSkKICAgICAgICBpZiBzZWxmLmh3bmQgPT0gMDoKICAgICAgICAgICAgcmFpc2UgTm9TdWNoV2luZG93RXJyb3IKCiAgICAgICAgIyDjgqbjgqPjg7Pjg4njgqbjgrXjgqTjgrrjgpLlj5blvpcKICAgICAgICBsZWZ0LCB0b3AsIHJpZ2h0LCBib3R0b20gPSB3aW4zMmd1aS5HZXRXaW5kb3dSZWN0KHNlbGYuaHduZCkKICAgICAgICB3aWR0aCA9IHJpZ2h0IC0gbGVmdAogICAgICAgIGhlaWdodCA9IGJvdHRvbSAtIHRvcAoKICAgICAgICAjIOODh+ODkOOCpOOCueOCs+ODs+ODhuOCreOCueODiOOCkuWPluW+lwogICAgICAgIHNlbGYuaHduZF9kYyA9IHdpbjMyZ3VpLkdldFdpbmRvd0RDKHNlbGYuaHduZCkKICAgICAgICBzZWxmLnNyY19kYyA9IHdpbjMydWkuQ3JlYXRlRENGcm9tSGFuZGxlKHNlbGYuaHduZF9kYykKICAgICAgICBzZWxmLm1lbV9kYyA9IHNlbGYuc3JjX2RjLkNyZWF0ZUNvbXBhdGlibGVEQygpCgogICAgICAgICMg44OT44OD44OI44Oe44OD44OX44KS5L2c5oiQCiAgICAgICAgc2VsZi5ibXAgPSB3aW4zMnVpLkNyZWF0ZUJpdG1hcCgpCiAgICAgICAgc2VsZi5ibXAuQ3JlYXRlQ29tcGF0aWJsZUJpdG1hcChzZWxmLnNyY19kYywgd2lkdGgsIGhlaWdodCkKCiAgICAgICAgIyDjg5Pjg4Pjg4jjg57jg4Pjg5fjgpLjg4fjg5DjgqTjgrnjgrPjg7Pjg4bjgq3jgrnjg4jjgavplqLpgKPku5jjgZEKICAgICAgICBzZWxmLm1lbV9kYy5TZWxlY3RPYmplY3Qoc2VsZi5ibXApCiAgICAgICAgcmVzdWx0ID0gd2luZGxsLnVzZXIzMi5QcmludFdpbmRvdygKICAgICAgICAgICAgc2VsZi5od25kLCBzZWxmLm1lbV9kYy5HZXRTYWZlSGRjKCksIDApCiAgICAgICAgaWYgcmVzdWx0ID09IDA6CiAgICAgICAgICAgIHJhaXNlIFByaW50U2NyZWVuRXJyb3IKCiAgICAgICAgYnVmID0gc2VsZi5ibXAuR2V0Qml0bWFwQml0cyhUcnVlKQogICAgICAgIEJpdG1hcCA9IG5hbWVkdHVwbGUoJ0JpdG1hcCcsIFsnZGF0YScsICd3aWR0aCcsICdoZWlnaHQnXSkKICAgICAgICB5aWVsZCBCaXRtYXAoYnVmLCB3aWR0aCwgaGVpZ2h0KQoKCmRlZiBtYWluKCk6CiAgICB3YyA9IFdpbmRvd0NoYXB0ZXIoKQoKICAgIHdpdGggd2MuY2FwdHVyZV93aW5kb3coJ05veFBsYXllcicpIGFzIGJtcDoKICAgICAgICAjIFBJTOOCkuS9v+OBhuWgtOWQiAogICAgICAgIGZyb20gUElMIGltcG9ydCBJbWFnZQogICAgICAgIGltID0gSW1hZ2UuZnJvbWJ1ZmZlcigKICAgICAgICAgICAgJ1JHQicsIChibXAud2lkdGgsIGJtcC5oZWlnaHQpLCBibXAuZGF0YSwgJ3JhdycsICdCR1JYJywgMCwgMSkKICAgICAgICBpbS5zYXZlKCJ0ZXN0LnBuZyIpCgogICAgICAgICMgTnVtcHnjgpLkvb/jgYbloLTlkIgoT3BlbkNW44Gv44GT44Gj44GhKQogICAgICAgIGltcG9ydCBudW1weSBhcyBucAogICAgICAgIGltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKICAgICAgICBkYXRhID0gbnAuZnJvbWJ1ZmZlcihibXAuZGF0YSwgZHR5cGU9bnAudWludDgpCiAgICAgICAgZGF0YSA9IGRhdGEucmVzaGFwZShibXAuaGVpZ2h0LCBibXAud2lkdGgsIDQpCiAgICAgICAgZGF0YSA9IGRhdGFbOiwgOiwgWzIsIDEsIDAsIDNdXSAgIyBCR1ItPlJHQgogICAgICAgIHBsdC5pbXNob3coZGF0YSkKICAgICAgICBwbHQuc2hvdygpCgoKaWYgX19uYW1lX18gPT0gIl9fbWFpbl9fIjoKICAgIG1haW4oKQo=