Реализация SVG дуговых кривых в Python
Я пытаюсь реализовать вычисления пути SVG в Python, но у меня возникают проблемы с кривыми Arc.
Я думаю, что проблема в преобразовании конечной точки в централизацию параметров, но я не могу найти проблему. Вы можете найти примечания о том, как это реализовать, в разделе F6.5 спецификаций SVG. Я также смотрел на реализации на других языках, и я не вижу, что они делают по-другому.
Моя реализация объекта 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() означает, что экран не исчезает при выходе из программы).
arc1 = Arc(0j, 100+50j, 0, 0, 0, 100+50j)
arc2 = Arc(0j, 100+50j, 0, 1, 0, 100+50j)
arc3 = Arc(0j, 100+50j, 0, 0, 1, 100+50j)
arc4 = Arc(0j, 100+50j, 0, 1, 1, 100+50j)
import turtle
t = turtle.Turtle()
t.penup()
t.goto(0, 0)
t.dot(5, 'red')
t.write('Start')
t.goto(100, 50)
t.dot(5, 'red')
t.write('End')
t.pencolor = t.color('blue')
for arc in (arc1, arc2, arc3, arc4):
t.penup()
p = arc.point(0)
t.goto(p.real, p.imag)
t.pendown()
for x in range(1,101):
p = arc.point(x*0.01)
t.goto(p.real, p.imag)
raw_input()
Проблема:
Каждая из этих четырех нарисованных дуг должна рисовать от начальной точки до конечной точки. Тем не менее, они взяты из неправильных точек. Две кривые идут от конца к началу, а две - от 100,-50 до 0,0 вместо от 0,0 до 100, 50.
Частично проблема заключается в том, что примечания по реализации дают вам формулу от того, как выполнить преобразование конечных точек формы в центр, но не объясняют, что это делает геометрически, поэтому я не совсем понимаю, что делает каждый шаг. Объяснение этого также было бы полезно.
2 ответа
Я думаю, что я нашел несколько ошибок в вашем коде:
theta = degrees(acos(p / n))
if uy > 0:
theta = -theta
self.theta = theta % 360
Состояние uy > 0
неправильно, правильно uy < 0
(направленный угол от (1, 0)
в (ux, uy)
отрицательно, если uy < 0
):
theta = degrees(acos(p / n))
if uy < 0:
theta = -theta
self.theta = theta % 360
затем
if self.arc == self.sweep:
angle = radians(self.theta - (self.delta * pos))
else:
angle = radians(self.delta + (self.delta * pos))
Различие здесь не обязательно, sweep
а также arc
параметры уже учтены в theta
а также delta
, Это может быть упрощено до:
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
Вот sin
а также cos
перепутал, поправь
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)
(См. Формулу F.6.3.1 в спецификации SVG.)
Может быть, вы могли бы взглянуть на ссылки ниже, кажется, есть пошаговое руководство о том, как вычислить дуги (см. computeArc()
):
или же