I implemented the painterly rendering algorithm presented in "Painterly Rendering with Curved Brush Strokes of Multiple Sizes" by Aaron Hertzman. This is an automated technique for rendering an image with different painting styles. The technique is motivated by an artist's tendency to draw a rough abstraction of an image, then iteratively draw in details with finer brush strokes.

This is achieved programmatically by bluring the source image with a Gaussian kernel with a standard deviation related to the brush size. The result of this operation is a reference image for that particular level of abstraction. As the brush strokes reduce in size, the deviation of the reference image decreases and reveals regions which require repainting by the algorithm to better convey the detail of the source image. In this way, painting of the image proceeds hierarchically. User defined parameters relating to stroke length, threshold for redrawing, neighborhood size, etc, define a particular style of painting. Let's consider an example, constructed with the parameters for an impressionist style painting.

Original Image

Referencce images computed for brush sizes of radius 8, 4, 2, respectively.

Painting the image with brush sizes of radius 8, 4, 2, respectively.

Finally, the following is the output of the painterly algorithm, with layers painted progressively over the canvas.

Some of the underlying brush strokes of the first two layers can be seen. The first layer, corresponding to brush radius 8, has 1,500 strokes, the second layer has 10,477 strokes, and the third and most detailed layer has 46,563 strokes.

The extension from the pseudo-code in the paper to the implementation was fairly straightforward. However, I noticed one shortcoming. The algorithm does not determine what to do with pixels with a vanishing gradient, and hence it does not respond well to images with large regions of constant color. As an example, consider the following image, taken on a foggy day. The painted Bridge image on the left has regions where painting strokes are not defined for. Through explicit checks against vanishing gradients, regions of the image may be left unpainted. The painting in the center highlights all such undefined stroke points with a red point size of 3. To compensate for this, I modified the vanishing gradient check to sample the reference image at the zero gradient points, and paint the undefined area with a brush size of similar radius to the brush stroke used in the pixel's neighborhood. The result can be seen on the right. The 'Impressionist' style parameters are used.


A few more examples of painterly rendering can be seen below, with the original image followed by Impressionist, Expressionist, and Colorist Wash renderings. The following parameters were set to render each style:


paintBrushRadii = {8,4,2} fCurve = 1.0 fSigma = 0.5 fGamma = 1.0 maxStrokeLength = 16 minStrokeLength = 4 eThresh = 2.0 //error Threshold used in paintLayer


paintBrushRadii = {8,4,2} fCurve = 0.25 fSigma = 0.5 fGamma = 1.0 maxStrokeLength = 16 minStrokeLength = 10 eThresh = 1.0 //error Threshold used in paintLayer

Colorist Wash:

paintBrushRadii = {8,4,2} fCurve = 1.0 fSigma = 0.5 fGamma = 1.0 maxStrokeLength = 16 minStrokeLength = 4 eThresh = 4.0 //error Threshold used in paintLayer


Bottle Vase


All photographs used on this page are of my own.