12,752,373 members (41,262 online)
alternative version

#### Stats

45.5K views
65 bookmarked
Posted 7 Oct 2012

# Oil Paint Effect: Implementation of Oil Painting Effect on an Image

, 20 Oct 2012 CPOL
 Rate this:
Applying oil painting effect on an image.

## Introduction

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]`.

```/*
/// Function to process Oil Painting Effect.
// Parameters:
pbyDataIn_i: Input RGB buffer of input image.
nRadius:     Radius of processing. This values is used to consider nearest pixels.
If this value is high, processing cost will increase. 2 ~5 are good values.
fIntensityLevels: Applied to r,g,b values intensity.
Increasing this values will create blocky output image.
nWidth: Width of image.
nHeight: Height of image.
pbyDataOut_o : Output RGB buffer.
*/
void PaintEffect::Process( const BYTE* pbyDataIn_i,
const float fIntensityLevels_i,
const int nWidth_i,
const int nHeight_i,
BYTE* pbyDataOut_o )
{
// nRadius pixels are avoided from left, right top, and bottom edges.
for( int nY = nRadius_i; nY < nHeight_i - nRadius_i; nY++)
{
for( int nX = nRadius_i; nX < nWidth_i - nRadius_i; nX++)
{
// Find intensities of nearest nRadius pixels in four direction.
for( int nY_O = -nRadius_i; nY_O <= nRadius_i; nY_O++ )
{
for( int nX_O = -nRadius_i; nX_O <= nRadius_i; nX_O++ )
{
int nR = pbyDataIn_i[( nX+nX_O) * 3  + ( nY + nY_O ) * nBytesInARow ];
int nG = pbyDataIn_i[( nX+nX_O) * 3  + ( nY + nY_O ) * nBytesInARow + 1];
int nB = pbyDataIn_i[( nX+nX_O) * 3  + ( nY + nY_O ) * nBytesInARow + 2];

// Find intensity of RGB value and apply intensity level.
int nCurIntensity =  ( ( ( nR + nG + nB ) / 3.0 ) * fIntensityLevels_i ) / 255;
if( nCurIntensity > 255 )
nCurIntensity = 255;
int i = nCurIntensity;
nIntensityCount[i]++;

nSumR[i] = nSumR[i] + nR;
nSumG[i] = nSumG[i] + nG;
nSumB[i] = nSumB[i] + nB;
}
}

int nCurMax = 0;
int nMaxIndex = 0;
for( int nI = 0; nI < 256; nI++ )
{
if( nIntensityCount[nI] > nCurMax )
{
nCurMax = nIntensityCount[nI];
nMaxIndex = nI;
}
}

pbyDataOut_o[( nX) * 3 + ( nY ) * nBytesInARow ] = nSumR[nMaxIndex] / nCurMax;
pbyDataOut_o[( nX) * 3 + ( nY ) * nBytesInARow + 1] = nSumG[nMaxIndex] / nCurMax;
pbyDataOut_o[( nX) * 3 + ( nY ) * nBytesInARow + 2] = nSumB[nMaxIndex] / nCurMax;
}
}
}```

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.

```// Find intensity of RGB value and apply intensity level.
int nCurIntensity =  ( ( ( nR + nG + nB ) / 3.0 ) * fIntensityLevels_i ) / 255; ```

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.

```// Sum of each pixels intensity is calculated.
nSumR[nCurIntensity] = nSumR[nCurIntensity] + nR;
nSumG[nCurIntensity] = nSumG[nCurIntensity] + nG;
nSumB[nCurIntensity] = nSumB[nCurIntensity] + nB;```

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.

```Final R = 949 / 7 = 135
Final G = 902 / 7 = 128
Final B = 458 / 7 = 65```

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

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 the Radius is high, then the processing cost will be high. The iterations inside `PaintEffectImpl` is as follows: Width * Height * (2 * Radius + 1 ) * (2 * Radius + 1 ).

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:

```// Calculating intensity with R,G,B values and Intensity parameter.
int nActualIntensity =  ( ( ( nR + nG + nB ) / 3.0 ) * IntensityParameter ) / 255;  ```

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.

```/*
Function to prepare Oil Painting Effect.
// Parameters:
pbyDataIn_i: Input RGB buffer of input image.
nRadius:     Radius of processing. This values is used to consider nearest pixels.
If this value is high, processing cost will increase. 2 ~5 are good values.
fIntensityLevels: Applied to r,g,b values intensity.
Increasing this values will create blocky output image.
nWidth: Width of image.
nHeight: Height of image.
pbyDataOut_o : Output RGB buffer.
*/
void PaintEffect::Process( const BYTE* pbyDataIn_i,
const float fIntensityLevels_i,
const int nWidth_i,
const int nHeight_i,
BYTE* pbyDataOut_o );```

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.

```// Code to copy effect applied data to Bitmap object.
HBITMAP hbmDest = ::CreateCompatibleBitmap(hdc, m_nImageWidth, m_nImageHeight);

