Tkinterだってアンチエイリアスしたい。

# Tkinterだってアンチエイリアスしたい
それくらい夢見たっていいでしょう。

ってことですが、元々は業務で利用しているTkinterのラッパーで用事があったものです。<br>
その時は良い結果が得られずに結局ギザギザの通常の描画で流したのですが、使えば使うほどやっぱり滑らかな描画さえできれば世界が変わる、そんな気がしたんだ！

*MacのTkinterは元々アンチエイリアスありで描画されるらしい？ので無関係かもしれませんがー？*

## とりあえず考えてみる。
アンチエイリアスのラインアルゴリズムでは **Xiaolin Wu** 先生のものが有名です。<br>
試しに一度Canvasに仕込んでみたのですが普通にラインを描くだけなら良いのですけれどもその他諸々をしようとすると何かと不便があったのでここでは別の方法を使います。<br>

座標は自然数で扱うこととし（ぁ）　一つの描画座標はx～x+1、y~y+1の合計4点の座標に作用するとしてそれぞれの座標に対して色の濃さを表してゆけば事実上アンチエイリアスが可能だろうと。そういうことを考えたので実装してみます。

**※頭の中の概念図**
![fig](https://user-images.githubusercontent.com/12671694/144586729-03b71550-9347-4495-96d7-1d3b1ba4fbbc.jpg)


そして、直接Canvasに描画すると何か問題があった記憶があるのでPhotoImageとして扱うことにします。


## アンチエイリアス対応PhotoImage、AAImage

### コード

*aaimage.py*

```python
import tkinter as tk

class AAImage(tk.PhotoImage):

    def __init__(self, conf={}, **kwargs):
        super().__init__(conf, **kwargs)
        self.pixpos = [(0, 0), (1, 0), (0, 1), (1, 1)]

    def rgbtohex(self, r, g, b):
        return "#%02x%02x%02x" % (int(r), int(g), int(b))

    def hexcolor(self, color):
        return self.rgbtohex(int(color[0]), int(color[1]), int(color[2]))

    def hextocolor(self, hexcolor):
        r = int(hexcolor[1:3], 16)
        g = int(hexcolor[3:5], 16)
        b = int(hexcolor[5:7], 16)
        return (r, g, b)

    """
     color fill
    """
    def fill(self, color):
        d = [self.hexcolor(color)] * int(self.height())
        for x in range(int(self.width())):
            self.put(d, (x, 0))

    """
     4 point antialiasing
     ** Not optimized **
    """
    def pset(self, x, y, color=(255,255,255)):
        R, G, B = color
        pp = [0] * 4
        pp[0] = (1 - self.pp(x)) * (1 - self.pp(y))
        pp[1] = self.pp(x) * (1 -self.pp(y))
        pp[2] = (1 - self.pp(x)) * self.pp(y)
        pp[3] = self.pp(x) * self.pp(y)

        for i in range(4):
            px = int(x + self.pixpos[i][0])
            py = int(y + self.pixpos[i][1])
            if pp[i] > 0:
                if px >= 0 and px < int(self.width()) \
                  and py >= 0 and py < int(self.height()):
                    bR, bG, bB = self.get(px, py)
                    tc = (
                        int(R * pp[i] + bR * (1 - pp[i])),
                        int(G * pp[i] + bG * (1 - pp[i])),
                        int(B * pp[i] + bB * (1 - pp[i]))
                        )
                    self.put(self.hexcolor(tc), (px, py))

    """
     draw line
    """
    def line(self, x1, y1, x2, y2, color=(0, 0, 0)):
        xmax = max(x1, x2)
        ymax = max(y1, y2)
        xmin = min(x1, x2)
        ymin = min(y1, y2)
        xr = xmax - xmin
        yr = ymax - ymin

        x = x1
        y = y1
        if xr > yr:
            if y2 - y1 == 0:
                yy = 0
            else:
                yy = (y2 - y1) / xr
            if x2 - x1 < 0:
                xx = -1
            else:
                xx = 1
            for i in range(int(xr)+1):
                 self.pset(x, y, color)
                 x += xx
                 y += yy
        else:
            if x2 - x1 == 0:
                xx = 0
            else:
                xx = (x2 - x1) / yr
            if y2 - y1 < 0:
                yy = -1
            else:
                yy = 1
            for i in range(int(yr)+1):
                 self.pset(x, y, color)
                 x += xx
                 y += yy

    def pp(self, pos):
        return pos - int(pos)

```
### 解説
とりあえず1ピクセルを描画するための ```pset```（実際には最大4ピクセルが描画される）とそれを利用した ```line``` を実装しました。<br>
画面を塗り替えたい事も多かったのでついでに ```fill``` も。

キモは ```pset``` なのですが残念ながら最適化はしていません。

~~誰かがカッコよく使いやすくして各種の描画アルゴリズムも実装して公開してくれるのを期待しています~~

各種描画アルゴリズムを実装すると便利に使えそうですね！

## 実際に使ってみる

```python
import random as rnd
import tkinter as tk
import aaimage

root = tk.Tk()
c = tk.Canvas(root)
c.pack(fill="both", expand=True)

image = aaimage.AAImage(width=150, height=150)
c.create_image(0, 0, image=image, anchor="nw")
image.fill((255,255,255))

# random dot
for i in range(200):
    x = rnd.random() * image.width()
    y = rnd.random() * image.height()
    image.pset(x, y, (0,0,255))

# somooth line
image.line(100, 5, 20, 120, (255, 0, 0))
image.line(10, 10, 130, 30, (0, 255, 0))

root.mainloop()
```

継承してるので当たり前ですが ```tk.PhotoImage``` のノリでそのまま使えます。<br>


## 実行結果
![aaimage](https://user-images.githubusercontent.com/12671694/144584331-57d2d043-e5d4-4b77-9a22-021d43e40b5b.png)

単体のドットには思うところがありますけれども1ピクセル以下を表現しようとしているわけなのでこれ以上どうともならないでしょう。<br>
ということで、大成功！ ~~これをガンｄ~~<br>

何事も諦めなければどうにかなるものですネ！