6

ベクトル グラフィックスを PDF 形式で生成する Python スクリプトを作成したい場合、その仕事に適したツールは何ですか? 特に、角が丸い塗りつぶされたポリゴン(つまり、直線と円弧で構成される平面図形) を描画する必要があります。

matplotlibを使用すると、角の丸い四角形や角のとがった一般的な多角形を簡単に描画できるようです。ただし、角の丸いポリゴンを描くには、まず形状を近似するベジエ曲線を計算する必要があるようです。

もっと簡単に利用できるものはありますか?または、生成したい形状に近似するベジエ曲線を計算するために使用できる別のライブラリはありますか? 理想的には、頂点ごとに (場所、角の半径) のペアを指定するだけです。

例を次に示します。赤いポリゴン (+ 各コーナーの半径) を指定すると、ライブラリは灰色の図を出力します。

例

(凸多角形の場合、ごまかして太いペンを使用して多角形の輪郭を描くことができます。ただし、これは凸でない場合には機能しません。)

4

2 に答える 2

14

これはややハックなmatplotlibソリューションです。主な複雑さは、matplotlibPathオブジェクトを使用してコンポジットを構築することに関連していますPath

#!/usr/bin/env python

import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch, Polygon
from matplotlib.transforms import Bbox, BboxTransformTo

def side(a, b, c):
    "On which side of line a-b is point c? Returns -1, 0, or 1."
    return np.sign(np.linalg.det(np.c_[[a,b,c],[1,1,1]]))

def center((prev, curr, next), radius):
    "Find center of arc approximating corner at curr."
    p0, p1 = prev
    c0, c1 = curr
    n0, n1 = next
    dp = radius * np.hypot(c1 - p1, c0 - p0)
    dn = radius * np.hypot(c1 - n1, c0 - n0)
    p = p1 * c0 - p0 * c1
    n = n1 * c0 - n0 * c1
    results = \
        np.linalg.solve([[p1 - c1, c0 - p0],
                         [n1 - c1, c0 - n0]],
                        [[p - dp, p - dp, p + dp, p + dp],
                         [n - dn, n + dn, n - dn, n + dn]])
    side_n = side(prev, curr, next)
    side_p = side(next, curr, prev)
    for r in results.T:
        if (side(prev, curr, r), side(next, curr, r)) == (side_n, side_p):
            return r
    raise ValueError, "Cannot find solution"

def proj((prev, curr, next), center):
    "Project center onto lines prev-curr and next-curr."
    p0, p1 = prev = np.asarray(prev)
    c0, c1 = curr = np.asarray(curr)
    n0, n1 = next = np.asarray(next)
    pc = curr - prev
    nc = curr - next
    pc2 = np.dot(pc, pc)
    nc2 = np.dot(nc, nc)
    return (prev + np.dot(center - prev, pc)/pc2 * pc,
            next + np.dot(center - next, nc)/nc2 * nc)

def rad2deg(angle):
    return angle * 180.0 / np.pi

def angle(center, point):
    x, y = np.asarray(point) - np.asarray(center)
    return np.arctan2(y, x)

def arc_path(center, start, end):
    "Return a Path for an arc from start to end around center."
    # matplotlib arcs always go ccw so we may need to mirror
    mirror = side(center, start, end) < 0
    if mirror: 
        start *= [1, -1]
        center *= [1, -1]
        end *= [1, -1]
    return Path.arc(rad2deg(angle(center, start)),
                    rad2deg(angle(center, end))), \
           mirror

def path(vertices, radii):
    "Return a Path for a closed rounded polygon."
    if np.isscalar(radii):
        radii = np.repeat(radii, len(vertices))
    else:
        radii = np.asarray(radii)
    pv = []
    pc = []
    first = True
    for i in range(len(vertices)):
        if i == 0:
            seg = (vertices[-1], vertices[0], vertices[1])
        elif i == len(vertices) - 1:
            seg = (vertices[-2], vertices[-1], vertices[0])
        else:
            seg = vertices[i-1:i+2]
        r = radii[i]
        c = center(seg, r)
        a, b = proj(seg, c)
        arc, mirror = arc_path(c, a, b)
        m = [1,1] if not mirror else [1,-1]
        bb = Bbox([c, c + (r, r)])
        iter = arc.iter_segments(BboxTransformTo(bb))
        for v, c in iter:
            if c == Path.CURVE4:
                pv.extend([m * v[0:2], m * v[2:4], m * v[4:6]])
                pc.extend([c, c, c])
            elif c == Path.MOVETO:
                pv.append(m * v)
                if first:
                    pc.append(Path.MOVETO)
                    first = False
                else:
                    pc.append(Path.LINETO)
    pv.append([0,0])
    pc.append(Path.CLOSEPOLY)

    return Path(pv, pc)

if __name__ == '__main__':
    from matplotlib import pyplot
    fig = pyplot.figure()
    ax = fig.add_subplot(111)
    vertices = [[3,0], [5,2], [10,0], [6,9], [6,5], [3, 5], [0,2]]

    patch = Polygon(vertices, edgecolor='red', facecolor='None',
                    linewidth=1)
    ax.add_patch(patch)

    patch = PathPatch(path(vertices, 0.5), 
                      edgecolor='black', facecolor='blue', alpha=0.4,
                      linewidth=2)
    ax.add_patch(patch)

    ax.set_xlim(-1, 11)
    ax.set_ylim(-1, 9)
    fig.savefig('foo.pdf')

上記のスクリプトの出力

于 2011-10-09T19:10:47.700 に答える
5

PDF ファイルの作成に関しては、PDF サーフェスへの「描画」をサポートするベクトル グラフィック ライブラリであるcairoライブラリを参照することをお勧めします。Python バインディングもあります。

角の丸いポリゴンの描画に関しては、これをすぐにサポートするグラフィックス ライブラリを知りません。

ただし、角の半径が与えられた場合、ポリゴンの角の円弧座標を計算するのはそれほど複雑ではありません。r基本的に、隣接する 2 つのエッジの角度の二等分線上で、両方のエッジからの距離 (つまり、目的の半径)を持つ点を見つける必要があります。これは円弧の中心です。始点と終点を見つけるために、この点から 2 つのエッジに投影します。

たとえば、ポリゴンのエッジが短すぎて 2 つの円弧に収まらない場合 (この場合は、より小さな半径を選択する必要があると思います) など、どうすればよいかなど、自明ではないケースが考えられます。の ...

HTH

于 2011-10-09T10:52:01.247 に答える