HOME Python download 書き込む

8. Menu を使ったシンプル 画像 Viewer


1. 初めに

前回は、widget 盛りだくさんの 画像 Viewer を作りましたが、次のような問題がありました。
  1. 画面がうるさい。
  2. 一覧表示できる画像が限られている。
  3. 背景色をつけた画像を保存できない。
この問題を解決するために今回メニューを使った画像 Vierer backgrounder.py を作りました。 backgrounder.py の特徴は、
  1. Tk.Menu を使っているので画面がシンプルである。
  2. ScrolledText に一覧表示画像を貼り付けたので、一覧表示できる画像に制限がない。
  3. 背景色をつけた画像を保存できる。
の3つです。生成する Widget は図1のようになります。


(a)


(b)

(c)

(d)

(e)

図1.backgrounder.py で生成する Window。
a) メイン window。
b) オリジナルサイズの画像に (GIF の場合)背景色をつけたもの。
c) 背景色調節用 window
d) ディレクトリ選択 window
e) 説明 window

2. プログラムの作成

[code 1] にソースコードを示します。

[code 1] (backgrounder.py)

 01:     #! /usr/bin/env python
 02:     # -*- coding: shift_jis -*-
 03:     
 04:     """
 05:     backgrounder.py 
 06:     
 07:     view images in a directory
 08:     
 09:     July 04, 2005
 10:     """
 11:     
 12:     import re
 13:     import os
 14:     import os.path as P
 15:     import Tkinter as Tk
 16:     import ScrolledText as S
 17:     import Image as I
 18:     import ImageTk as Itk
 19:     import ImageColor as Ic
 20:     import tkFileDialog as D
 21:     import tkMessageBox as M
 22:     
 23:     
 24:     SIZE = 100
 25:     IMAGR_TYPES = ['gif', 'png', 'bmp', 'jpg', 'tif', 'ppm']
 26:     MAM_COLN = 6
 27:     GEO_MAIN = '680x600+20+20'
 28:     GEO_SATE = '+730+50'
 29:     GEO_SCAL = '+730+400'
 30:     
 31:     
 32:     ## functions ------------------------------------------------
 33:     def get_size(tup):
 34:         """ It returns the size of images on the summary"""
 35:         x, y = tup
 36:         if (x<=100 and y<=100):
 37:             return (x, y)
 38:         elif x > y:
 39:             r = float(SIZE) / float(x)
 40:             return (100, int(y*r))
 41:         else:
 42:             r = float(SIZE) / float(y)
 43:             return (int(x*r), 100)
 44:             
 45:     
 46:     def make_regexp(types):
 47:         """ It returns the regular expression of the image file type""" 
 48:         str = "\.("
 49:         for k, v in types.iteritems():
 50:             if v.get():
 51:                 str += k + '|'
 52:     
 53:         str = str[0:-1] + ')$'
 54:         return re.compile(str, re.I)
 55:     
 56:     
 57:     ## classes -----------------------------------------------------------------------------------
 58:     class ImageLabel:
 59:         """ A Label class to show an image """
 60:     
 61:         id_original_size = None    # ID of the Label showing an original size image
 62:         bg_var = None              # variable for background color
 63:         image_file_now = None
 64:         
 65:         def __init__(self, stxt, image_file, img):
 66:             self.image= img
 67:             self.image_file = image_file
 68:             frame = Tk.Frame(stxt, height=115, width=100)
 69:             frame.pack_propagate(0)
 70:             txt_label=Tk.Label(frame, text=P.basename(self.image_file), font=('Helvetica', '8'))
 71:             txt_label.pack(side=Tk.BOTTOM)
 72:             
 73:             self.img_label=Tk.Label(frame, image=self.image)
 74:             self.img_label.pack(side=Tk.BOTTOM)
 75:     
 76:             stxt.window_create(Tk.END, align=Tk.BASELINE, padx=5, pady=5, window=frame)
 77:             self.img_label.bind('<Double-Button-1>', self.show)
 78:     
 79:         def show(self, event):
 80:             label = ImageLabel.id_original_size
 81:             if (label and label.winfo_exists()):
 82:                 top = label.winfo_toplevel()
 83:                 top.destroy()
 84:             top = Tk.Toplevel(self.img_label)
 85:             top.title(P.basename(self.image_file))
 86:             top.geometry(GEO_SATE)
 87:             img = I.open(self.image_file)
 88:             self.timg = Itk.PhotoImage(img)
 89:             label=Tk.Label(top, image=self.timg, bg=ImageLabel.bg_var.get())
 90:             label.pack()
 91:             ImageLabel.id_original_size = label
 92:             ImageLabel.image_file_now = self.image_file
 93:     
 94:     
 95:     class Frame(Tk.Frame):
 96:         """ The main class of this program. """
 97:         
 98:         def __init__(self, master=None):
 99:             Tk.Frame.__init__(self, master)
