tkinterでペイントアプリのレイヤ機能を作る方法

## はじめに
マウスで自由に線を引けるペイントアプリの作り方は既に紹介されていました。
しかし、レイヤ機能を持たせる方法は調べても見つからず、実装方法を考えたのでご紹介します。

ここで作るものは、2層のレイヤを右クリックによって入れ替えることができるペイントアプリです。

## 簡単なペイントアプリ
レイヤ機能を作る前に、最低限動くペイントアプリのコードです。
ここにコードを追加してレイヤ機能を持たせます。
```py
import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry("600x500")
        self.title("レイヤ機能の実装")
        
        self.canvas = tk.Canvas(self, background="white")
        self.canvas.pack(fill=tk.BOTH, expand=True)

        self.canvas.bind("<ButtonPress-1>", self.on_key_left)
        self.canvas.bind("<B1-Motion>", self.dragging)

        self.color = "black"

        self.button1 = tk.Button(text="赤にする", command=self.change_red)
        self.button1.pack()
        self.button2 = tk.Button(text="黒にする", command=self.change_black)
        self.button2.pack()
        
    # 左クリックしたとき
    def on_key_left(self, event):
        # 直線描画
        self.curr_id = self.canvas.create_line(event.x, event.y, event.x, event.y, fill = self.color, width = 5)
        
    # ドラッグ中
    def dragging(self, event):
        points = self.canvas.coords(self.curr_id)
        points.extend([event.x,event.y])
        self.canvas.coords(self.curr_id, points)

    def change_red(self):
        self.color = "red"
    def change_black(self):
        self.color = "black"

if __name__ == "__main__":
    application = Application()
    application.mainloop()
```

## レイヤ機能の実装
### レイヤの考え方
今回実装したいレイヤは2層なので、2枚のcanvasを用意してそれを入れ替えてあげると良さそうですが、tkinterではcanvasを透過して重ねることは想定されていないようです。

なので、実装するにあたって1枚のcanvas上でレイヤ機能を実装しなければいけません。
tkinterには、描いた線にIDが付与されるのでそれを使います。

### 実装方法
最後に全体のコードを載せるので、ここでは必要な部分を抜粋して紹介します。

#### レイヤ関係
まずは`selectLayer`という変数でどのレイヤを選択しているのかを識別します。
そして今回は2枚のレイヤを入れ替えるので、2つの配列を用意して、レイヤAに書いたものは`layer0`に、レイヤBに書いたものは`layer1`に追加します。

```py
# 選択中のレイヤ
self.selectLayer = 0

self.layer0 = []
self.layer1 = []

# 左クリックしたとき
def on_key_left(self, event):
    # 直線描画
    self.curr_id = self.canvas.create_line(event.x, event.y, event.x, event.y, fill = self.color, width = 5)
    
    if(self.selectLayer == 0):
        self.layer0.append(self.curr_id)
    else:
        self.layer1.append(self.curr_id)
```

#### 右クリック時の動き
右クリックでレイヤを切り替えるので、右クリックの動作を受け取る必要があります。
そして、右クリックをしたとき、切り替えたレイヤ側の線を前面に持ってきます。
```py
self.canvas.bind("<Button-3>", self.on_key_right)

# 右クリックしたとき
def on_key_right(self, event):
    if self.selectLayer == 0:
        for id in self.layer1:
            self.canvas.tag_raise(id)
    else:
        for id in self.layer0:
            self.canvas.tag_raise(id)

    self.selectLayer = (self.selectLayer + 1) % 2
```

#### クリア機能
せっかくなので選択中のレイヤに書いたものを全部消せる機能を付けます。
```py
def clear(self):
    if(self.selectLayer == 0):
        for id in self.layer0:
            self.canvas.delete(id)
    else:
        for id in self.layer1:
            self.canvas.delete(id)
```

### 全体のコード

```py
import tkinter as tk

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry("600x500")
        self.title("canvas tag_raise")
        
        self.canvas = tk.Canvas(self, background="white")
        self.canvas.pack(fill=tk.BOTH, expand=True)

        self.canvas.bind("<ButtonPress-1>", self.on_key_left)
        self.canvas.bind("<B1-Motion>", self.dragging)
        self.canvas.bind("<Button-3>", self.on_key_right)

        self.selectLayer = 0
        
        self.layer0 = []
        self.layer1 = []

        self.color = "black"

        self.button1 = tk.Button(text="赤にする", command=self.change_red)
        self.button1.pack()
        self.button2 = tk.Button(text="黒にする", command=self.change_black)
        self.button2.pack()
        self.button3 = tk.Button(text="クリア", command=self.clear)
        self.button3.pack()
        
    # 右クリックしたとき
    def on_key_right(self, event):
        if self.selectLayer == 0:
            for id in self.layer1:
                self.canvas.tag_raise(id)
        else:
            for id in self.layer0:
                self.canvas.tag_raise(id)

        self.selectLayer = (self.selectLayer + 1) % 2

    # 左クリックしたとき
    def on_key_left(self, event):
        # 直線描画
        self.curr_id = self.canvas.create_line(event.x, event.y, event.x, event.y, fill = self.color, width = 5)
        
        if self.selectLayer == 0:
            self.layer0.append(self.curr_id)
        else:
            self.layer1.append(self.curr_id)
        
    # ドラッグ中
    def dragging(self, event):
        points = self.canvas.coords(self.curr_id)
        points.extend([event.x,event.y])
        self.canvas.coords(self.curr_id, points)

    def change_red(self):
        self.color = "red"
    def change_black(self):
        self.color = "black"

    def clear(self):
        if self.selectLayer == 0:
            for id in self.layer0:
                self.canvas.delete(id)
        else:
            for id in self.layer1:
                self.canvas.delete(id)

if __name__ == "__main__":
    application = Application()
    application.mainloop()
```

これで、レイヤの切り替えられるペイントアプリが動くはずです。

別のレイヤに色の違う線を重なるように書いて右クリックをしたらレイヤの切り替えが実感できると思います。

## さいごに
今回は2層限定でしたが、レイヤの配列を二次元にしてやることで自由にレイヤの追加削除ができたり、レイヤの統合などの機能も追加できるはずです。

ぜひ発展させていってください！