Article

# Convolution of Bitmaps

, 29 Mar 2006
 Rate this:
Article discussing how to convolve images, specificially the convolution of bitmaps.

## Introduction

Image convolution plays an important role in computer graphics applications. Convolution of images allows for image alterations that enable the creation of new images from old ones. Convolution also allows for important features such as edge detection, with many widespread uses. The convolution of an image is a simple process by which the pixel of an image is multiplied by a kernel, or masked, to create a new pixel value. Convolution is commonly referred to as filtering.

## Details

First, for a given pixel (x,y), we give a weight to each of the surrounding pixels. It may be thought of as giving a number telling how important the pixel is to us. The number may be any integer or floating point number, though I usually stick to floating point since floats will accept integers as well. The kernel or mask that contains the filter may actually be any size (3x3, 5x5, 7x7); however, 3x3 is very common. Since the process for each is the same, I will concentrate only on the 3x3 kernels.

Second, the actual process of convolution involves getting each pixel near a given pixel (x,y), and multiplying each of the pixel's channels by the weighted kernel value. This means that for a 3x3 kernel, we would multiply the pixels like so:

```(x-1,y-1)       *       kernel_value[row0][col0]
(x  ,y-1)       *       kernel_value[row0][col1]
(x+1,y-1)       *       kernel_value[row0][col2]
(x-1,y  )       *       kernel_value[row1][col0]
(x  ,y  )       *       kernel_value[row1][col1]
(x+1,y  )       *       kernel_value[row1][col2]
(x-1,y+1)       *       kernel_value[row2][col0]
(x  ,y+1)       *       kernel_value[row2][col1]
(x+1,y+1)       *       kernel_value[row2][col2]```

The process is repeated for each channel of the image. This means that the red, green, and blue color channels (if working in RGB color space) must each be multiplied by the kernel values. The kernel position is related to the pixel position it is multiplied by. Simply put, the kernel is allocated in `kernel[rows][cols]`, which would be `kernel[3][3]` in this case. The 3x3 (5x5 or 7x7, if using a larger kernel) area around the pixel (x,y) is then multiplied by the kernel to get the total sum. If we were working with a 100x100 image, allocated as `image[100][100]`, and we wanted the value for pixel (10,10), the process for each channel would look like:

```float fTotalSum =
Pixel(10-1,10-1)       *       kernel_value[row0][col0] +
Pixel(10  ,10-1)       *       kernel_value[row0][col1] +
Pixel(10+1,10-1)       *       kernel_value[row0][col2] +
Pixel(10-1,10  )       *       kernel_value[row1][col0] +
Pixel(10  ,10  )       *       kernel_value[row1][col1] +
Pixel(10+1,10  )       *       kernel_value[row1][col2] +
Pixel(10-1,10+1)       *       kernel_value[row2][col0] +
Pixel(10  ,10+1)       *       kernel_value[row2][col1] +
Pixel(10+1,10+1)       *       kernel_value[row2][col2] +```

Finally, each value is added to the total sum, which is then divided by the total weight of the kernel. The kernel's weight is given by adding each value contained in the kernel. If the value is zero or less, then a weight of 1 is given to avoid a division by zero.

The actual code to convolve an image is:

```for (int i=0; i <= 2; i++)//loop through rows
{
for (int j=0; j <= 2; j++)//loop through columns
{
//get pixel near source pixel
/*
if x,y is source pixel then we loop through and
get pixels at coordinates:
x-1,y-1
x-1,y
x-1,y+1
x,y-1
x,y
x,y+1
x+1,y-1
x+1,y
x+1,y+1
*/
COLORREF tmpPixel = pDC->GetPixel(sourcex+(i-(2>>1)),
sourcey+(j-(2>>1)));
//get kernel value
float fKernel = kernel[i][j];
//multiply each channel by kernel value, and add to sum
//notice how each channel is treated separately
rSum += (GetRValue(tmpPixel)*fKernel);
gSum += (GetGValue(tmpPixel)*fKernel);
bSum += (GetBValue(tmpPixel)*fKernel);
//add the kernel value to the kernel sum
kSum += fKernel;
}
}
//if kernel sum is less than 0, reset to 1 to avoid divide by zero
if (kSum <= 0)
kSum = 1;
//divide each channel by kernel sum
rSum/=kSum;
gSum/=kSum;
bSum/=kSum;```

