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
<?xml version="1.0" encoding="utf-8" ?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" height="100%" version="1.1" width="100%">
<defs/>
<path d="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" fill="#cccccc" stroke="rgb(0%,0%,0%)"/>
<circle cx="232.61200139240884" cy="544.5389262783622" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="235.99939109364783" cy="481.167010726253" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="222.8646966578942" cy="671.6542120776984" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="408.3613440408311" cy="643.2165654204395" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="407.65827356755125" cy="597.5192747910403" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="427.85860573657766" cy="352.7284809963312" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="429.3424976577309" cy="320.00581936726485" fill="#880000" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="265.989242576088" cy="24.68507445982098" fill="#000088" r="12" stroke="rgb(0%,0%,0%)"/>
<circle cx="314.63807953304564" cy="794.3321574928206" fill="#000088" r="12" stroke="rgb(0%,0%,0%)"/>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment