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 依存なので
関係ない人も多いかもしれません)。
さて、次回はタイマー機能(一定秒数後にキャプチャーする機能)をつけて
みることにします。

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

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

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

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