1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-03

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

それくらい夢見たっていいでしょう。

ってことですが、元々は業務で利用しているTkinterのラッパーで用事があったものです。

その時は良い結果が得られずに結局ギザギザの通常の描画で流したのですが、使えば使うほどやっぱり滑らかな描画さえできれば世界が変わる、そんな気がしたんだ!

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

とりあえず考えてみる。

アンチエイリアスのラインアルゴリズムでは Xiaolin Wu 先生のものが有名です。

試しに一度Canvasに仕込んでみたのですが普通にラインを描くだけなら良いのですけれどもその他諸々をしようとすると何かと不便があったのでここでは別の方法を使います。

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

※頭の中の概念図
fig

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

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

コード

aaimage.py

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 を実装しました。

画面を塗り替えたい事も多かったのでついでに fill も。

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

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

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

実際に使ってみる

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 のノリでそのまま使えます。

実行結果

aaimage

単体のドットには思うところがありますけれども1ピクセル以下を表現しようとしているわけなのでこれ以上どうともならないでしょう。

ということで、大成功! これをガンd

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?