読者です 読者をやめる 読者になる 読者になる

yukiのブログ

作ったものなど

絵を描くプログラム

エクセルとシェルとHTMLで絵を描くことができて、コードを少量追加すれば好きなもので絵を描くこともできるプログラムを書きました。

github.com

このプログラムで何ができるかはgithubのREADMEに譲るとして、このプログラムの中身がどうなっているかここにまとめました。

クラス編成

主なクラスはPainterとFigureの2つです。

図形クラス

図形を表すための基底クラスFigureがあり、実際の図形クラスPoint, Line, PolygonなどはFigureを継承します。

Figureの子クラスは__iter__()を持ち、イテレートによって図形を構成するより小さな図形を返します。例えばPolygonをイテレートするとLineが、LineをイテレートするとPointが返ります。

以下は図形クラスの例です。

class Figure(object):
    """ 図形の基底クラス """

    def __iter__(self) -> Iterable:
        pass


class Line(Figure):
    """ 線分 """

    def __init__(self, a: Point, b: Point) -> None:
        self.a = a
        self.b = b
        self.stopper = max(abs(self.a.x - self.b.x), abs(self.a.y - self.b.y))

    def __iter__(self) -> Iterable[Point]:
        return (Point.interpolate(self.a, self.b, i / self.stopper) for i in range(int(self.stopper)))


class Polygon(Figure):
    """ 多角形 """

    def __init__(self, points: List[Point]) -> None:
        self.points = points
        self.stopper = len(points)

    def __iter__(self) -> Iterable[Line]:
        return (Line(self.points[i - 1], self.points[i]) for i in range(self.stopper))

class Diamond(Figure):
    """ ダイヤモンドパターン """

    def __init__(self,
                 center: Point,
                 r: float,
                 n: int,
                 color: Callable[[float], List[int]] = lambda t: [0, 0, 0]) -> None:
        self.circle = lambda: circular_points(center, r, n, color)

    def __iter__(self) -> Iterable(Line):
        return (Line(p, q) for p in self.circle() for q in self.circle())

描画クラス

描画クラスの基底クラスはPainterで、これを継承したTkPainterやColorArrayPainterが実際の描画を行います。

Painterのdraw()の役割は、Figureを受け取ってそれをPointまで解きほぐし、解きほぐされたPointにput_pixel()を適用することです。FigureはイテレートするとFigureの構成要素の図形を返すので、Pointを返すまでイテレートすればFigureをPointまで解きほぐすことができます。put_pixel()は各クラスがオーバーライドします。

Painterによっては直線や円を高速に描画する関数が存在する場合があるので、その場合は高速な関数が利用できるように設定できます。どの種類の図形をどの関数で描画するのかは自書式配列で設定します。何も設定しなければ、Pointをput_pixel()で描画する、という情報だけが記録されます。

以下は既定のPainterクラスです。

class Painter(object):
    """ 絵を描くための基底クラス """

    def __init__(self) -> None:
        # draw_functionsで描画に使う関数を指定する
        # 何も指定しなければ、すべて点で描画される(遅い)
        self.draw_functions = {
            Point: self.put_pixel
        }

    def put_pixel(self, canvas, point: Point) -> None:
        """ canvasにpointを描画する """
        assert False, "Override me!"

    def draw(self, canvas, figure: Figure) -> None:
        """ canvasにfigureを描く """
        try:
            self.draw_functions[type(figure)](canvas, figure)
        except KeyError:
            for p in figure:
                self.draw(canvas, p)

ColorArrayPrinter

ColorArrayPainterを使うと、図形を描画した後の画面の(x, y)座標の色がC[x][y]に格納されている配列Cが作れます。

このような配列ができても目に見えないので、さらにこれを目に見える形にまでもっていく処理が必要です。そのような処理を受け持つクラスがColorArrayPrinterです。

class ColorArrayPrinter(object):
    def print(self, color_array: ColorArray) -> None:
        """ color_arrayを使って絵を描く """
        self.open(color_array)
        for x_array in color_array:
            for point in x_array:
                self.put_pixel(point)
            self.new_line()
        self.close()

子クラスはopen(), put_pixel(), new_line(), close()をオーバーライドして使うことになります。

感想

短いのでそのうち書き足すつもりです。