There’s a whole body of literature out there for illustrating vector fields. One topic is ‘evenly spaced streamlines’: lines that follow a direction field, and don’t get too close to each other.

How do we do this? From a starting point, use the midpoint method to figure out the position of the next point. If the new point is far enough from other streamlines, draw a line from the old point to the new one, and repeat until you get too close.

Unfortunately, this has some difficulties. It gets expensive to constantly check the exact distance to all the other streamlines. And if you don’t check the distance to the current streamline, you may end up self-intersecting. But you should also ignore the points that were just drawn on the same line. How should we deal with these problems? Use a bitmap!

The general idea is to keep track of whether a pixel has been visited yet, and if so, how long ago. If it was visited long ago, we know that a line went through there, so we shouldn’t continue the streamline. If it was visited extremely recently, then it’s probably the current line’s steps through that pixel, so we can ignore this intersection.

Let’s say that no two lines can occupy the same pixel in bitmap **B**. First, some definitions. When integrating along the streamline, the step length is **ds**, where **ds**<1, measured in pixels. Initialize **B** as all zeros, and the initial timestep value as **t=0**. Assuming that the streamline is started in a good place, here’s the algorithm:

- Increment
**t** - Find the pixel location
**(x,y)**for the next point along the streamline, and the value**b**=**B(x,y)**.- If
**b**==0, the pixel has not been visited before. Set**B(x,y)**=**t**, and continue drawing. - If
**b**>0, the pixel has been visited before.- If (
**t**–**B(x,y)**) <**1/ds**, you’re spooked by your own footprints. This was recently set by the current streamline, so ignore it and keep drawing. - Otherwise, you’re crossing the path of another line, so don’t continue. Start a new line somewhere else.

- If (

- If

There can be other termination conditions as well, such as going out of bounds, or drawing a line that is too long.

Let’s test this out with the canonical flow around a spinning cylinder. Here’s a Line Integral Convolution visualization of that flow:

With randomly chosen starting points, we get:

Which is a good start, but there’s a feeling of uneven spacing. How about choosing start points that are as close as possible to other lines? Or as far as possible? Or at a couple pixels away?

Packing them in closely is not very good. It leads to a lot of short lines. As far as possible works well, it seems. This suggests starting the lines at least 1 px away from other lines, and as far as possible.

Now let’s throw it at images! How does the Mona Lisa look as evenly spaced streamlines?

It looks neat. It basically just shows swirls and edges, rather than tones. For tones, we can we can hatch along the streamlines to do some halftoning!

Neat.