pythonで画面キャプチャーツールを作ってみよう(2)

   2021/05/26

さて今日はtkinterというGUIを使ってみましょう。
キャプチャー動作用の button と
キャプチャー結果を表示するための canvas を使います。
クラスとして書いた方が良いのですが、まずはベタで
書いてみます。

#! /usr/bin/python3

import tkinter as tk
from PIL import Image,ImageGrab,ImageTk

img=None
pimg=None

def capture():
    global img,pimg
    img=ImageGrab.grab(bbox=(0,0,200,160))
    pimg=ImageTk.PhotoImage(image=img)
    canvas.create_image( 0,0, anchor="nw", image=pimg )

root=tk.Tk()
button=tk.Button(root,text='Capture',command=capture)
button.pack()
canvas=tk.Canvas(root,bg='black')
canvas.pack()

root.mainloop()

def capture(): には、ボタンを押した時に実行する処理を
書いています。この関数(capture)をボタンを作る時に command= で
与えます。
ImageGrab.grab()でキャプチャーするのですが、手抜きで座標を
(0,0)-(200,160) に決め打ちしています(ので、実用性はありません)。
canvasに画像を表示するには、create_image()というメソッドを
使うのですが、表示できる画像のタイプが PhotoImage というモノ
なので、ImageTkを使って変換しています。
anchor=”nw”は左上(north west)を起点にする指定で、canvasの(0,0)
に画像の左上をあわせて表示させています。

tkinterの部品(buttonやcanvasの)配置は、親(入れ物)を指定して
おいて、部品をpack()します。Javaの場合は入れ物(panelとかframe)
から子(buttonなどの部品)を add するのですが、tkinterは逆です。
canvasは他と見分けられるように、背景色(bg)を黒に指定しています。
root.mainloop()はおまじないです。これによって画面に窓(root)が
表示されます。

とりあえず動作確認できたら、改善点を考えます。
tkinterのcanvasは、描画したモノを保持するので、上記のプログラム
では、Captureボタンを押すたびに描画物が積み重なっていきます。
これはあまりよくないので、次はcreate_imageする前に前回の描画物を
消す(delete)するようにします。
キャプチャーエリアを決め打ちするのは、使い物にならないのですが
座標を数値で指定するのも面倒ですから、窓(canvas)自体をかぶせた
領域をキャプチャーすることにします。
それと、せっかくキャプチャーした画像を保存できるようにしましょう。

#! /usr/bin/python3

import time
import tkinter as tk
from tkinter import filedialog
from PIL import Image,ImageGrab,ImageTk

img=None
pimg=None
drawid=None

def capture():
    global img,pimg,drawid
    x=canvas.winfo_rootx()  # canvas.winfo_x() は加算不要でした
    y=canvas.winfo_rooty()  # canvas.winfo_y() は加算不要でした
    w=canvas.winfo_width()
    h=canvas.winfo_height()
    geo=root.wm_geometry() # 窓の位置を記録
    root.withdraw() # 消す
    time.sleep(0.1)
    img=ImageGrab.grab(bbox=(x,y,x+w,y+h))
    root.deiconify() # 再表示
    root.wm_geometry(geo) # 消した位置に戻す
    if drawid!=None:
        canvas.delete(drawid)
        pimg=None
        drawid=None
    pimg=ImageTk.PhotoImage(image=img)
    drawid=canvas.create_image(
        w//2,h//2,
        image=pimg
    )


def save():
    path_name=filedialog.asksaveasfilename()
    if path_name:
        img.save(path_name)


root=tk.Tk()
root.geometry("200x200")
frame=tk.Frame(root)
captbtn=tk.Button(frame,text='Capture',command=capture)
savebtn=tk.Button(frame,text='Save',command=save)
captbtn.pack(side='left')
savebtn.pack(side='left')
frame.pack()
canvas=tk.Canvas(root,bg='black',width=1920,height=1080)
canvas.pack()

root.mainloop()

pack()は特に指定しなければ縦に詰めていきます。
ボタンを2つ横に並べる為に、Fremeを用意して side=’left’指定を
つけてpack()しています。このFrameはJavaで言うとpanel相当です。
canvasの位置とサイズをキャプチャー範囲とするために
winfo_x()やwinfo_width()で位置やサイズを取得しています。
winfo_x()では、親(root)に対する相対位置が得られるので
親窓(root)の位置 winfo_rootx() と加算して画面上の位置を得ます。

キャプチャーする際には自分自身(root窓)をwithdraw()を使って消します。
そうしないと自分自身をキャプチャーしてしまいますから。
キャプチャー後にroot窓を戻すには deiconify() を使います。
ただ、元の位置には戻らないので、消す前に場所サイズを記憶して
その位置に再配置します。wm_geometry()の wm は WindowManagerの
ことで、窓の装飾(タイトルバーや枠など)を含む座標になります。

保存は、filedialog.asksaveasfilename() で、パス名(ファイル名)
を得て、saveします。asksaveasfilename()やsave()が細かいことを
全部やってくれるので簡単ですね。
その他、canvasに画像を表示する位置を中央にしています。
anchor指定をしない時は画像の中央が起点になるので、それを
canvasの中央(//2)に指定しています。

ざざっとキャプチャーするには充分だと思いますが、微調整すなわち
ちょっと大きくとか、1ドット狭く、などの指定はやりにくいですね。
「窓かぶせ」の操作と、座標値(数字での)の微調整の両方が必要な
気がしてきます。
それと、即時キャプチャーだけでなく、タイマー付きキャプチャーも
欲しいですね。プルダウンメニューなど撮るのに、キャプチャー指定
してから操作が必要ですから。
そういった事を考えながら、次回はclass化もしてみることにします。

この記事へのコメントはこちら

メールアドレスは公開されませんのでご安心ください。
また、* が付いている欄は必須項目となりますので、必ずご記入をお願いします。

内容に問題なければ、下記の「コメント送信」ボタンを押してください。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)