The source code included performs some common image convolutions. Also included is a Convolve Image menu option that allows users to enter their own kernel. Common 3x3 kernels include:

```gaussianBlur[3][3] = {0.045, 0.122, 0.045, 0.122,
0.332, 0.122, 0.045, 0.122, 0.045};
gaussianBlur2[3][3] = {1, 2, 1, 2, 4, 2, 1, 2, 1};
gaussianBlur3[3][3] = {0, 1, 0, 1, 1, 1, 0, 1, 0};
unsharpen[3][3] = {-1, -1, -1, -1, 9, -1, -1, -1, -1};
sharpness[3][3] = {0,-1,0,-1,5,-1,0,-1,0};
sharpen[3][3] = {-1, -1, -1, -1, 16, -1, -1, -1, -1};
edgeDetect[3][3] = {-0.125, -0.125, -0.125, -0.125,
1, -0.125, -0.125, -0.125, -0.125};
edgeDetect2[3][3] = {-1, -1, -1, -1, 8, -1, -1, -1, -1};
edgeDetect3[3][3] = {-5, 0, 0, 0, 0, 0, 0, 0, 5};
edgeDetect4[3][3] = {-1, -1, -1, 0, 0, 0, 1, 1, 1};
edgeDetect5[3][3] = {-1, -1, -1, 2, 2, 2, -1, -1, -1};
edgeDetect6[3][3] = {-5, -5, -5, -5, 39, -5, -5, -5, -5};
sobelHorizontal[3][3] = {1, 2, 1, 0, 0, 0, -1, -2, -1 };
sobelVertical[3][3] = {1, 0, -1, 2, 0, -2, 1, 0, -1 };
previtHorizontal[3][3] = {1, 1, 1, 0, 0, 0, -1, -1, -1 };
previtVertical[3][3] = {1, 0, -1, 1, 0, -1, 1, 0, -1};
boxBlur[3][3] = {0.111f, 0.111f, 0.111f, 0.111f,
0.111f, 0.111f, 0.111f, 0.111f, 0.111f};
triangleBlur[3][3] = { 0.0625, 0.125, 0.0625,
0.125, 0.25, 0.125, 0.0625, 0.125, 0.0625};```

Last but not least is the ability to show a convoluted image as a grayscale result. In order to display a filtered image as grayscale, we just add a couple lines to the bottom of the `Convolve` function:

```//return new pixel value
if (bGrayscale)
{
int grayscale=0.299*rSum + 0.587*gSum + 0.114*bSum;
rSum=grayscale;
gSum=grayscale;
bSum=grayscale;
}

clrReturn = RGB(rSum,gSum,bSum);```

This means that the entire `Convolve` function now looks like:

```COLORREF CImageConvolutionView::Convolve(CDC* pDC, int sourcex,
int sourcey, float kernel[3][3], int nBias,BOOL bGrayscale)
{
float rSum = 0, gSum = 0, bSum = 0, kSum = 0;
COLORREF clrReturn = RGB(0,0,0);
for (int i=0; i <= 2; i++)//loop through rows
{
for (int j=0; j <= 2; j++)//loop through columns
{
//get pixel near source pixel
/*
if x,y is source pixel then we loop
through and get pixels at coordinates:
x-1,y-1
x-1,y
x-1,y+1
x,y-1
x,y
x,y+1
x+1,y-1
x+1,y
x+1,y+1
*/
COLORREF tmpPixel = pDC->GetPixel(sourcex+(i-(2>>1)),
sourcey+(j-(2>>1)));
//get kernel value
float fKernel = kernel[i][j];
//multiply each channel by kernel value, and add to sum
//notice how each channel is treated separately
rSum += (GetRValue(tmpPixel)*fKernel);
gSum += (GetGValue(tmpPixel)*fKernel);
bSum += (GetBValue(tmpPixel)*fKernel);
//add the kernel value to the kernel sum
kSum += fKernel;
}
}
//if kernel sum is less than 0, reset to 1 to avoid divide by zero
if (kSum <= 0)
kSum = 1;
//divide each channel by kernel sum
rSum/=kSum;
gSum/=kSum;
bSum/=kSum;
rSum += nBias;
gSum += nBias;
bSum += nBias;
//prevent channel overflow by clamping to 0..255
if (rSum > 255)
rSum = 255;
else if (rSum < 0)
rSum = 0;
if (gSum > 255)
gSum = 255;
else if (gSum < 0)
gSum = 0;
if (bSum > 255)
bSum = 255;
else if (bSum < 0)
bSum = 0;
//return new pixel value
if (bGrayscale)
{
int grayscale=0.299*rSum + 0.587*gSum + 0.114*bSum;
rSum=grayscale;
gSum=grayscale;
bSum=grayscale;
}

clrReturn = RGB(rSum,gSum,bSum);
return clrReturn;
}```

