Impressionist Painter
A Project in Non-Photorealistic Rendering
Download:
Impressionist Painter (Win32, 882 kB)
Overview:
The application is an implementation of Haeberli's paper "Paint by Numbers" (SIGGRAPH '90), applet here. The user selects an input image, and then paints onto a blank canvas, where colour is sampled from the image. The user can select the type of brush, stroke thickness and distribtion settings, impose an ordering on the strokes, and choose whether and how the strokes should orient themselves in the direction of edges in the original image. The result is an image which appears to be artistically in the impressionist style.
Images:
Audi of Audis |
Depth |
Blocky |
Blocks Wavy |
Lines Wavy |
Lines Noisy |
Audi of Circles |
Chloe on Bike |
Bumpy Slabs |
Features:
- Brush selection: Circles, Lines, Crosses, Blocks, even 3D models. Click here to see an Audi made of Audis.
- Stroke size sorting: Often, brush strokes that are smaller define areas which contain more detail. Since the implementation uses OpenGL, I take advantage of the Z-Buffer to (optionally) depth-sort the strokes painted onto the canvas. (This produced an interesting effect.)
- Edge detection: Brush strokes can be oriented to follow edges, by using gradient directions in the local neighbourhood of the stroke. (Originally, I precomputed a vector field when the image was loaded, but this took considerable time (30 seconds or so) so I opted for the near-equivalent solution of computing the orientations within a local neighbourhood as each stroke is laid.) The user can select between two types of weights for contributing gradients.
Instructions:
- Unzip impressionist.zip to a folder, and run impressionist.exe
- Type the filename to load where it says "Image Filename" and then click the "Load Image" button (a default filename is included that works to get started quickly)
- Left click and drag onto the white canvas to start painting
- You can select between different types of brushes in the Brushes panel
- You can load a 3D model to be used as a brush by typing in the filename of the model and then pressing the "Load OBJ Model" button (a default filename is provided that works to get started quickly)
- You can specify the location of the light by right clicking anywhere on the canvas, 3D brushes will be lit from that direction. This can be used to specify areas of emphasis in your images.
- You can manipulate how the strokes are placed in the Advanced rollout, by setting the number of strokes which are simultaneously placed, the maximum radius the strokes will placed from the cursor, and the width of the strokes. You can also specify whether you would like smaller strokes to appear in front of larger ones, or whether the stroke last drawn should be frontmost.
- You can have the brush strokes automatically orient themselves along edges in the image by checking the box labelled "Follow Edges". You can choose between two weighting schemes for how gradients contribute to each brush's orientation - the "Reciprocal" weighting produces more gentle sweeping brush strokes, while the "Exponential" weighting is much noisier but follows the edges more precisely. The "Gradient Threshold" spinner specifies the minimum gradient magnitude for contribution to the orientation.
- If using print-screen isn't your style, you can save your creations by specifying a filename in the text field, and pressing "Save Image". Note that the image is saved as an uncompressed TGA format file.
Implementation Details:
The project was implemented in C++. Libraries used include: OpenGL (graphics), DevIL (image processing), GLUI (user interface widgets). Uses code by Rob Bateman to export TGA files. Uses a modified Vector class originally by Allen Sherrod. The image of the girl holding the apple is a mural by Albert Castro.
The components of this project I implemented are:
- The user interface - I wrote the OpenGL code for orthographic projection, and set up the right pane that has all the widgets by manipulating objects from the GLUI library
- The OBJ (3d model) loader class - this is a class I implemented for use in a game I'd been working on a couple years ago, that found its way into this project as a type of brush. I think that using a brush which is the actual object being painted is a neat concept.
- Vector field computation - I wrote code to compute the spatial gradients. Drawing lines perpendicular to strong gradients alone produced a neat artistic effect:
The approach to determine the orientation at each pixel location p is to go through all other pixel locations p' whose gradients are of a minimum magnitude, weigh them based on their distance from p (the two formulas which the user can select are: 1.0/sqrt((p_x-p'_x)^2+(p_y-p'_y)^2) or exp(-sqrt((p_x-p'_x)^2+(p_y-p'_y)^2))), add the weighted arctans of the gradient vectors together, divide by the total weight contributions (to normalize) and then add 90 degrees (to follow edges rather than point perpendicular to them). Drawing strokes as outlined rectangular strips clearly illustrates the different results from two ways of weighting:
Reciprocal - sweeping, gradual
|
Exponential - noisy, precise
|
There was a problem with speed. Originally, I computed this field of vectors after the image is loaded, and it took roughly 30 seconds to 1 minute for a reasonably sized image. To make things faster, I opted for two things - one is to compute the orientations for each stroke as they are painted (which makes sense as the orientations for most pixels aren't looked at anyway) , two is to look within a smaller neighbourhood around each pixel p, instead of the entire image (as pixels p' that are far from p hardly contribute anyway). After making these changes, there is still noticeable slowdown as you paint with strokes that follow edges, but not so much that painting is not at an interactive rate.