# Accelerated Smooth Bitmap Resizing

### WEBINAR:On-Demand

Application Security Testing: An Integral Part of DevOps

Environment: VC6, Win32

When we want to resize a bitmap and also the resulting bitmap to be of the highest possible quality, the Win32 AIP function StretchBlt() is not of much help. There are already articles about this task in the CodeGuru sections, but the proposed algorithms take considerable amount of memory when used on large bitmaps - with dimensions of several thousand pixels on each axis.

The function ScaleBitmap() uses an algorithm adjusted to use minimum additional memory and not sacrificing the overall performance. The prototype of the function is

```HBITMAP ScaleBitmap(HBITMAP hBmp,
WORD wNewWidth,
WORD wNewHeight);
```

The only limitation is that both wNewWidth and wNewHeight should be greater or lesser than the dimensions of the original bitmap. The returned bitmap is compatible with the screen device context.

The code below, let's mark it as variant A, produces images of good quality, but because of the large number of floating point calculations it is not as fast as we want.

The main reserve for improving the performance of the routine is in the replacement of all floating-point calculations with integer ones. How could we do that without degradation of image quality?

Actually we don't need more than 3 or 4 digits after the decimal point to produce the same result - the result after all is just a byte [0..255]. The obvious solution is to replace all floating numbers with numbers with fixed decimal point. By example we should replace the number 0.00345 with 345 and also divide the final result by 100000.
OK - let's do it.

Oops!!! In the code we should multiply these fixed-point numbers. Because we are limited to 4-byte integer numbers for performance reasons (means 2,147,483,648 to 2,147,483,647), we can't use 4 digits after the decimal point, because 256 * 10000 * 10000 exceeds 4-byte integer range and somewhere we will get a silent overflow. It seems that we should use the available 3 digits and be happy with that.

This is not so bad, but who limits us to use just the powers of 10 to divide our fixed-point numbers. With the same success we can use the powers of 16, moreover the division by such a number equals to shifting right the number. Let's do some calculations. We have 4 bytes - that means 32 bits. We need 8 bits for the color values and the remaining of 24 bits leads us to decision to use 0x1000 (or 1 << 12) as the divisor for our fixed-point numbers. If the numbers are unsigned (and they are) we shouldn't have troubles with overflows.

As a result we will have about 3,6 valid decimal digits in our fixed-point numbers - it's closer to 4 digits than just 3 digits.

The second piece of code (variant B) uses this method to do all calculations with fixed-point integer numbers. This leads to obvious acceleration of the performance of code B compared to performance of code A, but how big is it?

The following table represents the performance gain of code B over code A on processing a 305x435 pixels image.

 Result size / Original size 5% 10% 25% 50% 75% 100% 125% 150% 200% 300% Performance gain 24% 25% 34% 52% 59% 73% 67% 71% 75% 76%

Considering that along with the calculations the code does also a lot of other work, we can conclude that the 4-byte integer calculations are 3 to 4 times faster than these with floating point numbers.

### Variant A