Last but not least, I did a little tweaking to get the program to load a default image from a resource (`IDB_BITMAP1`). Then, I added the ability to convolve this default image. The program will still load image from a file, the only difference is that it will now show a default image at startup.

Please note that this article is, by no means, an example of fast processing of pixels. It is merely meant to show how convolution can be done on images. If you would like a more advanced image processor, then feel free to email me with the subject "WANT CODE:ImageEdit Please". That is an unreleased image processor I have done, though parts are not implemented yet due to lack of time, that contains much more functionality, using the CxImage library as its basis for reading and saving images.

A list of licenses authors might use can be found here

## You may also be interested in...

Web Developer
United States
Programming using MFC and ATL for almost 12 years now. Currently studying Operating System implementation as well as Image processing. Previously worked on DSP and the use of FFT for audio application. Programmed using ADO, ODBC, ATL, COM, MFC for shell interfacing, databasing tasks, Internet items, and customization programs.

 FirstPrev Next
 Re: 24 bit RGB Fred Ackers 13-Apr-06 4:03
 Re: 24 bit RGB ñoqui 13-Apr-06 4:40
 Does not compile in MSVC 2003 Dark Alchemist 6-Jan-06 8:27
 Re: Does not compile in MSVC 2003 Aqiruse 29-Mar-06 9:08
 please , help ? dynamica123 1-Oct-05 22:03
 Terrible Christian Graus 30-Mar-05 16:02
 Re: Terrible Georgi Petrov 30-Mar-05 19:17
 Re: Terrible Christian Graus 31-Mar-05 12:03
 Re: Terrible Georgi Petrov 1-Apr-05 1:19
 Re: Terrible Christian Graus 1-Apr-05 8:49
 Re: Terrible Rick York 29-Mar-06 12:21
 Re: Terrible Christian Graus 29-Mar-06 12:23
 Re: Terrible Fred Ackers 30-Mar-06 0:22
 Re: Terrible JKaminski 7-Apr-06 2:55
 Re: Terrible Christian Graus 1-Apr-05 8:50
 Re: Terrible Mohammad A Gdeisat 31-Jul-06 9:32
 O! Yes it is excelent Georgi Petrov 26-Aug-04 0:45
 Re: O! Yes it is excelent Christian Graus 30-Mar-05 16:04
 Re: O! Yes it is excelent Georgi Petrov 30-Mar-05 19:15
 Re: O! Yes it is excelent Christian Graus 31-Mar-05 12:00
 Re: O! Yes it is excelent wittend 22-Oct-07 10:02
 Grayscale conversion Erik_Egsgard 20-Aug-04 3:45
 Re: Grayscale conversion Aqiruse 20-Aug-04 13:58
 Re: Grayscale conversion Erik_Egsgard 21-Aug-04 18:29
 Re: Grayscale conversion Erik_Egsgard 24-Aug-04 2:57
 Re: Grayscale conversion Georgi Petrov 8-Nov-04 0:52
 Re: Grayscale conversion Oystein F. Saebo 7-Feb-06 7:44
 Nice work Jerry Evans 20-Aug-04 2:48
 Re: Nice work Aqiruse 20-Aug-04 13:59
 Re: Nice work Christian Graus 30-Mar-05 16:00
 What wrong with this code? gilazilla 18-Aug-04 4:41
 Re: What wrong with this code? Aqiruse 18-Aug-04 8:27
 The code I tried was as follows: ``` //This is the code I used in the OnDraw function of my sample application //I used hBitmap instead of m_Bitmap since I don't know what m_Bitmap is or where it was loaded HBITMAP hBitmap = (HBITMAP)::LoadImage(NULL,pDoc->m_szFilename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION); CDC MemDC; MemDC.CreateCompatibleDC (NULL); MemDC.SelectObject (&hBitmap);//use hBitmap instead BITMAP bm; ::GetObject( hBitmap, sizeof( bm ), &bm );//Load bitmap info   pDC->BitBlt (20, 20, bm.bmWidth, //used bitmap info instead of m_BitmapWidth bm.bmHeight,//used bitmap info instead of m_BitmapHeight &MemDC, 0, 0, SRCCOPY);   CDC resultDC; CBitmap m_result;   m_result.CreateCompatibleBitmap(pDC,bm.bmWidth,bm.bmHeight); resultDC.CreateCompatibleDC (NULL); resultDC.SelectObject (&hBitmap);   for( int y = 0; y < bm.bmHeight ; y++ )//use bitmap info { for( int x = 0; x < bm.bmWidth ; x++ )//use bitmap info { //set pixel to color returned by convolve resultDC.SetPixel(x,y,Convolve(&MemDC,x,y,m_kernel)); } }   //Display Result pDC->BitBlt (300, 20, bm.bmWidth, //use bitmap info bm.bmHeight, //use bitmap info &resultDC, 0, 0, SRCCOPY);   //This function is exactly the same as yours. I did not change anything in here COLORREF CImageConvolutionView::Convolve(CDC *pDC, int sourcex, int sourcey, float kernel[][3]) { float rSum = 0, gSum = 0, bSum = 0, kSum = 0; float grayscale =0; COLORREF clrReturn = RGB(0,0,0); for (int i=0; i <= 2; i++)//loop through rows { for (int j=0; j <= 2; j++)//loop through columns { COLORREF tmpPixel = pDC->GetPixel(sourcex+(i-(2>>1)),sourcey+(j-(2>>1)));   float fKernel = kernel[i][j];   rSum += (GetRValue(tmpPixel)*fKernel); gSum += (GetGValue(tmpPixel)*fKernel); bSum += (GetBValue(tmpPixel)*fKernel);   kSum += fKernel; } }   if (kSum <= 0) kSum = 1;   rSum/=kSum; gSum/=kSum; bSum/=kSum; if (rSum > 255) rSum = 255; else if (rSum < 0) rSum = 0; if (gSum > 255) gSum = 255; else if (gSum < 0) gSum = 0; if (bSum > 255) bSum = 255; else if (bSum < 0) bSum = 0;   grayscale=0.299*rSum + 0.587*gSum + 0.114*bSum; rSum=grayscale; gSum=grayscale; bSum=grayscale;   clrReturn = RGB(rSum,gSum,bSum); return clrReturn;   }   ``` When I compiled and tested the above code in my sample application, the code worked fine and I received a proper grayscale image! I tested the above code with several different images and I received accurate results with each image I tested. I would check to make sure that your m_Bitmap contains a properly loaded bitmap image. That is why I always display the image before performing a convolution on it. Otherwise, I don't know what to say other than your code worked fine for me... Sorry, I can't be of more help.   Nothing is impossible, It's merely a matter of finding an answer to the question of HOW? ... And answering that question is usually the most difficult part of the job!!!
 This is what i want hasansheik 22-Jun-04 4:20
 Nice article PixiGreg 28-Mar-04 3:40
 A more flexible & efficient method Aris A S 27-Mar-04 5:57
 Re: A more flexible & efficient method Rick York 27-Mar-04 19:17
 Re: Sorry, this will correct it ariest 28-Mar-04 20:51
 Last Visit: 31-Dec-99 18:00     Last Update: 28-Aug-14 20:42 Refresh 1