tkinterでイメージビューワー作ってる途中のメモ

f:id:touchP:20201112234146p:plain

 

タイトルの通りpythontkinterでイメージビューワーを作っています。まだ途中ですが、一区切りついたので続きを作る未来の自分へメモを残します。

できること

  1. ダイアログを開いて画像を選択したら、画像を表示してくれる。ファイルの名前も表示してくれる。
  2. 画像をドラッグしたら、動いてくれる。
  3. ホイールをぐりぐりしたら、そこを中心に画像を拡大縮小してくれる。
  4. 画像を画面ぴったりに表示してくれる。
  5. 矢印キー(←→)を押したら、画像を切り替えてくれる。ファイルの名前も切り替えてくれる。

 頑張ったところ

1ホイールをぐりぐりしたら、そこを中心に画像を拡大縮小

Canvas座標と画像の座標を取得して頑張った。

 ・Screenクラスの関数zoom_in_outのとこ

f:id:touchP:20201113001000j:plain

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でビューワーを作ろうとしていて始めの一歩で躓いてしまった方のお役に立てたらうれしいです。