Tips & Tricks¶
Freehand Anything¶
While this library was designed for rendering the types of input points
generated by the movement of the human hand, you can pass any set of points
into the library’s functions. For example, here’s what you get when running
Feather Icons through
perfect_freehand.get_stroke()
.
Rendering¶
While perfect_freehand.get_stroke()
returns an array of points
representing the outline of a stroke, it’s up to you to decide how you will
render these points.
The function below will turn the points returned by get_stroke()
into SVG
path data:
from itertools import chain, pairwise
def svg_path_from_stroke(stroke):
if len(stroke) == 0:
return ""
d = ['M', f'{stroke[0][0]}', f'{stroke[0][1]}', 'Q']
for ((x0, y0), (x1, y1)) in pairwise(chain(stroke, [stroke[0]])):
d.extend([
f'{(x0 + x1) / 2}',
f'{(y0 + y1) / 2}',
f'{x1}',
f'{y1}'
])
d.append('Z')
return ' '.join(d)
To use this function, first run your input points through get_stroke()
,
then pass the result to svg_path_from_stroke
. The path must be rendered in
the SVG using fill-rule="nonzero"
.
Flattening¶
By default, the polygon’s paths include self-crossings. You may wish to remove these crossings and render a stroke as a “flattened” polygon. One example of how this can be done is using the object.buffer() method provided by shapely.
As an example, here’s a method for generating a flattened svg polygon, re-using
the svg_path_from_stroke()
function seen in the previous example:
from shapely.geometry import Polygon
def flat_svg_path_from_stroke(stroke):
polygon = Polygon(stroke)
# At this point, polygon.is_valid may be False due to self-
# crossings. To fix this, use the "buffer" method with an
# offset of 0.
polygon = polygon.buffer(0)
# The polygon will now consist of one exterior ring, and holes
# will be represented by interior rings.
d = [svg_path_from_stroke(polygon.exterior.coords)]
for interior in polygon.interiors:
d.append(svg_path_from_stroke(interior.coords))
return ' '.join(d)
Since the SVG path now consists of multiple separate rings, in order for the
holes to be rendered you must used fill-rule="evenodd"
.