用Python实现SVG圆弧曲线

2024-10-03 17:19:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图用Python实现SVG路径计算,但是在使用弧线时遇到了问题。在

我想问题出在从端点到中心参数化的转换上,但是我找不到问题。您可以在SVG specifications的F6.5节中找到有关如何实现它的注释。我还研究了其他语言的实现,也看不出它们有什么不同。在

我的Arc对象实现如下:

class Arc(object):

    def __init__(self, start, radius, rotation, arc, sweep, end):
        """radius is complex, rotation is in degrees, 
           large and sweep are 1 or 0 (True/False also work)"""

        self.start = start
        self.radius = radius
        self.rotation = rotation
        self.arc = bool(arc)
        self.sweep = bool(sweep)
        self.end = end

        self._parameterize()

    def _parameterize(self):
        # Conversion from endpoint to center parameterization
        # http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes

        cosr = cos(radians(self.rotation))
        sinr = sin(radians(self.rotation))
        dx = (self.start.real - self.end.real) / 2
        dy = (self.start.imag - self.end.imag) / 2
        x1prim = cosr * dx + sinr * dy
        x1prim_sq = x1prim * x1prim
        y1prim = -sinr * dx + cosr * dy
        y1prim_sq = y1prim * y1prim

        rx = self.radius.real
        rx_sq = rx * rx
        ry = self.radius.imag        
        ry_sq = ry * ry

        # Correct out of range radii
        radius_check = (x1prim_sq / rx_sq) + (y1prim_sq / ry_sq)
        if radius_check > 1:
            rx *= sqrt(radius_check)
            ry *= sqrt(radius_check)
            rx_sq = rx * rx
            ry_sq = ry * ry

        t1 = rx_sq * y1prim_sq
        t2 = ry_sq * x1prim_sq
        c = sqrt((rx_sq * ry_sq - t1 - t2) / (t1 + t2))
        if self.arc == self.sweep:
            c = -c
        cxprim = c * rx * y1prim / ry
        cyprim = -c * ry * x1prim / rx

        self.center = complex((cosr * cxprim - sinr * cyprim) + 
                              ((self.start.real + self.end.real) / 2),
                              (sinr * cxprim + cosr * cyprim) + 
                              ((self.start.imag + self.end.imag) / 2))

        ux = (x1prim - cxprim) / rx
        uy = (y1prim - cyprim) / ry
        vx = (-x1prim - cxprim) / rx
        vy = (-y1prim - cyprim) / ry
        n = sqrt(ux * ux + uy * uy)
        p = ux
        theta = degrees(acos(p / n))
        if uy > 0:
            theta = -theta
        self.theta = theta % 360

        n = sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))
        p = ux * vx + uy * vy
        if p == 0:
            delta = degrees(acos(0))
        else:
            delta = degrees(acos(p / n))
        if (ux * vy - uy * vx) < 0:
            delta = -delta
        self.delta = delta % 360
        if not self.sweep:
            self.delta -= 360

    def point(self, pos):
        if self.arc == self.sweep:
            angle = radians(self.theta - (self.delta * pos))
        else:
            angle = radians(self.delta + (self.delta * pos))

        x = sin(angle) * self.radius.real + self.center.real
        y = cos(angle) * self.radius.imag + self.center.imag
        return complex(x, y)

您可以使用下面的代码来测试这一点,该代码将用Turtle模块绘制曲线。(末尾的raw_input()只是为了程序退出时屏幕不会消失)。在

^{pr2}$

问题是:

绘制的这四条弧中的每一条都应从起点绘制到终点。然而,它们是从错误的地方画出来的。两条曲线从终点到起点,两条从100,-50到0,0,而不是从0,0到100,50。

问题的一部分是,实现说明给出了从端点到中心的转换的公式,但没有解释它在几何上的作用,所以我不太清楚每一步都做了什么。对此作出解释也会有帮助。在


Tags: selfsqrxrealstartenddeltaradius
2条回答

我想我在你的代码中发现了一些错误:

theta = degrees(acos(p / n))
if uy > 0:
    theta = -theta
self.theta = theta % 360

条件uy > 0错误,正确的是uy < 0(从(1, 0)到{}的有向角为负,如果uy < 0,则为负):

^{pr2}$

那么

^{3}$

这里不需要区分,sweep和{}参数已经在theta和^{中说明。这可以简化为:

angle = radians(self.theta + (self.delta * pos))

最后呢

x = sin(angle) * self.radius.real + self.center.real
y = cos(angle) * self.radius.imag + self.center.imag

这里sincos混在一起了,正确的是

x = cos(angle) * self.radius.real + self.center.real
y = sin(angle) * self.radius.imag + self.center.imag

在这些修改之后,程序按预期运行。在


编辑:还有一个问题。point方法不考虑可能的rotation参数。以下版本应正确:

def point(self, pos):
    angle = radians(self.theta + (self.delta * pos))
    cosr = cos(radians(self.rotation))
    sinr = sin(radians(self.rotation))

    x = cosr * cos(angle) * self.radius.real - sinr * sin(angle) * self.radius.imag + self.center.real
    y = sinr * cos(angle) * self.radius.real + cosr * sin(angle) * self.radius.imag + self.center.imag
    return complex(x, y)

(见SVG specification中的公式F.6.3.1。)

相关问题 更多 >