Pythonのcanvasに表示した四角形を変形する

Pythonのtkinterのcanvasに四角形を表示して、それを変形させます。領域を選択するときによく使われる枠のようなものです。

目次

  1. 枠で領域を選択したい
  2. どのようにするのか
  3. 試してみた

枠で領域を選択したい

ペイントのような画像を扱うアプリでは、矩形の領域を選択するときに画像上に枠を表示してガイドにするものが多いです。これができると便利そうですよね。

そこで、Pythonのtkinterのcanvasウィジェットに四角形(rectangle)を表示して、マウスに合わせてその形を変形させてみます。

どのようにするのか

マウスの左ボタンを押したときのポインタの座標を、枠(rectangle)の起点とします。

マウスをドラッグしているときのポインタの座標を、枠の終点とします。

ドラッグしたときに枠が表示されていなければ、起点と終点の座標を利用して新しい枠を作ります。もしすでに枠があるのであれば、枠の終点の座標を変更します。

枠(rectangle)を作ると、アイテムIDが振られます。そのIDを変数に格納しておきます。起動時と、枠を消したときには、その変数にNoneを入れます。こうすることで、アイテムIDの変数を参照すれば枠が存在するかどうかがわかります。

また、枠の座標を変更するには、canvasオブジェクトのcoordsメソッドを使用します。(coordsメソッドはアイテムの座標の取得もできます。)

tkinter.Canvas.coords(item, x0, y0, x1, y1)

x0, y0, x1, y1 = tkinter.Canvas.coords(item)
変数 内容
item int アイテムID。
x0 int アイテムの左上端のx座標。
y0 int アイテムの左上端のy座標。
x1 int アイテムの右下端のx座標。
y1 int アイテムの右下端のy座標。

試してみた

import tkinter
import tkinter.ttk

class Application(tkinter.Frame):

    CANVAS_WIDTH = 300
    CANVAS_HEIGHT = 300

    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('tkinter canvas trial')
        self.pack()
        self.create_widgets()
        self.startup()

    def create_widgets(self):
        self.start_x = tkinter.StringVar()
        self.start_y = tkinter.StringVar()
        self.current_x = tkinter.StringVar()
        self.current_y = tkinter.StringVar()

        self.label_description = tkinter.ttk.Label(self, text='Mouse position')
        self.label_description.grid(row=0, column=1)
        self.label_start_x = tkinter.ttk.Label(self, textvariable=self.start_x)
        self.label_start_x.grid(row=1, column=1)
        self.label_start_y = tkinter.ttk.Label(self, textvariable=self.start_y)
        self.label_start_y.grid(row=2, column=1)
        self.label_current_x = tkinter.ttk.Label(self, textvariable=self.current_x)
        self.label_current_x.grid(row=3, column=1)
        self.label_current_y = tkinter.ttk.Label(self, textvariable=self.current_y)
        self.label_current_y.grid(row=4, column=1)

        self.select_all_button = tkinter.ttk.Button(self, text='Select All', command=self.select_all)
        self.select_all_button.grid(row=5, column=1)

        self.test_canvas = tkinter.Canvas(self, bg='lightblue',
            width=self.CANVAS_WIDTH+1, height=self.CANVAS_HEIGHT+1,
            highlightthickness=0)
        self.test_canvas.grid(row=0, column=0, rowspan=6, padx=10, pady=10)
        self.test_canvas.bind('<ButtonPress-1>', self.start_pickup)
        self.test_canvas.bind('<B1-Motion>', self.pickup_position)

    def startup(self):
        self.rect_start_x = None
        self.rect_start_y = None
        self.rect = None

    def start_pickup(self, event):
        if 0 <= event.x <= self.CANVAS_WIDTH and 0 <= event.y <= self.CANVAS_HEIGHT:
            self.start_x.set('x : ' + str(event.x))
            self.start_y.set('y : ' + str(event.y))
            self.rect_start_x = event.x
            self.rect_start_y = event.y

    def pickup_position(self, event):
        if 0 <= event.x <= self.CANVAS_WIDTH and 0 <= event.y <= self.CANVAS_HEIGHT:
            self.current_x.set('x : ' + str(event.x))
            self.current_y.set('y : ' + str(event.y))
            if self.rect:
                self.test_canvas.coords(self.rect,
                    min(self.rect_start_x, event.x), min(self.rect_start_y, event.y),
                    max(self.rect_start_x, event.x), max(self.rect_start_y, event.y))
            else:
                self.rect = self.test_canvas.create_rectangle(self.rect_start_x,
                    self.rect_start_y, event.x, event.y, outline='red')

    def select_all(self):
        if self.rect:
            self.test_canvas.coords(self.rect, 0, 0, self.CANVAS_WIDTH, self.CANVAS_HEIGHT)
        else:
            self.rect = self.test_canvas.create_rectangle(0, 0,
                self.CANVAS_WIDTH, self.CANVAS_HEIGHT, outline='red')
        x0, y0, x1, y1 = self.test_canvas.coords(self.rect)
        self.start_x.set('x : ' + str(x0))
        self.start_y.set('y : ' + str(y0))
        self.current_x.set('x : ' + str(x1))
        self.current_y.set('y : ' + str(y1))

root = tkinter.Tk()
app = Application(master=root)
app.mainloop()

コードの内容は上に書いたとおりですね。

実際に動かすとこうなります。

広告

PythonでGUIカテゴリの投稿