diff --git a/dist/svgpathtools-1.4.1-py3.7.egg b/dist/svgpathtools-1.4.1-py3.7.egg index e4e8b36..7759b14 100644 Binary files a/dist/svgpathtools-1.4.1-py3.7.egg and b/dist/svgpathtools-1.4.1-py3.7.egg differ diff --git a/svgpathtools/path.py b/svgpathtools/path.py index 5209ad0..f295426 100644 --- a/svgpathtools/path.py +++ b/svgpathtools/path.py @@ -13,6 +13,7 @@ from warnings import warn from operator import itemgetter import numpy as np from itertools import tee +from functools import reduce # these imports were originally from math and cmath, now are from numpy # in order to encourage code that generalizes to vector inputs @@ -288,7 +289,6 @@ def scale(curve, sx, sy=None, origin=0j): raise TypeError("Input `curve` should be a Path, Line, " "QuadraticBezier, CubicBezier, or Arc object.") - def transform(curve, tf): """Transforms the curve by the homogeneous transformation matrix tf""" def to_point(p): @@ -310,13 +310,32 @@ def transform(curve, tf): elif isinstance(curve, Arc): new_start = to_complex(tf.dot(to_point(curve.start))) new_end = to_complex(tf.dot(to_point(curve.end))) - new_radius = to_complex(tf.dot(to_vector(curve.radius))) - if tf[0][0] * tf[1][1] >= 0.0: - new_sweep = curve.sweep - else: - new_sweep = not curve.sweep - return Arc(new_start, radius=new_radius, rotation=curve.rotation, - large_arc=curve.large_arc, sweep=new_sweep, end=new_end) + + # Based on https://math.stackexchange.com/questions/2349726/compute-the-major-and-minor-axis-of-an-ellipse-after-linearly-transforming-it + rx2 = curve.radius.real ** 2 + ry2 = curve.radius.imag ** 2 + + Q = np.array([[1/rx2, 0], [0, 1/ry2]]) + invT = np.linalg.inv(tf[:2,:2]) + D = reduce(np.matmul, [invT.T, Q, invT]) + + eigvals, eigvecs = np.linalg.eig(D) + + rx = 1 / np.sqrt(eigvals[0]) + ry = 1 / np.sqrt(eigvals[1]) + + new_radius = complex(rx, ry) + + xeigvec = eigvecs[:, 0] + rot = np.degrees(np.arccos(xeigvec[0])) + + if new_radius.real == 0 or new_radius.imag == 0 : + return Line(new_start, new_end) + else : + return Arc(new_start, radius=new_radius, rotation=curve.rotation + rot, + large_arc=curve.large_arc, sweep=curve.sweep, end=new_end, + autoscale_radius=False) + else: raise TypeError("Input `curve` should be a Path, Line, " "QuadraticBezier, CubicBezier, or Arc object.")