Categories
halftoning

Halftoning with Reaction-Diffusion Patterns

The easiest way to make a reaction-diffusion pattern involves two blurs and a comparison. It doesn’t give the full fancy dynamics of other approaches, but it works well for halftoning.

This simple method avoids differential equations, and works with just image filtering. At each step, the activation chemical spreads with radius r_a, and the inhibition chemical spreads with radius r_i. Also, the intensity of the inhibition is scaled with a weight factor w. Then the regions that are more activated are drawn in white, versus the black inhibited regions. With an initial black and white pattern P, and a gaussian filtered version of it defined as filt(P,f), one iteration is as simple as:

P_{new} = \left( filt(P,f_i)*w < filt(P,r_a) \right) .

We’ve got to define the parameters, though. The radii are a bit arbitrary, except that the inhibition radius should be larger than the activation radius. Setting r_a=r_i/2 works well. The width of the dots and stripes is around r_i.

What is the effect of the activation weight? Let’s explore. Here’s the result when w increases across the image from 0 on the left to 1.5 on the right:

It looks like low values result in a dark image, and high values result in a bright image. That means that we can make a calibration curve and use it for halftoning! Very roughly, the output intensity is I \approx 1-\exp \left( -2.6*w+2 \right). Your calibration curve will vary, depending on how exactly you implement the filters. With the proper calibration curve in place, here’s an animation of the convergence:

It works! One thing that I didn’t mention is that it needs to be fed some noise to converge properly. Otherwise, it’s too black in some regions and white in others. Weak white noise works well.

But the squiggles don’t follow the edges. How can we fix that? Well, let’s introduce some anisotropy through the filters. I’ve been using radially symmetric gaussian filters, but now I want to intensify them along one axis. That is, each filter will take the form

f_i = exp\left(- \left( \frac{A^2}{2 (r_i/s)^2}+\frac{B^2}{2 r_i^2} \right) \right), with
A= x \cos(t)-y \sin(t) ,
B= x \sin(t)+y \cos(t) .

Thes filters have angle t, and anisotropy factor s. When s=1, these are radially symmetric again. An anisotropy level of s=0.5 works well for forming lines, without making them too straight. Intermediate values result in shorter, wigglier lines.

If just one angle is used, then the resulting image will have lines along that direction:

But this still doesn’t follow the orientation of the image. To do that, we need to extract an orientation field (\theta(x,y)) from the image. Then for each filter angle t_j, we need to filter P with its pair of oriented filters, and update the regions where the \theta(x,y) is closest to t_j. That gets us these:

With 3, 4, and 5 filter angles.

And let’s do each color channel separately:

Stripes and dots and color!

This sort of filtering gives another fun way to stylize images! Here are more examples:

If you want to play with the anistropic version with your webcam, check it out here.

5 replies on “Halftoning with Reaction-Diffusion Patterns”

Is it possible to generate the first circular pattern in a vector version (CDR, EPS)? Drawing by hand will take a long time ….

In my code, I would probably use a contour detection tool to find the outlines of each dark portion.
Practically, it’s probably better to use some sort of vectorization program. For example, https://www.vectorizer.io seems to work well.

Vectorization has a weak effect. You cannot see the constriction and expansion of the elements, but some waves. I could pay you for a vector file identical to the picture. Got a copy of your file? Please 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.