pythonで画面キャプチャーツールを作ってみよう(3)
最初に先日(2)の2番めのコードを修正したご連絡。
キャプチャー位置を求める式は以下のように canvas.winfo_rootx(),
canvas.winfo_rooty()だけでOKでした。
x=canvas.winfo_rootx() # canvas.winfo_x() は加算不要でした
y=canvas.winfo_rooty() # canvas.winfo_y() は加算不要でした
※ 先日の記事は修正済です。
さて、今日はclass化のお話。
ベタで書くと、関数をまたいで使う変数を global にする必要が生じます。
例えば、先のコードでは img という変数を、capture()と save()の両方で
使うので、globalにしています。
コードが小さい場合や global変数が少なければそのままでも良いのですが
規模が大きくなると、だんだん分からなくなってきます。
コードを書いている時は分かっていても、翌日、あるいは1週間後などに
見ると、何だっけコレ? どこで何に使ってるんだっけ? になります。
関連する関数をまとめて classにして、その中で使う共通変数をクラス内
変数とすると、他の(class外の)変数と区別しやすくなります。
pythonのクラスの書き方は、javaに比べるとシンプルです。例えば
class CapWin(tk.Frame):
という感じ。
この場合、CapWinがクラス名で()でくくっている(tk.Frame)は親クラスの
指定です。javaで書くと
public class CapWin extends Frame {
みたいなものです。javaの場合はクラス名と同一のメソッド(関数)を
コンストラクタとして用意しますが、pythonの場合は __init__(self)
という関数がコンストラクタになります。super()は親クラスの事で
super().__init__() は、親クラスのコンストラクタを呼んでいます。
self というのはクラス内を示すおまじないで self.hoge と書くと
クラス内変数に、self.fuga() と書くとクラス内のメソッド(関数)を
呼ぶことになります。
※ self は javaでいうと this に相当します。
※※ クラス内メソッドの宣言は、def fuga(self): などと書きます。
何も継承しない(親クラスを持たない)場合は
class CapWin():
あるいは
class CapWin:
と書きます。
では実例として、先日のコードをclass化してみます。
#! /usr/bin/python3
import time
import tkinter as tk
from tkinter import filedialog
from PIL import Image,ImageGrab,ImageTk
class CapWin(tk.Frame):
def __init__(self,master):
super().__init__(master)
self.img=None
self.pimg=None
self.drawid=None
self.frame=tk.Frame(master)
self.captbtn=tk.Button(self.frame,text='Capture',command=self.capture)
self.savebtn=tk.Button(self.frame,text='Save',command=self.save)
self.captbtn.pack(side='left')
self.savebtn.pack(side='left')
self.frame.pack()
self.canvas=tk.Canvas(root,bg='black',width=1920,height=1080)
self.canvas.pack()
def capture(self):
x=self.canvas.winfo_rootx() # +canvas.winfo_x() は不要
y=self.canvas.winfo_rooty() # +canvas.winfo_x() は不要
w=self.canvas.winfo_width()
h=self.canvas.winfo_height()
geo=root.wm_geometry() # 窓の位置を記録
root.withdraw() # 消す
time.sleep(0.1)
self.img=ImageGrab.grab(bbox=(x,y,x+w,y+h))
root.deiconify() # 再表示
p=geo.split('+')
if p[1]=="0" and p[2]=="0": # 起動後1回目は +0+0 が返るので
if x!=0 or y!=0: # 実際の位置と異なっていれば
geo=p[0] # +0+0 を削除
root.wm_geometry(geo) # 消した位置に戻す
if self.drawid!=None:
self.canvas.delete(self.drawid)
self.pimg=None
self.drawid=None
self.pimg=ImageTk.PhotoImage(image=self.img)
self.drawid=self.canvas.create_image(
w//2,h//2,
image=self.pimg
)
def save(self):
imgext=('.png','.bmp','.jpg')
path_name=filedialog.asksaveasfilename(
filetypes=[('image',imgext),('All','.*')])
if path_name :
if not path_name[-4:] in imgext :
pn=pn+".png"
self.img.save(path_name)
# end of class CapWin(tk.Frame) #############################
root=tk.Tk()
root.geometry("200x200")
capwin=CapWin(master=root)
capwin.pack()
root.mainloop()
やたらと self. がくっついてきますが、慣れましょう。
メソッド内で、self.がついていない変数は局所変数で、関数を抜けると
消えてしまいます。img はキャプチャーしたあと、saveするのに使います
からself.img として保持します。pimgはcanvasへの表示用ですが
これも保持しておく必要があるので self.pimg としています。
クラスの終わりを示す記号は無くて、インデントが無くなる(左端から
始まる)とクラス外という事になります。上記ではわかりやすいように
# end of class CanWin … とコメントを入れていますが、無くても
かまいません。
先日のコードと全く同じだと、面白くないので save の際には
filetypes=[('image',imgext),('All','.*')])
をつけて
filedialogに表示されるファイルを絞りこんでいます。(Allを選べば
全ファイルの表示も可能)
また、直接ファイル名を入力した時用に拡張子チェックをして
.png / .bmp / .jpg 以外なら .png を付けるようにしました。
それと、キャプチャー時に窓を消して戻す際に、1回目は画面中央だったのが
画面左上に飛んでしまうのを対処してみました(WindowManager 依存なので
関係ない人も多いかもしれません)。
さて、次回はタイマー機能(一定秒数後にキャプチャーする機能)をつけて
みることにします。
この記事へのコメントはこちら