Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to interpolate SVG path into a pixel coordinates (not simply raster) in Python

I need to convert an SVG description of a path, ie. something like:

M400 597 C235 599 478 607 85 554 C310 675 2 494 399 718 C124 547 569 828 68 400 C-108 317 304 703 96 218 L47 215 L400 290 C602 -146 465 467 550 99 L548 35 L706 400 L580 686 C546 614 591 672 529 629 L400 597 Z

into a list of all the pixels that would fall along that path (assuming the canvas is the size of the monitor. As you can see, the paths I need to work with amount to scribbles and are quite complex. Ideally, I'd like to generate such a path and then convert the entire thing to a pixel-by-pixel description, ie.

p= [(403, 808), (403, 807), (403, 805), (403, 802), (403, 801), (403, 800), (403, 799), 
(403, 797), (403, 794), (403, 792), (402, 789), (401, 787), (400, 785), (399, 784), 
(399, 783), (398, 782)]  # ... it'd be much longer, but you get the idea

Alternatively, I'd be as content with any means of generating paths with curve and line constituents (as in, SVG is simply how I've achieved this so far). The context is a bit peculiar; it's for an experiment in cognitive psychology, wherein I need to gradually animate a dot traversing a path generated according to certain rules, and export that path as pixel data.

To do the animation, I'm intending to simply redraw the dot at each x,y position along the path—hence the need for said list.

My math skills aren't great—I'm came to code from design, not CS—and the paths will get quite complex, meaning computing these points with math alone is... maybe not beyond me but definitely more demanding than I'm aiming for.

Libraries, tricks, strategies—all welcome & appreciated.

like image 504
Jonline Avatar asked May 01 '16 20:05

Jonline


1 Answers

I needed to convert an SVG path into discrete points for some weird purposes. Apparently there is no light library for doing that. I ended up creating my own parser.

Input files mostly consist of Bezier curves. I wrote a quick function for that:

def cubic_bezier_sample(start, control1, control2, end):
    inputs = np.array([start, control1, control2, end])
    cubic_bezier_matrix = np.array([
        [-1,  3, -3,  1],
        [ 3, -6,  3,  0],
        [-3,  3,  0,  0],
        [ 1,  0,  0,  0]
    ])
    partial = cubic_bezier_matrix.dot(inputs)

    return (lambda t: np.array([t**3, t**2, t, 1]).dot(partial))

def quadratic_sample(start, control, end):
    # Quadratic bezier curve is just cubic bezier curve
    # with the same control points.
    return cubic_bezier_sample(start, control, control, end)

It can be used as follow to generate 10 samples:

n = 10
curve = cubic_bezier_sample((50,0), (50,100), (100,100), (50,0))
points = [curve(float(t)/n) for t in xrange(0, n + 1)]

The code requires numpy. You can also do the dot product without numpy if you want. I am able to get the parameters with svg.path.


Full code example

import numpy as np
import matplotlib.pyplot as plt

def cubic_bezier_sample(start, control1, control2, end):
    inputs = np.array([start, control1, control2, end])
    cubic_bezier_matrix = np.array([
        [-1,  3, -3,  1],
        [ 3, -6,  3,  0],
        [-3,  3,  0,  0],
        [ 1,  0,  0,  0]
    ])
    partial = cubic_bezier_matrix.dot(inputs)

    return (lambda t: np.array([t**3, t**2, t, 1]).dot(partial))

# == control points ==
start = np.array([0, 0])
control1 = np.array([60, 5])
control2 = np.array([40, 95])
end = np.array([100, 100])

# number of segments to generate
n_segments = 100
# get curve segment generator
curve = cubic_bezier_sample(start, control1, control2, end)
# get points on curve
points = np.array([curve(t) for t in np.linspace(0, 1, n_segments)])

# == plot ==
controls = np.array([start, control1, control2, end])
# segmented curve
plt.plot(points[:, 0], points[:, 1], '-')
# control points
plt.plot(controls[:,0], controls[:,1], 'o')
# misc lines
plt.plot([start[0], control1[0]], [start[1], control1[1]], '-', lw=1)
plt.plot([control2[0], end[0]], [control2[1], end[1]], '-', lw=1)

plt.show()

Figure 1

like image 94
Derek 朕會功夫 Avatar answered Oct 17 '22 07:10

Derek 朕會功夫