This article demonstrates an application to create an oil painting effect of an image. Basically this effect is achieved by examining
the nearest pixels for all pixels.
For every pixel, it finds the maximum repeated color and that color will be considered as the output. In effect, we will get
a blocky image with less information,
and it will be similar to the painting effect of the image.

Screenshot of the application with painting effect.

Screenshot of the application without oil painting effect.

Details of the oil painting algorithm are explained below.

Oil Painting Effect Details

Analysis of nearest pixels is explained with a real example. A pixel from the above image is considered to analyze the
oil painting algorithm. This example considers
Radius as 2 and Intensity as 10.

For every pixel, the surrounding pixels are analysed. Find
the maximum repeated pixels within the area, and place it as output.
Processing of a pixel [X,Y] analyzes pixels from [X-Radius,Y-Radius] to [X+Radius,Y+Radius].

Here analyzing the nearest pixels of a pixel (X,Y), for this example, X,Y is at the center of
the blue rectangle. The nearest pixels of the pixel X,Y is displayed in the right top corner. We will analyse the intensity of these nearest pixels and find out the final R, G, B for pixel (X,Y) which will help create
an oil painting effect.

The RGB values of the nearest pixels are shown in the below image.

Intensity of the above pixels are calculated with the following logic. The average of R ,G, and B is multiplied with Intensity and the final intensity value
is prepared.

The calculated intensity of the nearest pixels are shown in the below image.

The intensity values of the nearest pixels range from 6 to 10. The final pixel value depends on the occurrence of the intensity values.
The most repeating intensity
value is considered for the output. From the above image, the most repeating intensity is 8.
Seven nearest pixels have the intensity value 8. On calculating the intensity of each pixel,
the sum of R, G, B corresponding to each intensity is also calculated.

The sum of R, G, B component for each intensity value is as shown below.

From the above table, the intensity value 8 is the most repeating intensity.
Seven nearest pixels have the intensity value 8, and therefore the output pixel is prepared from the sum
of R, G, B components corresponding to intensity 8.

And at last, RGB (131,151,82) at X,Y is changed to RGB (135,128,65) after analyzing RGB values of
the nearest pixels.

The output pixel is changed according to the intensity of the nearest pixels.
The output image is created by applying the same algorithm for all pixels. The maximum repeated pixels
are considered for output, and it removes small (smooth) changes in the image. In effect we will get a rough/blocky image. It will be similar to that of an oil painting effect.

Parameters of Painting Effect

Radius

This parameter decides the nearest pixels to analyze. For every pixel (X,Y)
the nearest pixels from (X-Radius, Y-Radius) to ( X+Radius, Y+Radius)
are analyzed to prepare the output image.

If Radius is increased, the computation cost of the algorithm will also increase and we will get a much blocky image. Suitable values of Radius
are from 3 to 7, which will produce
a good oil painting effect.

Intensity

This parameter is used to find the intensity of a pixel. The final intensity of a pixel is calculated by the following logic:

This parameter helps to change the intensity, in-effect the output image becomes blocky.

Details of PaintEffect Application

On loading a new image, the effect will be applied with the help of the PaintEffectImpl class. PaintEffectImpl::Process is used to prepare
the oil paint effect applied image. The prototype of PaintEffectImpl::Process is as follows.

The output image (painting effect applied image) will be available in m_pbyEffectAppliedData. This image is copied to a bitmap object. This bitmap will
be blitted into the static window on each repainting of the dialog.

The following code is used to copy the effect applied image to
the CBitmap object.

Why OpenGL Display?

Initially this application created without OpenGL support. GDI was used to display the output image into static window. But loading image in different size creates image with improper aspect ratio, and it create bad quality image. The static window[which displays the output image] in the application can display an image of dimension [400,300]. When user loads a new image with different size, a new Bitmap object is created in the actual size of the image, and algorithm is applied on the actual size image. But we have to display the new image in to a static window of dimension 400X300. Therefore CDC::StretchBlit API is used to display the Bitmap of actual size to 400X300.

Sometimes resizing of the image creates bad quality image as seen “OpenGL Display OFF” screen shot in the below Figure. If the resizing is performed with OpenGL texture mapping, the output quality will be good. OpenGL texture is created with GL_LINEAR interpolation type for MIN(minimizing) and MAG(Maximizing) interpolation type. Therefore resizing the texture uses Bi-Linear interpolation to create a good quality image.