if (hbmDest)
{
if (SetDIBits(hdc, hbmDest, 0, m_nImageHeight, m_pbyEffectAppliedData, &stBitmapInfo, DIB_RGB_COLORS))
{
m_BitmapObject.DeleteObject();
m_BitmapObject.Attach( hbmDest );
}
}```

## Drawing Image to Screen

Output bitmap is displayed to screen with GDI. `StretchBlit` is used to display the re-sized bitmap to the output window.

```void CPaintEffectDlg::DrawWithGDI()
{
// Use GDI method to draw the bitmap to screen.
CDC* pDC = GetDlgItem( IDC_STATIC_IMAGE )->GetDC();

if (m_BitmapObject.GetSafeHandle())
{
CDC dcMem;

if (dcMem.CreateCompatibleDC(pDC))
{
CBitmap* pOldBM = dcMem.SelectObject(&m_BitmapObject);
BITMAP BM;

m_BitmapObject.GetBitmap(&BM);

RECT ClientRegion;
GetDlgItem( IDC_STATIC_IMAGE )->GetClientRect( &ClientRegion );

// StretchBlt is used to resize the actual image to region of static window.
pDC->StretchBlt( 0, 0, ClientRegion.right, ClientRegion.bottom,
&dcMem, 0, 0, m_nImageWidth, m_nImageHeight, SRCCOPY );

dcMem.SelectObject(pOldBM);

}
GetDlgItem( IDC_STATIC_IMAGE )->ReleaseDC( pDC );
}
} ```

#### 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.

```// StretchBlt is used to resize the actual image to region of static window.
pDC->StretchBlt( 0, 0, ClientRegion.right, ClientRegion.bottom,
&dcMem, 0, 0, m_nImageWidth, m_nImageHeight, SRCCOPY ); ```

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.

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.

```void CPaintEffectDlg::OnButtonLoadBitmap()
{
// Create a file open Dialog for opening .bmp file.
CFileDialog* pFileOpenDlg = new CFileDialog( TRUE,L"image", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
L"Input Image (*.bmp;*.jpg;*.png;*.tga)|*.bmp;*.jpg;*.png;*.tga||");
if( IDOK == pFileOpenDlg->DoModal())
{
CString csFileName = pFileOpenDlg->GetPathName();
int nWidth = 0;
int nHeight = 0;
BYTE* pbyData = 0;
if( !BMPLoaderObj.LoadBMP( csFileName.GetBuffer( 0 ), nWidth, nHeight, pbyData ))
{
return;
}

ProcessEffect();

Invalidate( false );
}
}```

## 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.

```// To save the processed image.
void CPaintEffectDlg::OnBnClickedButtonSave()
{
// Construct file name. Radius and Intensity parameters are added in the deafult file name.
CString csFileName;
CFileDialog SaveDlg( false, L"*.bmp", csFileName );
if( IDOK == SaveDlg.DoModal())
{
CString csFileName = SaveDlg.GetPathName();
SaveBmp.SaveBMP( csFileName, m_nImageWidth, m_nImageHeight, m_pbyEffectAppliedData );
}
}```

## 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.

```// All windows other than image display window is moved in X direction.
MoveWindowInXDirection( IDC_EDIT_RADIUS, nParamWindowMoveX );
MoveWindowInXDirection( IDC_STATIC_RADIUS, nParamWindowMoveX );
MoveWindowInXDirection( IDC_EDIT_INTENSITY, nParamWindowMoveX );
MoveWindowInXDirection( IDC_STATIC_INTENSITY, nParamWindowMoveX );
MoveWindowInXDirection( IDC_STATIC_PARAM, nParamWindowMoveX );
MoveWindowInXDirection( IDC_BUTTON_LOAD_BITMAP, nParamWindowMoveX );
MoveWindowInXDirection( IDC_BUTTON_SAVE, nParamWindowMoveX );
MoveWindowInXDirection( IDC_BUTTON_ABOUT, nParamWindowMoveX );
MoveWindowInXDirection( IDC_CHECK_PAINTING_EFFECT, nParamWindowMoveX );
MoveWindowInXDirection( IDC_STATIC_STATUS, nParamWindowMoveX );
MoveWindowInXDirection( IDC_CHECK_OPENGL_DISPLAY, nParamWindowMoveX );```

## 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.

## Revision History

• 08-October-2012: Initial version.
• 13-October-2012: Solved painting issue, Added slider control to change Radius and Intensity.

## About the Author

 India
No Biography provided

## You may also be interested in...

 Pro Pro

## Comments and Discussions

 First PrevNext
 My Vote of 5 syed shanu5-Aug-15 23:55 syed shanu 5-Aug-15 23:55
 My vote of 5 Savalia Manoj M26-Jun-13 4:27 Savalia Manoj M 26-Jun-13 4:27
 Re: My vote of 5 Santhosh G_26-Jun-13 6:36 Santhosh G_ 26-Jun-13 6:36
 My vote of 5 @AmitGajjar26-Jun-13 4:25 @AmitGajjar 26-Jun-13 4:25
 Re: My vote of 5 Santhosh G_26-Jun-13 6:36 Santhosh G_ 26-Jun-13 6:36
 My vote of 5 Sreejith Member 81093134-Jun-13 0:06 Sreejith Member 8109313 4-Jun-13 0:06
 Re: My vote of 5 Santhosh G_4-Jun-13 8:19 Santhosh G_ 4-Jun-13 8:19
 My vote of 5 m_harriss5-May-13 21:21 m_harriss 5-May-13 21:21
 Re: My vote of 5 Santhosh G_6-May-13 6:00 Santhosh G_ 6-May-13 6:00
 My vote of 5 Mihai MOGA18-Nov-12 8:30 Mihai MOGA 18-Nov-12 8:30
 This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
 Re: My vote of 5 Santhosh G_22-Nov-12 2:33 Santhosh G_ 22-Nov-12 2:33
 My vote of 5 gndnet7-Nov-12 8:14 gndnet 7-Nov-12 8:14
 Re: My vote of 5 Santhosh G_8-Nov-12 20:37 Santhosh G_ 8-Nov-12 20:37
 Similar work, for reference Kochise5-Nov-12 22:37 Kochise 5-Nov-12 22:37
 My vote of 5 Michael Haephrati מיכאל האפרתי21-Oct-12 9:24 Michael Haephrati מיכאל האפרתי 21-Oct-12 9:24
 Re: My vote of 5 Santhosh_G21-Oct-12 15:47 Santhosh_G 21-Oct-12 15:47
 My vote of 1 BCantor16-Oct-12 14:25 BCantor 16-Oct-12 14:25
 Re: My vote of 1 Santhosh_G21-Oct-12 18:51 Santhosh_G 21-Oct-12 18:51
 Re: My vote of 1 BCantor22-Oct-12 11:50 BCantor 22-Oct-12 11:50
 Re: My vote of 1 stivo2229-Jan-14 11:58 stivo22 29-Jan-14 11:58
 Re: My vote of 1 MacSpudster30-Jan-14 6:26 MacSpudster 30-Jan-14 6:26
 My vote of 5 Monjurul Habib15-Oct-12 11:01 Monjurul Habib 15-Oct-12 11:01
 Re: My vote of 5 Santhosh_G15-Oct-12 15:45 Santhosh_G 15-Oct-12 15:45
 My vote of 5 ed welch13-Oct-12 5:16 ed welch 13-Oct-12 5:16
 Re: My vote of 5 Santhosh_G13-Oct-12 5:49 Santhosh_G 13-Oct-12 5:49
 Last Visit: 31-Dec-99 19:00     Last Update: 21-Feb-17 18:33 Refresh 12 Next »

General    News    Suggestion    Question    Bug    Answer    Joke    Praise    Rant    Admin

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.