```//
// Smooth bitmap resize
//
// Ivaylo Byalkov, November 16, 2000
//

#include "stdafx.h"
#include <math.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////

// helper function prototypes
static BITMAPINFO *PrepareRGBBitmapInfo(WORD wWidth, WORD wHeight);
static void ShrinkData(BYTE *pInBuff, WORD wWidth, WORD wHeight,
BYTE *pOutBuff, WORD wNewWidth, WORD wNewHeight);
static void EnlargeData(BYTE *pInBuff, WORD wWidth, WORD wHeight,
BYTE *pOutBuff, WORD wNewWidth, WORD wNewHeight);

///////////////////////////////////////////////////////////
// Main resize function

HBITMAP ScaleBitmap(HBITMAP hBmp, WORD wNewWidth, WORD wNewHeight)
{
BITMAP bmp;
::GetObject(hBmp, sizeof(BITMAP), &bmp);

// check for valid size
if ((bmp.bmWidth > wNewWidth
&& bmp.bmHeight < wNewHeight)
|| bmp.bmWidth < wNewWidth
&& bmp.bmHeight > wNewHeight))
return NULL;

HDC hDC = ::GetDC(NULL);
BITMAPINFO *pbi = PrepareRGBBitmapInfo((WORD)bmp.bmWidth,
(WORD)bmp.bmHeight);

::GetDIBits(hDC,
hBmp,
0,
bmp.bmHeight,
pData,
pbi,
DIB_RGB_COLORS);

delete pbi;
pbi = PrepareRGBBitmapInfo(wNewWidth, wNewHeight);

if(bmp.bmWidth >= wNewWidth
&& bmp.bmHeight >= wNewHeight)
ShrinkData(pData, (WORD)bmp.bmWidth, (WORD)bmp.bmHeight,
pData2, wNewWidth, wNewHeight);
else
EnlargeData(pData, (WORD)bmp.bmWidth, (WORD)bmp.bmHeight,
pData2, wNewWidth, wNewHeight);

delete pData;

HBITMAP hResBmp = ::CreateCompatibleBitmap(hDC,
wNewWidth,
wNewHeight);

::SetDIBits(hDC,
hResBmp,
0,
wNewHeight,
pData2,
pbi,
DIB_RGB_COLORS);

::ReleaseDC(NULL, hDC);

delete pbi;
delete pData2;

return hResBmp;
}

///////////////////////////////////////////////////////////

BITMAPINFO *PrepareRGBBitmapInfo(WORD wWidth, WORD wHeight)
{
BITMAPINFO *pRes = new BITMAPINFO;
::ZeroMemory(pRes, sizeof(BITMAPINFO));
pRes->bmiHeader.biSizeImage = ((3 * wWidth + 3) & ~3)
* wHeight;

return pRes;
}

///////////////////////////////////////////////////////////

static float *CreateCoeff(int nLen, int nNewLen, BOOL bShrink)
{
int nSum = 0, nSum2;
float *pRes = new float[2 * nLen];
float *pCoeff = pRes;
float fNorm = (bShrink)? (float)nNewLen / nLen : 1;
int	  nDenom = (bShrink)? nLen : nNewLen;

::ZeroMemory(pRes, 2 * nLen * sizeof(float));
for(int i = 0; i < nLen; i++, pCoeff += 2)
{
nSum2 = nSum + nNewLen;
if(nSum2 > nLen)
{
*pCoeff = (float)(nLen - nSum) / nDenom;
pCoeff[1] = (float)(nSum2 - nLen) / nDenom;
nSum2 -= nLen;
}
else
{
*pCoeff = fNorm;
if(nSum2 == nLen)
{
pCoeff[1] = -1;
nSum2 = 0;
}
}
nSum = nSum2;
}

return pRes;
}

///////////////////////////////////////////////////////////

#define F_DELTA		0.0001f

void ShrinkData(BYTE *pInBuff,
WORD wWidth,
WORD wHeight,
BYTE *pOutBuff,
WORD wNewWidth,
WORD wNewHeight)
{
BYTE  *pLine = pInBuff, *pPix;
BYTE  *pOutLine = pOutBuff;
DWORD dwInLn = (3 * wWidth + 3) & ~3;
DWORD dwOutLn = (3 * wNewWidth + 3) & ~3;
int   x, y, i, ii;
BOOL  bCrossRow, bCrossCol;
float *pRowCoeff = CreateCoeff(wWidth, wNewWidth, TRUE);
float *pColCoeff = CreateCoeff(wHeight, wNewHeight, TRUE);
float fTmp, *pXCoeff, *pYCoeff = pColCoeff;
DWORD dwBuffLn = 3 * wNewWidth * sizeof(float);
float *fBuff = new float[6 * wNewWidth];
float *fCurrLn = fBuff,
*fCurrPix,
*fNextLn = fBuff + 3 * wNewWidth,
*fNextPix;

::ZeroMemory(fBuff, 2 * dwBuffLn);

y = 0;
while(y < wNewHeight)
{
pPix = pLine;
pLine += dwInLn;

fCurrPix = fCurrLn;
fNextPix = fNextLn;

x = 0;
pXCoeff = pRowCoeff;
bCrossRow = pYCoeff[1] > F_DELTA;
while(x < wNewWidth)
{
fTmp = *pXCoeff * *pYCoeff;
for(i = 0; i < 3; i++)
fCurrPix[i] += fTmp * pPix[i];
bCrossCol = pXCoeff[1] > F_DELTA;
if(bCrossCol)
{
fTmp = pXCoeff[1] * *pYCoeff;
for(i = 0, ii = 3; i < 3; i++, ii++)
fCurrPix[ii] += fTmp * pPix[i];
}

if(bCrossRow)
{
fTmp = *pXCoeff * pYCoeff[1];
for(i = 0; i < 3; i++)
fNextPix[i] += fTmp * pPix[i];
if(bCrossCol)
{
fTmp = pXCoeff[1] * pYCoeff[1];
for(i = 0, ii = 3; i < 3; i++, ii++)
fNextPix[ii] += fTmp * pPix[i];
}
}

if(fabs(pXCoeff[1]) > F_DELTA)
{
x++;
fCurrPix += 3;
fNextPix += 3;
}

pXCoeff += 2;
pPix += 3;
}

if(fabs(pYCoeff[1]) > F_DELTA)
{
// set result line
fCurrPix = fCurrLn;
pPix = pOutLine;
for(i = 3 * wNewWidth; i > 0; i--, fCurrPix++, pPix++)
*pPix = (BYTE)*fCurrPix;

// prepare line buffers
fCurrPix = fNextLn;
fNextLn = fCurrLn;
fCurrLn = fCurrPix;
::ZeroMemory(fNextLn, dwBuffLn);

y++;
pOutLine += dwOutLn;
}
pYCoeff += 2;
}

delete [] pRowCoeff;
delete [] pColCoeff;
delete [] fBuff;
}

///////////////////////////////////////////////////////////

void EnlargeData(BYTE *pInBuff,
WORD wWidth,
WORD wHeight,
BYTE *pOutBuff,
WORD wNewWidth,
WORD wNewHeight)
{
BYTE  *pLine = pInBuff,
*pPix = pLine,
*pPixOld,
*pUpPix,
*pUpPixOld;
BYTE  *pOutLine = pOutBuff, *pOutPix;
DWORD dwInLn = (3 * wWidth + 3) & ~3;
DWORD dwOutLn = (3 * wNewWidth + 3) & ~3;
int   x, y, i;
BOOL  bCrossRow, bCrossCol;
float *pRowCoeff = CreateCoeff(wNewWidth, wWidth, FALSE);
float *pColCoeff = CreateCoeff(wNewHeight, wHeight, FALSE);
float fTmp, fPtTmp[3], *pXCoeff, *pYCoeff = pColCoeff;

y = 0;
while(y < wHeight)
{
bCrossRow = pYCoeff[1] > F_DELTA;
x = 0;
pXCoeff = pRowCoeff;
pOutPix = pOutLine;
pOutLine += dwOutLn;
pUpPix = pLine;
if(fabs(pYCoeff[1]) > F_DELTA)
{
y++;
pLine += dwInLn;
pPix = pLine;
}

while(x < wWidth)
{
bCrossCol = pXCoeff[1] > F_DELTA;
pUpPixOld = pUpPix;
pPixOld = pPix;
if(fabs(pXCoeff[1]) > F_DELTA)
{
x++;
pUpPix += 3;
pPix += 3;
}
fTmp = *pXCoeff * *pYCoeff;
for(i = 0; i < 3; i++)
fPtTmp[i] = fTmp * pUpPixOld[i];
if(bCrossCol)
{
fTmp = pXCoeff[1] * *pYCoeff;
for(i = 0; i < 3; i++)
fPtTmp[i] += fTmp * pUpPix[i];
}
if(bCrossRow)
{
fTmp = *pXCoeff * pYCoeff[1];
for(i = 0; i < 3; i++)
fPtTmp[i] += fTmp * pPixOld[i];
if(bCrossCol)
{
fTmp = pXCoeff[1] * pYCoeff[1];
for(i = 0; i < 3; i++)
fPtTmp[i] += fTmp * pPix[i];
}
}
for(i = 0; i < 3; i++, pOutPix++)
*pOutPix = (BYTE)fPtTmp[i];
pXCoeff += 2;
}
pYCoeff += 2;
}

delete [] pRowCoeff;
delete [] pColCoeff;
}

// end src
```

