タイトルの通りpythonのtkinterでイメージビューワーを作っています。まだ途中ですが、一区切りついたので続きを作る未来の自分へメモを残します。
できること
- ダイアログを開いて画像を選択したら、画像を表示してくれる。ファイルの名前も表示してくれる。
- 画像をドラッグしたら、動いてくれる。
- ホイールをぐりぐりしたら、そこを中心に画像を拡大縮小してくれる。
- 画像を画面ぴったりに表示してくれる。
- 矢印キー(←→)を押したら、画像を切り替えてくれる。ファイルの名前も切り替えてくれる。
頑張ったところ
1ホイールをぐりぐりしたら、そこを中心に画像を拡大縮小
Canvas座標と画像の座標を取得して頑張った。
・Screenクラスの関数zoom_in_outのとこ
2矢印キー(←→)を押したら、画像とファイルの名前を切り替え
ファイル名表示用とは別にStringVerをつくってtraceで外から監視した。
・Dialogクラスのself.label_changed = tk.StringVar()のとこ
コード
import os, pathlib import tkinter as tk from tkinter import filedialog from PIL import Image, ImageTk class Screen(tk.Canvas): def __init__(self, master=None): super().__init__(master, bg="#203744") self.ratio = 1 self.set_events() pass def set_events(self): self.bind("<ButtonPress-2>", self.move_start) self.bind("<B2-Motion>", self.move_move) self.bind("<MouseWheel>", self.zoom_in_out) self.bind("<Double-Button-1>", self.fit_image2screen) pass def get_image(self, img_path): self.img_path = img_path self.img = Image.open(self.img_path) pass def draw(self): self.tkimg = ImageTk.PhotoImage(self.draw_img) self.create_image(0, 0, image=self.tkimg, anchor=tk.NW) pass def move_start(self, event): self.scan_mark(event.x, event.y) pass def move_move(self, event): self.scan_dragto(event.x, event.y, gain=1) pass def zoom_in_out(self, event): self.old_ratio = self.ratio if event.delta > 0 and self.ratio < 1.5: self.ratio += 0.1 elif event.delta < 0 and self.ratio > 0.2: self.ratio -= 0.1 self.draw_img = self.img.resize((int(self.img.width * self.ratio), int(self.img.height * self.ratio))) self.draw() x = event.x + ((self.ratio - self.old_ratio) / self.old_ratio) * self.canvasx(event.x) y = event.y + ((self.ratio - self.old_ratio) / self.old_ratio) * self.canvasy(event.y) self.scan_mark(int(x), int(y)) self.scan_dragto(event.x, event.y, gain=1) self.unbind("<MouseWheel>") self.after(10, self.set_events) pass def fit_image2screen(self, event=None): if self.winfo_width() <= self.winfo_height(): self.ratio = round(self.winfo_width() / self.img.width, 1) else: self.ratio = round(self.winfo_height() / self.img.height, 1) self.draw_img = self.img.resize((int(self.img.width * self.ratio), int(self.img.height * self.ratio))) self.draw() x = - self.canvasx(0) + self.draw_img.width / 2 y = - self.canvasy(0) + self.draw_img.height / 2 self.scan_mark(int(x), int(y)) self.scan_dragto(int(self.winfo_width()/2), int(self.winfo_height()/2), gain=1) pass class Dialog(tk.Frame): def __init__(self, master=None): super().__init__(master) self.ftype = [("画像ファイル", "*.jpg; *.png; *.jpeg")] self.iDir = os.path.abspath(os.path.dirname(__file__)) icon_path = os.path.join(os.path.dirname(__file__), "image.ico") self.button_icon = ImageTk.PhotoImage(Image.open(icon_path)) self.create_widgets() pass def create_widgets(self): tk.Button(self, image=self.button_icon, command=self.open_file).grid(row=0, column=0) self.label_text = tk.StringVar() tk.Label(self, textvariable=self.label_text, width=20, anchor=tk.W).grid(row=0, column=1) self.label_changed = tk.StringVar() pass def open_file(self): self.file_path = filedialog.askopenfilename(filetypes=self.ftype, initialdir=self.iDir) self.dir_path, self.file_name = os.path.split(self.file_path) self.label_text.set(self.file_name) self.iDir = os.path.dirname(self.dir_path) self.label_changed.set(True) pass class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.master.geometry("1280x720") self.master.title("いめーじびゅーわー") self.master.rowconfigure(0, weight=True) self.master.columnconfigure(0, weight=True) self.grid(sticky=(tk.NW, tk.SE)) self.rowconfigure(0, weight=True) self.columnconfigure(0, weight=True) self.create_widgets() self.set_events() self.exts = [".jpg", ".jpeg", ".JPG", ".png", ".PNG"] self.file_index = None pass def create_widgets(self): self.screen = Screen(self) self.screen.grid(row=0, column=0, sticky=(tk.NW, tk.SE)) self.dialog = Dialog(self) self.dialog.grid(row=0, column=1, sticky=tk.SE) pass def set_events(self): self.dialog.label_changed.trace("w", lambda *arge: self.file_opened()) self.dialog.bind_all("<Right>", self.next_img) self.dialog.bind_all("<Left>", self.prev_img) pass def file_opened(self): if self.dialog.file_path != "": self.img_path = self.dialog.file_path self.screen.get_image(self.img_path) self.screen.fit_image2screen() self.dir_path = self.dialog.dir_path self.file_list = sorted([os.path.split(str(x))[-1] for x in pathlib.Path(self.dir_path).glob("*") if x.suffix in self.exts]) self.file_index = self.file_list.index(os.path.split(self.img_path)[-1]) print(self.file_list, self.file_index) pass def next_img(self, event=None): if self.file_index == None: self.dialog.open_file() elif self.file_index < len(self.file_list)-1: self.file_index += 1 self.new_img(self.file_index) pass def prev_img(self, event=None): if self.file_index == None: self.dialog.open_file() elif 0 < self.file_index: self.file_index -= 1 self.new_img(self.file_index) pass def new_img(self, file_index): self.file_name = self.file_list[self.file_index] self.img_path = os.path.join(self.dir_path, self.file_name) self.screen.get_image(self.img_path) self.screen.fit_image2screen() self.dialog.label_text.set(self.file_name) pass def main(): root = tk.Tk() app = Application(master=root) app.mainloop() if __name__ == "__main__": main()
今後追加したいこと
右上のアイテル部分に写真のタイトルと撮影の日付やちょっとしたメモなんかをかけるようにしたい
最後に
ほんとにメモみたいな記事になってしまいましたが、私と同じようにtkinterでビューワーを作ろうとしていて始めの一歩で躓いてしまった方のお役に立てたらうれしいです。