SquiggleDraw

Vectorizing Bitmap Images with Squiggles

Github

I recently got my hands on a new toy: the Axidraw V3. A type of pen plotter, this machine uses two dimensions of motorized movement to draw whatever it’s instructed to. It can be controlled in a number of ways, but usually you pass the digital drawing instructions as an SVG file, which is a common format for vector graphics used on the web.

There are many ways to create art with the pen plotter. One of the most common methods is drawing vectorized bitmap images. This means taking an image with pixel representation- like one taken with a camera- and translating it to a vector representation. Just as there can be inumerable ways to sketch the same picture of a beatuiful beach sunset, there are many ways to vectorize bitmap images.

I recently implemented one such way to vectorize bitmap images in python, called SquiggleDraw, inspired by a previous implementation in a language called Processing. SquiggleDraw turns a bitmap image into many horizontal sinusoidal waves whose amplitude and frequency depend on the images luminance.

from squiggledraw import SquiggleDrawer

First Example

This is a picture of my dog, Gus, sleeping.

gus sleeping Zzzz

We can use the SquiggleDrawer object to prepare the image before we convert it to vector graphics. Usually, it helps to increase the contrast and downsample the image, as each row of pixels will be turned into a single drawn line, and you don’t want too many lines.

squiggle_drawer = SquiggleDrawer()
squiggle_drawer.prep_image(fname='gus.jpg', contrast_cutoff=4, downsample_amount=16)
squiggle_drawer.show_image()

gus sleeping Zzzz

Now we can calculcate and render the squiggles. The paramters change how to squiggles are generated, and are described in the docstring.

squiggle_drawer.calculate_squiggles(
                    max_wave_num = 5,
                    wave_num_threshold = 1.2,
                    max_amplitude= 2,
                    amp_threshold = 1,
                    resolution = 20,
                    )

squiggle_drawer.render_squiggles( 
                width_in_inches = 8.5,
                height_in_inches = 11,
                border_in_inches = .5,
                line_width = .2,
                rgb=(0, 0, 0),
                )

This is what the resulting squiggles look like.

import IPython
IPython.display.SVG(data=squiggle_drawer.svgio.getvalue())

gus sleeping Zzzz

Another Example

fourier

squiggle_drawer = SquiggleDrawer()

squiggle_drawer.prep_image(fname='fourier.jpg', contrast_cutoff=10, downsample_amount=10)

squiggle_drawer.calculate_squiggles(
                    max_wave_num = 5,
                    wave_num_threshold = 3,
                    max_amplitude= 2.5,
                    amp_threshold = 1,
                    resolution = 20,
                    )

squiggle_drawer.render_squiggles( 
                width_in_inches = 8.5,
                height_in_inches = 11,
                border_in_inches = .5,
                line_width = .2,
                rgb=(0, 0, 0),
                )        

IPython.display.SVG(data=squiggle_drawer.svgio.getvalue())

fourier

squiggle_drawer.save_squiggle_image(fname='fourier_squiggled.svg')

Optimizing

I recomend you use an SVG optimizer like scour if you are going to use the files in a web application. This will reduce the file size and improve the rendering of the vector graphics.

! scour -i fourier_squiggled.svg -o fourier_squiggled_optimized.svg
Scour processed file "fourier_squiggled.svg" in 4446 ms: 3842833/5940874 bytes new/orig -> 64.7%

Pen Plotting

Heres that image after drawn with the pen plotter

fourier_drawn