The
following image shows the difference between StretchBlit and OpenGL display.

Load Image

A new image can be loaded with this button. The GDI+ library is used to get the image buffer of all types of image files. After loading a new image, Oil Paint Effect
is applied to the image and redrawn to the screen.

Save Image

Effect applied image can be saved to a file. The size of the input image is used as the size of output image.
The PaintEffect dialog will display the resized image
and it is not good to save the resized image. The file name is created with the
Radius and Intensity parameters.

Resizing the Dialog

The output image is drawn to screen by resizing the output image to fit into the static window in the dialog. Therefore loading
a new image with a different
size may stretch or skew the image. If the dialog can resize, the user can view the image in the desired size.
The WM_SIZE message is handled
and the static window which displays the image is resized according to the new size of the dialog. All controls except the static window which displays images
are moved in the X direction based on the size of new window.

Issues and Limitations

Time for processing a big image is too high. Currently the painting effect is applied in
a GUI thread, therefore processing a big sized image may take time for displaying the image.

If the Radius is a high value, then the processing time is too high. No parallel processing method
was tried. When I tried to prepare a shader, cg does not support array access
without a loop variable. Trying to prepare a GPU implementation of this effect.

Points of Interest

Preparing a real example was a little complex. I prepared another application to draw the nearest pixels in large size
and prepared the images used in the Details of Algorithm section.

I can easily prepare the output of the algorithm. But copying the output properly to screen took
a long time. Preparing a BITMAP object and copying this image
to a memory DC. In the WM_PAINT message, this memory DC is copied to
the output window.

Resizing of the dialog is handled in a special way. The MoveWindowInXDirection function is created to move a control in
the X direction. On each resize,
the required movement in X direction is identified and MoveWindowInXDirection is called for all controls. MoveWindowInXDirection gets
the previous position
of the control and calculates the new position and calls SetWindowPos to apply the new position.

On changing a parameter related to Oil Paint Effect or loading a new image, the dialog
stops responding, and the user won't get any notification.
Therefore a static button is added to indicate the algorithm processing is going on.
The "Processing Effect..." message indicates the processing of the algorithm is going on,
and we need to wait a few seconds to display the new image.

In first version, there was some painting issues when switching from Desktop and PaintEffect application. After long time of analysis, I observed that the WM_PAINT is called, and the entire scene was drawn. But sometimes it was not proper. I used the DC of static window by calling GetDlgItem() for drawing, and it caused the painting issue. When I prepared a CPaintDC from static window, the paining issue was solved.

Hi BCantor,
When I read this message, I thought I did some mistakes in this post.
I supposed that StretchBlit and OpenGL are familiar. Now I modified "Why OpenGL Display" section.
Thanks for your feedback.
Thanks for voting +5.

Yes, I did "copy" and "paste" various words from your article into a seeming reasonable sounding sentence to give the impression I'm some sort of expert on the topic and that I'm pointing out an "oversight" or something.

I've done this 3 times now (this and 2 other articles) and the code author actually finds something incorrect (not in my comment, but *because* of my comment) and then changes code or other info. It's small stuff, no dramatic errors.

But, if you think about it, we can get so wrapped up in our code that we can miss a simple thing or two and when we see/read something that "doesn't jive", our brains start rethinking and rechecking everything just to make sure we're correct. In that process, we can from time to time see some minor items not exactly how we thought it should be.

Think of it. When you see someone with a tie on tell them their tie is upside down as you point at it. When the look "down" at it, they'll say "No it's not." "Ah, but it is from *your* perspective, is it not?!"

Sort of like someone who is reasonably knowledgeable at something builds something and a total novice comes by and, with no clue other than tossing together words on the building plans into a reasonably sounding sentence says "The vertical stress points are off by 0.3% and will cause the foundation of the building to stress after 7.32 years .. other than that, the building is 5 thumbs up!" The builder is all "Hmm, that might be true." Then realizes what the novice said was jibbeish yet discovers that a different color paint would reduce the sun's solar affects on the outer north wall ... or something like that