Skip to content

Instantly share code, notes, and snippets.

@simonecesano
Created August 31, 2021 07:13
Show Gist options
  • Save simonecesano/11c04cc65fcaa42acd3a32440c041304 to your computer and use it in GitHub Desktop.
Save simonecesano/11c04cc65fcaa42acd3a32440c041304 to your computer and use it in GitHub Desktop.
from svgpathtools import parse_path, kinks
import numpy as np
# a bunch of paths - atually all quite similar
paths = [
'm 269.68858,24.762486 c -40.50864,-1.700513 -82.13567,24.752876 -90.53902,65.804462 -13.14294,37.392562 -17.2298,77.029262 -21.30965,116.163222 2.18009,35.53756 2.00517,72.32618 18.49409,105.0151 17.65672,49.88028 40.60061,89.34487 56.23634,137.55913 8.01979,33.12323 -0.64042,66.17131 0.0854,99.31004 -1.70152,58.80692 -18.69271,117.97032 -3.57251,176.35988 4.75221,41.18507 47.61108,68.96937 84.43863,69.35203 36.82756,0.38265 66.60039,-18.16206 77.87482,-50.2879 17.90406,-42.01272 17.79235,-81.49902 16.42076,-133.65857 -1.37159,-52.15955 6.43528,-134.61422 11.35889,-178.16317 4.9236,-43.54895 9.02792,-54.92485 8.65922,-82.92663 C 434.67597,283.85003 417.4602,218.83964 391.752,159.17256 381.13197,135.51373 372.07804,115.94824 355.87316,89.293903 339.66828,62.639564 309.19251,27.626442 269.68858,24.762486 Z',
'm 269.903,19.8186 c -11.91711,-0.51685 -32.27731,1.070319 -53.46034,13.363287 -7.57964,4.398624 -15.09441,10.115707 -21.7639,17.309543 -6.10547,6.585479 -11.48984,14.405852 -15.42262,23.411503 -1.88956,4.341093 -3.20655,8.762796 -4.45743,13.203647 -0.006,0.02087 -0.16665,0.593017 -0.17253,0.613887 -0.92526,3.298648 -2.13708,7.795125 -3.58284,11.703021 -0.0335,0.0905 -0.0643,0.181961 -0.0924,0.274263 -10.08152,33.076419 -13.8021,65.940699 -18.03284,106.519239 -0.0282,0.27038 -0.0341,0.54266 -0.0175,0.814 0.98132,16.11317 1.53474,33.95368 3.54713,50.93551 2.03496,17.17236 5.60753,34.19583 12.80558,50.3898 0.01,0.0224 0.02,0.0446 0.0303,0.0669 2.63691,5.70301 4.88806,11.66869 7.07194,17.50312 2.39713,6.40418 4.5705,12.24991 7.19584,18.13489 8.15411,19.54232 16.41702,37.29627 23.38065,53.17688 10.17192,23.19711 16.22795,38.97872 20.92776,53.58235 a 4.9485337,4.9485337 90 0 0 9.4212,-3.03198 c -4.83331,-15.01844 -11.02505,-31.12699 -21.28502,-54.5249 -7.02031,-16.00985 -15.28013,-33.76328 -23.33392,-53.06904 -0.0157,-0.0377 -0.032,-0.0753 -0.0486,-0.11262 -2.4887,-5.57281 -4.56,-11.13612 -6.98887,-17.62505 -2.16627,-5.78741 -4.53371,-12.07537 -7.34197,-18.15331 -6.63971,-14.94999 -10.03852,-30.89399 -12.00657,-47.50167 -1.9519,-16.47139 -2.48119,-33.55744 -3.4719,-49.96405 4.19097,-40.19716 7.83789,-72.12408 17.5727,-104.11915 1.59772,-4.346868 2.91877,-9.259422 3.77871,-12.325233 -0.007,0.02482 0.17656,-0.62828 0.1696,-0.603457 1.2399,-4.401869 2.40609,-8.261978 4.00337,-11.931579 3.44767,-7.894808 8.18241,-14.785879 13.6081,-20.638137 5.93027,-6.396497 12.64887,-11.517647 19.47371,-15.47825 19.10508,-11.087085 37.44871,-12.495977 48.06391,-12.035592 Z',
'm 317.49193,24.762486 c 40.50864,-1.700513 82.13567,24.752876 90.53902,65.804462 13.14294,37.392562 17.2298,77.029262 21.30965,116.163222 -2.18009,35.53756 -2.00517,72.32618 -18.49409,105.0151 -17.65672,49.88028 -40.60061,89.34487 -56.23634,137.55913 -8.01979,33.12323 0.64042,66.17131 -0.0854,99.31004 1.70152,58.80692 18.69271,117.97032 3.57251,176.35988 -4.75221,41.18507 -47.61108,68.96937 -84.43863,69.35203 -36.82756,0.38265 -66.60039,-18.16206 -77.87482,-50.2879 -17.90406,-42.01272 -17.79235,-81.49902 -16.42076,-133.65857 1.37159,-52.15955 -6.43528,-134.61422 -11.35889,-178.16317 -4.9236,-43.54895 -9.02792,-54.92485 -8.65922,-82.92663 -6.84042,-65.44005 10.37535,-130.45044 36.08355,-190.11752 10.62003,-23.65883 19.67396,-43.22432 35.87884,-69.878657 16.20488,-26.654339 46.68065,-61.667461 86.18458,-64.531417 z',
'M 43.179732,261.02847 C 43.179732,194.89491 43.179732,194.89491 109.1273,194.89491',
'm 43.179732,261.02847 c 0,-66.13356 60.431218,-66.13356 65.947568,-66.13356',
]
path = parse_path(paths[0])
print("Parsed path is %d long\n" % path.length())
print("Parsed path has %d segments\n" % len(path))
hpoints = [] # points where tangent is horizontal
vpoints = [] # points where tangent is vertical
i=0
# the path is made up of segments
for s in path:
# convert segment to numpy polynomial for later use
p = s.poly()
print("### Segment %d\n" % i)
i += 1
# print segment as python object
print("#### Python object\n\n " + str(s) + "\n")
# prettify equation
bpretty = "{}*(1-t)^3 + 3*{}*(1-t)^2*t + 3*{}*(1-t)*t^2 + {}*t^3".format(*s.bpoints())
# print it, and print its standard format
print("#### CubicBezier\n\n b.point(x) = " +
bpretty + "\n\n" +
"#### Standard form\n\n " +
str(p).replace('x','t').replace("\n","\n ") + "\n")
print("---------------------------------")
# find roots - in this case of the derivative
# this is the part that needs figuring out
# numpy treats 2d polynomials as polynomials with complex coefficients. Since
# you need to find the roots of the two coordinates seperately, you separate
# the single complex polynomial into two separate ones, one containing the
# real part of the coefficients, the other one containing the imaginary part.
p1 = np.poly1d(np.real(p))
p2 = np.poly1d(np.imag(p))
# take the derivative of each of the two polynomials
d1 = p1.deriv()
d2 = p2.deriv()
# obtain points where tangent is horizontal (in terms of the coordinate
# used to parametrize, that is "t") by imposing the y coordinate of the
# derivative to be equal to 0
hpoints_raw = np.roots(d2)
# same thing but now the x coordinate is equal to 0
vpoints_raw = np.roots(d1)
for t in hpoints_raw:
if np.isreal(t): # check if solution is real
if 0 <= t and t <= 1: # check if t is within bounds
# by evaluating p1 and p2 at the value of t found, you are converting
# from parametrized coordinates to the original coordinates
hpoints.append((p1(t), p2(t)))
for t in vpoints_raw:
if np.isreal(t):
if 0 <= t and t <= 1:
vpoints.append((p1(t), p2(t)))
print("---------------------------------")
print("horizontal points: ", hpoints)
print("vertical points: ", vpoints)
print("---------------------------------")
// ------------------------------------------------------
// saving the SVG
// ------------------------------------------------------
import svgwrite
import os
if os.path.exists('test_02.svg'): os.remove('test_02.svg')
dwg = svgwrite.Drawing('test_02.svg')
dwg.add(dwg.path(d=paths[0],stroke=svgwrite.rgb(0, 0, 0, '%'), fill="#cccccc"))
for p in vpoints:
print(p[0], p[1])
dwg.add(dwg.circle(center=(p[0], p[1]), r=12, stroke=svgwrite.rgb(0, 0, 0, '%'), fill="#880000"))
for p in hpoints:
print(p[0], p[1])
dwg.add(dwg.circle(center=(p[0], p[1]), r=12, stroke=svgwrite.rgb(0, 0, 0, '%'), fill="#000088"))
dwg.save(pretty=True, indent=2)
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment