Final Project 1997
Impressionist Filtering


Implementation of the paper "Processing Images and Video for An Impressionist Effect" by Peter Litwinowicz, published on SIGGRAPH'97 Proceedings, pages 407-414.


The paper describes an image sequence transformation in a new sequence that seems to be painted by free hand, inspired in an Impressionist style.


Antonio Scuri (
Paula Frederick (
Enylton Machado (
Mario Guimarães (
Raul Renteria (
Use this e-mail to send a message to all team :


The source code and the binaries are available at


The program was implemented in C with IUP (user interface), CD (graphical package) and IM (image file persistence) libraries. It works across all major-computing platforms : Windows 95/NT, Silicon Graphics, Sun, Linux e IBM RISC600.
The libraries are freeware for academical purpose. To acquire it, send a message to TeCGraf :
The documentation of the used libraries is available at :


Although there are many C files, only those beginning with imp are relevant for this project.
IMP.C contains the main module that manages the impressionist menu items and the algorithm starting point. The Impressionist menu items specify the algorithm parameters, except the Apply item, that set the filter over a RGB image resulting in a new image. The comprehension of the Apply function is fundamental to understand the other modules and how other pre-defined functions work , such as image creation, message display, file management, etc...
IMPBRUSH.C contains the drawing brush creation function and it is done.
IMPCLIP.C contains the brush clipping function, if the function is selected.
IMPTHETA.C contains the brush orientation calculus functions, if the brush is automatic through the gradient.
IMPPAINT.C contains the functions that draw the brush over the image, considering clipping and orientation.
Observation: the brush definition and the exported functions declaration are in IMP.H file.


The user controls the algorithm  through many parameters.
Dimensions: (Length, Length Delta, Radius, Radius Delta) the Delta values works as intervals for the main values random variation. (default: 7, 3, 1.75, 0.25)
Rendering I: (Color Interpolation or Color Average) the color of the brush is determined by the color of its center. In the center, the color can be interpolated by the neighbors colors or is an average of the neighbors colors. (default: interpolation)
Rendering II: (Solid Color or Texture) the color of the brush can be modified by a texture modulation combined with a transparency value. (default: solid color)
Spacing: the brushes are placed trough the image in a uniform regular grid, controlled by a determined spacing.. (default: 2)
Clipping: Each brush drawing can respect (or not) the original image borders. (default: off)
Gaussian Kernel Size: the borders calculus uses the Sobel filter magnitude applied in the intensities image of the original image filtered by a Gaussian. (default: 11)
Orientation: (Theta, Estimate Theta, or Estimate Theta on Areas) Each stroke orientation can be determined by following an orientation or trough an angle estimate using the normal trough the gradient located under the brush.
ATTENTION: The algorithms work only for 24 bpp RGB images. For other images types the filter menu is not available.


Impressionist Filter for an image
Generate Uniform Strokes
Calculate Intensity Image
Calculate Gaussian for Sobel
Calculate Sobel
Calculate Gaussian for Orientation
Calculate Stroke Orientation

For Each Brush
  Clip Brush
  Paint Brush
Some calculi are not executed upon the user choice. Although there is not necessary the brushes list for a unique image, it was used to prepare the code for the second part of the paper. Following the pseudo code and each proceeding description.


For Each Point in the grid [width / spacing, height / spacing]
  Generate a Brush in a free random position and mark the position as used.
Beyond distributing the strokes uniformly and randomly ordered, the algorithm also modify the size and width of the brush with  small random variations.


For Each Pixel
    intensity(x,y) = 0.30 red(x,y) + 0.59 green(x,y) + 0.11 blue(x,y)
Works with  a very used conversion to extract the intensity value of a RGB color.


for each pixel
  Calculate Gradient in X direction 
  Calculate Gradient in Y direction 
  Sobel = sqrt(Gx2 + Gy2)

                    -1  0  1
Gradient mask in X: -2  0  2
                    -1  0  1

                     1  2  1
Gradient mask in Y:  0  0  0
                    -1 -2 -1


For Each Pixel
  Gaussian(x,y) = (    Intensity(x-1,y+1) + 2*Intensity(x,y+1) +   Intensity(x+1,y+1)
                   + 2*Intensity(x-1,y)   + 4*Intensity(x,y)   + 2*Intensity(x+1,y)
                   +   Intensity(x-1,y-1) + 2*Intensity(x,y-1) +   Intensity(x+1,y-1) )/16;

                     1  2  1
Gaussian mask: 1/16  2  4  2
                     1  2  1
As convolution mask it was used a discretization of the Gaussian nucleus with 3x3 samples. To obtain  major grade discretizations we used this same mask several times.

This algorithm was implemented in order to obtain a better performance, but we have the disadvantage of using more memory, because we pre calculate 2*Intensity for all image. The performance gain will be obtained because for each pixel the calculus will be executed once instead of the 4 that would be necessary if we have used other implementation.

Using the symmetric of the original sign (mirror) extended the image for the borders calculus.


For Each Brush
  Calculate Gradient on Brush Center using Bilinear Interpolation
  Brush Orientation is normal to the gradient direction
As each brush stroke is located in any point we interpolate the gradient in this point with the neighbors gradients. If the gradient is different from zero, the stroke angle is the gradient angle plus 90o ; if the angle is zero we use a random value for the angle.


Given a stroke, determined by its center (cx, cy) and orientation (dirx e diry) we will limit its size based on the borders showed by the Sobel image parameter. The implemented algorithm follows the stages bellow:
- starting in the center (cx,cy) we should go trough (step by step) in the orientation given by Sobel image (dirx, diry) using a Bilinear Interpolation for the value calculus in the desired point.
- this course should finish when a distance larger than the original stroke distance would be achieved or when a border would be detected. A border is detected when the Sobel image value decreases, when it is lower than the last value given.
- In the end of the last stage we would have the first edge of the stroke.
- To specify the second edge we would repeat the same process, but using as course orientation (-dirx, -diry).


Calculate Brush Color
Calculate Brush Vertical Limits
For Each Discrete Line Between Vertical Limits
  Calculate Horizontal Limits
  For Each Pixel in the horizontal limits
    pixel = brush color
In this moment the brush drawing limits calculus considers the calculated clipping. The edges calculus try to make them round. In the brush borders there is an amortization of the pixel color value, considering the pixel value of the image.

PaintBrush with Texture

Calculate Brush Color
Calculate Brush Vertical Limits
For Each Discrete Line Between Vertical Limits
  Calculate Horizontal Limits
  For Each Pixel in the horizontal limits
    Calculate The Pixel Texture Coordinates, u and v
    alpha = alpha_texture[u, v] / 255
    intensity = intensity_texture[u, v] / 255
    back_color = pixel
    pixel = (brush_color * intensity - back_color) * alpha + back_color
Two texture images are used for the stroke render. One is used for defining intensity of the brush color and the other the brush transparency. The pixel color after the stroke will be:

actual_color * (transparency - 1) + brush_color * intensity * transparency

The intensity and transparency assume values inside the range of 0 to 1, while the pixel variable in the formula above refers to the pixel color before the stroke. The strokes are rectangular and without anti-aliasing because the borders soften effect can be obtained trough alpha texture.


The order which the stages of the algorithms were implemented determines an amount of necessary memory. We could reduce this quantity by modifying this order and executing some calculus simultaneously. These changes would complicate the comprehension of  the painting process code, so we decided to use a few more memory to make the code clearer and more similar with the paper orientation.


We have achieved some images that demonstrate the paper idea. The algorithms settled well and the development process was very productive.
Some examples of the filter application:



bird (312Kb)

impressionist bird (299Kb)

fruit (111Kb)

impressionist fruit (131Kb)

lenna (158Kb)

impressionist lenna (199Kb)

portrait (74Kb)

impressionist portrait (94Kb)

under_water (117Kb)

impressionistt under_water (130Kb)



The brush stroke draw could be optimized but it would complicate even more the complex code but we could have more soft and well finished results with the stroke. The actual result can be accepted by an artistic point of view, because this is a perception problem. We have not implemented the estimate angle algorithm for zero gradient areas. In this cases a random value was used.