### Variant B

```//
// Functions for smooth bitmap resize
//
// Improvement: float calculations changed to int.
//
// Ivaylo Byalkov, January 24, 2000
// e-mail: ivob@i-n.net
//

#include "stdafx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////

// helper function prototypes
static BITMAPINFO *PrepareRGBBitmapInfo(WORD wWidth,
WORD wHeight);

static void ShrinkDataInt(BYTE *pInBuff,
WORD wWidth,
WORD wHeight,
BYTE *pOutBuff,
WORD wNewWidth,
WORD wNewHeight);

static void EnlargeDataInt(BYTE *pInBuff,
WORD wWidth,
WORD wHeight,
BYTE *pOutBuff,
WORD wNewWidth,
WORD wNewHeight);

///////////////////////////////////////////////////////////
// Main resize function

HBITMAP ScaleBitmapInt(HBITMAP hBmp,
WORD wNewWidth,
WORD wNewHeight)
{
BITMAP bmp;
::GetObject(hBmp, sizeof(BITMAP), &bmp);

// check for valid size
if((bmp.bmWidth > wNewWidth
&& bmp.bmHeight < wNewHeight)
|| bmp.bmWidth < wNewWidth
&& bmp.bmHeight > wNewHeight))
return NULL;

HDC hDC = ::GetDC(NULL);
BITMAPINFO *pbi = PrepareRGBBitmapInfo((WORD)bmp.bmWidth,
(WORD)bmp.bmHeight);

::GetDIBits(hDC, hBmp, 0, bmp.bmHeight, pData, pbi, DIB_RGB_COLORS);

delete pbi;
pbi = PrepareRGBBitmapInfo(wNewWidth, wNewHeight);

if(bmp.bmWidth >= wNewWidth
&& bmp.bmHeight >= wNewHeight)
ShrinkDataInt(pData,
(WORD)bmp.bmWidth,
(WORD)bmp.bmHeight,
pData2,
wNewWidth,
wNewHeight);
else
EnlargeDataInt(pData,
(WORD)bmp.bmWidth,
(WORD)bmp.bmHeight,
pData2,
wNewWidth,
wNewHeight);

delete pData;

HBITMAP hResBmp = ::CreateCompatibleBitmap(hDC,
wNewWidth,
wNewHeight);

::SetDIBits(hDC,
hResBmp,
0,
wNewHeight,
pData2,
pbi,
DIB_RGB_COLORS);

::ReleaseDC(NULL, hDC);

delete pbi;
delete pData2;

return hResBmp;
}

///////////////////////////////////////////////////////////

BITMAPINFO *PrepareRGBBitmapInfo(WORD wWidth, WORD wHeight)
{
BITMAPINFO *pRes = new BITMAPINFO;
::ZeroMemory(pRes, sizeof(BITMAPINFO));

((3 * wWidth + 3) & ~3) * wHeight;

return pRes;
}

///////////////////////////////////////////////////////////

static int *CreateCoeffInt(int nLen, int nNewLen, BOOL bShrink)
{
int nSum = 0, nSum2;
int *pRes = new int[2 * nLen];
int *pCoeff = pRes;
int nNorm = (bShrink)
? (nNewLen << 12) / nLen : 0x1000;
int	nDenom = (bShrink)? nLen : nNewLen;

::ZeroMemory(pRes, 2 * nLen * sizeof(int));
for(int i = 0; i < nLen; i++, pCoeff += 2)
{
nSum2 = nSum + nNewLen;
if(nSum2 > nLen)
{
*pCoeff = ((nLen - nSum) << 12) / nDenom;
pCoeff[1] = ((nSum2 - nLen) << 12) / nDenom;
nSum2 -= nLen;
}
else
{
*pCoeff = nNorm;
if(nSum2 == nLen)
{
pCoeff[1] = -1;
nSum2 = 0;
}
}
nSum = nSum2;
}

return pRes;
}

///////////////////////////////////////////////////////////

void ShrinkDataInt(BYTE *pInBuff,
WORD wWidth,
WORD wHeight,
BYTE *pOutBuff,
WORD wNewWidth,
WORD wNewHeight)
{
BYTE  *pLine = pInBuff, *pPix;
BYTE  *pOutLine = pOutBuff;
DWORD dwInLn = (3 * wWidth + 3) & ~3;
DWORD dwOutLn = (3 * wNewWidth + 3) & ~3;
int   x, y, i, ii;
BOOL  bCrossRow, bCrossCol;
int   *pRowCoeff = CreateCoeffInt(wWidth,
wNewWidth,
TRUE);
int   *pColCoeff = CreateCoeffInt(wHeight,
wNewHeight,
TRUE);
int   *pXCoeff, *pYCoeff = pColCoeff;
DWORD dwBuffLn = 3 * wNewWidth * sizeof(DWORD);
DWORD *pdwBuff = new DWORD[6 * wNewWidth];
DWORD *pdwCurrLn = pdwBuff,
*pdwCurrPix,
*pdwNextLn = pdwBuff + 3 * wNewWidth;
DWORD dwTmp, *pdwNextPix;

::ZeroMemory(pdwBuff, 2 * dwBuffLn);

y = 0;
while(y < wNewHeight)
{
pPix = pLine;
pLine += dwInLn;

pdwCurrPix = pdwCurrLn;
pdwNextPix = pdwNextLn;

x = 0;
pXCoeff = pRowCoeff;
bCrossRow = pYCoeff[1] > 0;
while(x < wNewWidth)
{
dwTmp = *pXCoeff * *pYCoeff;
for(i = 0; i < 3; i++)
pdwCurrPix[i] += dwTmp * pPix[i];
bCrossCol = pXCoeff[1] > 0;
if(bCrossCol)
{
dwTmp = pXCoeff[1] * *pYCoeff;
for(i = 0, ii = 3; i < 3; i++, ii++)
pdwCurrPix[ii] += dwTmp * pPix[i];
}
if(bCrossRow)
{
dwTmp = *pXCoeff * pYCoeff[1];
for(i = 0; i < 3; i++)
pdwNextPix[i] += dwTmp * pPix[i];
if(bCrossCol)
{
dwTmp = pXCoeff[1] * pYCoeff[1];
for(i = 0, ii = 3; i < 3; i++, ii++)
pdwNextPix[ii] += dwTmp * pPix[i];
}
}
if(pXCoeff[1])
{
x++;
pdwCurrPix += 3;
pdwNextPix += 3;
}
pXCoeff += 2;
pPix += 3;
}
if(pYCoeff[1])
{
// set result line
pdwCurrPix = pdwCurrLn;
pPix = pOutLine;
for(i = 3 * wNewWidth; i > 0; i--, pdwCurrPix++, pPix++)
*pPix = ((LPBYTE)pdwCurrPix)[3];

// prepare line buffers
pdwCurrPix = pdwNextLn;
pdwNextLn = pdwCurrLn;
pdwCurrLn = pdwCurrPix;
::ZeroMemory(pdwNextLn, dwBuffLn);

y++;
pOutLine += dwOutLn;
}
pYCoeff += 2;
}

delete [] pRowCoeff;
delete [] pColCoeff;
delete [] pdwBuff;
}

///////////////////////////////////////////////////////////

void EnlargeDataInt(BYTE *pInBuff,
WORD wWidth,
WORD wHeight,
BYTE *pOutBuff,
WORD wNewWidth,
WORD wNewHeight)
{
BYTE  *pLine = pInBuff,
*pPix = pLine,
*pPixOld,
*pUpPix,
*pUpPixOld;
BYTE  *pOutLine = pOutBuff, *pOutPix;
DWORD dwInLn = (3 * wWidth + 3) & ~3;
DWORD dwOutLn = (3 * wNewWidth + 3) & ~3;
int   x, y, i;
BOOL  bCrossRow, bCrossCol;
int   *pRowCoeff = CreateCoeffInt(wNewWidth,
wWidth,
FALSE);
int   *pColCoeff = CreateCoeffInt(wNewHeight,
wHeight,
FALSE);
int   *pXCoeff, *pYCoeff = pColCoeff;
DWORD dwTmp, dwPtTmp[3];

y = 0;
while(y < wHeight)
{
bCrossRow = pYCoeff[1] > 0;
x = 0;
pXCoeff = pRowCoeff;
pOutPix = pOutLine;
pOutLine += dwOutLn;
pUpPix = pLine;
if(pYCoeff[1])
{
y++;
pLine += dwInLn;
pPix = pLine;
}

while(x < wWidth)
{
bCrossCol = pXCoeff[1] > 0;
pUpPixOld = pUpPix;
pPixOld = pPix;
if(pXCoeff[1])
{
x++;
pUpPix += 3;
pPix += 3;
}

dwTmp = *pXCoeff * *pYCoeff;

for(i = 0; i < 3; i++)
dwPtTmp[i] = dwTmp * pUpPixOld[i];

if(bCrossCol)
{
dwTmp = pXCoeff[1] * *pYCoeff;
for(i = 0; i < 3; i++)
dwPtTmp[i] += dwTmp * pUpPix[i];
}

if(bCrossRow)
{
dwTmp = *pXCoeff * pYCoeff[1];
for(i = 0; i < 3; i++)
dwPtTmp[i] += dwTmp * pPixOld[i];
if(bCrossCol)
{
dwTmp = pXCoeff[1] * pYCoeff[1];
for(i = 0; i < 3; i++)
dwPtTmp[i] += dwTmp * pPix[i];
}
}

for(i = 0; i < 3; i++, pOutPix++)
*pOutPix = ((LPBYTE)(dwPtTmp + i))[3];

pXCoeff += 2;
}
pYCoeff += 2;
}

delete [] pRowCoeff;
delete [] pColCoeff;
}

// end src
```

• #### Is it bug?

Posted by Toshihiko Takasaki on 04/17/2013 10:16am

I'm a Japanese beginner.I think that this program is very excellent, but in the case of huge size picture(over 1MB) load, applications should end in failure. So I tried to increase the buffer size of above "55. BYTE *pData = new BYTE[pbi-bmiHeader.biSizeImage];", then picture load is success, but in the picture "one dirty line"(about one pixel width) is found. If there are some easy resolving methods, please teach me the code.

• You must have javascript enabled in order to post comments.

## Top White Papers and Webcasts

• As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
• The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
• Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...