100:             self.master.title('BackGrounder')
101:             self.master.geometry(GEO_MAIN)
102:             self.cus_top = None
103:             self.fout = None
104:     
105:             ### Menu
106:             menu_bar = Tk.Menu(self, tearoff=0)
107:             # File
108:             menu_file = Tk.Menu(menu_bar, tearoff=0)
109:             menu_bar.add_cascade(label="File", menu=menu_file,  underline=0)
110:             menu_file.add_command(label="Browse Dir.", command=self.browse, underline=0, accelerator = 'Ctrl-O')
111:             menu_file.add_command(label="ReLoad", command=self.load_dir, underline=0, accelerator = 'Ctrl-R')
112:             menu_file.add_command(label="Save As", command=self.save_image, underline=0, accelerator = 'Ctrl-S')
113:             menu_file_type = Tk.Menu(menu_file, tearoff=0)
114:             menu_file.add_cascade(label="File Type", menu=menu_file_type, underline=0)
115:             menu_file.add_separator()
116:             menu_file.add_command(label="Exit", command=self.exit, underline=0 , accelerator = 'Ctrl-Q')
117:     
118:             menu_bar.add_command(label="Back Ground", command=self.cus_bg, underline=0)
119:             menu_bar.add_command(label="Help", command=self.show_info, underline=0)
120:     
121:             # short-cuts
122:             self.master.bind('<Control-KeyPress-o>', self.browse)
123:             self.master.bind('<Control-KeyPress-s>', self.save_image)
124:             self.master.bind('<Control-KeyPress-r>', self.load_dir)
125:             self.master.bind('<Control-KeyPress-q>', self.exit)
126:     
127:             # check buttons in menu_file_type
128:             self.var_type = dict()
129:             for i, image_type in enumerate(IMAGR_TYPES):
130:                 self.var_type[image_type] = Tk.IntVar()
131:                 menu_file_type.add_checkbutton(label=image_type, variable=self.var_type[image_type])
132:                 menu_file_type.invoke(i)
133:     
134:             ImageLabel.bg_var = Tk.StringVar()
135:             ImageLabel.bg_var.set('#FFFFFF')
136:     
137:             # add menu bar
138:             try:
139:                 self.master.config(menu=menu_bar)     # this required to show the menu bar
140:             except AttributeError:
141:                 self.master.Tk.call(master, "config", "-menu", menu_bar)
142:     
143:     
144:             self.once = False
145:             self.stxt = S.ScrolledText(self, bg=self.cget('bg'), cursor=self.cget('cursor'), state=Tk.DISABLED)
146:             self.stxt.pack(fill=Tk.BOTH, expand=1)
147:             self.pack(fill=Tk.BOTH, expand=1)
148:     
149:     
150:         def update(self, dir, types):
151:             self.stxt.configure(state=Tk.NORMAL)
152:             if self.once:
153:                 self.stxt.delete('1.0', Tk.END)
154:     
155:             self.once=True
156:             pat = make_regexp(types)
157:     
158:             for f in os.listdir(dir):
159:                 if pat.search(f):
160:                     file = P.join(dir, f)
161:                     img = I.open(file)
162:                     ImageLabel(self.stxt, file,
163:                                Itk.PhotoImage(img.resize(get_size(img.size), I.NEAREST)))
164:     
165:             self.stxt.configure(state=Tk.DISABLED)
166:     
167:     
168:         def browse(self, event=None):
169:             self.dir = D.askdirectory()
170:             if self.dir:
171:                 self.load_dir()
172:     
173:         def load_dir(self, event=None):
174:             self.update(self.dir, self.var_type)
175:             
176:     
177:         def show_info(self):
178:             M.showinfo(u"使い方",  u"メニューバーの File->Browse Dir. でディレクトリを選択してください。\n"
179:                                    u"そのディレクトリに含まれる画像ファイルの一覧が表示されます。\n"
180:                                    u"一覧にある画像をクリックすると、別窓でオリジナルサイズの画像が表示されます。\n\n"
181:                                    u"透過型 GIF の場合は背景色をつけることができます。\n"
182:                                    u"メニューバーの Back Ground をクリックすると\n"
183:                                    u"赤、緑、青用の3つのスケールがある Window が現れるので、\n"
184:                                    u"それで背景色を調節できます。\n\n"
185:                                    u"背景色をつけた画像は File->SaveAs により保存することができます。"
186:                                    )
187:     
188:         def cus_bg(self):
189:             if not (self.cus_top and  self.cus_top.winfo_exists()):
190:                 self.cus_top = Tk.Toplevel(self)
191:                 self.cus_top.title('Create Back Ground')
192:                 self.cus_top.geometry(GEO_SCAL)
193:                 cusf = Tk.Frame(self.cus_top)
194:                 cusf.pack(fill=Tk.BOTH, padx=10, pady=10)
195:                 self.scale = dict()
196:                 for i, color in enumerate(('red', 'green', 'blue')):
197:                     l=Tk.Label(cusf, text=color+': ', anchor=Tk.W, fg=color, font=('Helvetica', '10', 'bold'))
198:                     l.grid(row=i, column=0, sticky=Tk.W)
199:                     self.scale[color] = Tk.Scale(cusf, orient=Tk.HORIZONTAL, length=300, from_=0, to=255, 
200:                                                  command=self.customize_bg, tickinterval=50)
201:                     self.scale[color].set(255)
202:                     self.scale[color].grid(row=i, column=1)
203:     
204:     
205:         def customize_bg(self, event):
206:             ImageLabel.bg_var.set('#%02X%02X%02X' %
207:                             (self.scale['red'].get(), self.scale['green'].get(), self.scale['blue'].get()))
208:             bg1 = ImageLabel.bg_var.get()
209:             label = ImageLabel.id_original_size
210:             if label and label.winfo_exists():
211:                 label.configure(bg=bg1)
212:                 top = label.winfo_toplevel()
213:                 top.focus_set()
214:     
215:         def save_image(self, event=None):
216:             self.fout = D.asksaveasfilename(initialdir=self.dir, initialfile=self.fout and P.basename(self.fout) or None)
217:             if self.fout:
218:                 img=I.open(ImageLabel.image_file_now)
219:                 imgc = img.mode == 'RGB' and img or img.convert('RGB')
220:                 bg1 = Ic.getrgb(ImageLabel.bg_var.get())         # background color
221:                 ls=[]                                            # a sequence to store image data
222:                 for c0 in imgc.getdata():
223:                     if(c0==(255,255,255)):
224:                         ls.append(bg1)
225:                     else:
226:                         ls.append(c0)
227:                         
228:                 imgc.putdata(ls)
229:                 imgc.save(self.fout)
230:     
231:         def exit(self, event=None):
232:             self.master.destroy()
233:     
234:     ##------------------------------------------------ 
235:     
236:     if __name__ == '__main__':
237:         f = Frame()
238:         f.mainloop()

ご覧になるとわかるように7割以上 viewer.py と同じです。 プログラムが若干長いので、部分ごとに説明していきたいと思います。見出しのカッコ内の数字はコードの行数です。

2.1. import するモジュールの宣言

12-21 行目でre, os, os.path(P), Tkinter(Tk), ScrolledText(S), Image(I), ImageTk(Itk), ImageColor(Ic), tkFIleDialog(D), tkMessageBox(M) の10個のモジュールを読み込んでいます。 このうち、ScrolledText については次回以降詳しく説明します。 ImageColor モジュールは色文字列を整数のタプル (r, g, b) に変換するとき使います。

2.2. 定数の宣言

24--29 行目で定数を宣言しています。viewer.py と同じものです。

2.3. 関数の宣言

33--54 行目で2つの関数を定義しています。これも viewer.py と同じものです。

2.4. class ImageLabel)

一覧表示する画像のクラスです。クリックされると原寸大のイメージを別窓で表示します。(58--92)

2.5. class Frame(Tk.Frame):

メインのクラスの説明です。__init__ でメニューバーを定義しています。(95--229)