Smart resize for monochrome bitmaps

If we make a monochrome bitmap smaller by a simple pixel resampling the resulting image looks lousy (pic.2). The problem here is that we take a stand-alone pixel and disregard its surrounding. We can improve the picture a little bit by calculating the value of a pixel as the average of the pixels we disregard and then rounding this value to the nearest color. However the best result is obtained in a grayscale destination bitmap (pic. 3). This is implemented in the following function.
Pic.1 Original image Pic 2. Simple resize   Pic 3. Smart resize

#define WIDTHBYTES(bits)    (((bits) + 31) / 32 * 4) // for padding

// Function name    : ZoomOutBmp
// Description      : creates a new bitmap which is a grayscale 
//                    zoomed out version of the original
// Return type      : HDIB - handle to a new bitmap 
// Argument         : double zoom - number of times to zoom out
// Argument         : HDIB hSrcDIB - handle to a source bitmap
HDIB ZoomOutBmp(double zoom, HDIB hSrcDIB)
    if (hSrcDIB == NULL) // nothing to do
        return NULL;
    if (zoom < 1) // no zoomin in this function
        return NULL;
    LPSTR pSrcDIB = (LPSTR) ::GlobalLock((HGLOBAL) hSrcDIB);
    ASSERT(bmihSrc.biBitCount == 1); // only monochrome bitmaps supported
    LPSTR pSrcBits = (LPSTR) (pSrcDIB + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*2);
    BITMAPINFOHEADER bmihDst = bmihSrc;
    bmihDst.biWidth = (LONG)(bmihDst.biWidth / zoom + 0.5);
    bmihDst.biHeight = (LONG)(bmihDst.biHeight / zoom + 0.5);
    bmihDst.biBitCount = 8; // grayscale in any case
    bmihDst.biClrUsed = 0;
    // prepare destination bitmap
    DWORD dwDIBSize = sizeof(bmihDst) + sizeof(RGBQUAD)*256 +
        WIDTHBYTES(bmihDst.biWidth * bmihDst.biBitCount) * bmihDst.biHeight;
    bmihDst.biSizeImage = dwDIBSize;
    // allocate space for the new bitmap
    if (hDstDIB == 0) {
        ::GlobalUnlock((HGLOBAL) hSrcDIB);
        return NULL;
    LPSTR pDstDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDstDIB);
    // copy header
    memcpy(pDstDIB, &bmihDst, sizeof(bmihDst));
    // prepare grayscale palette
    for (int i=0; i < (1 << bmihDst.biBitCount); i++) {
        RGBQUAD& palEntry = *(RGBQUAD*)(pDstDIB + sizeof(bmihDst) + i * sizeof(RGBQUAD));
        palEntry.rgbRed = palEntry.rgbGreen = palEntry.rgbBlue = i;
    LPSTR pDstBits = (LPSTR) (pDstDIB + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256);
    // now fill the bits
    LPSTR curSrcLineBits, curDstLineBits;
    int j, k;
    int scale = (int)(zoom + 0.5); // integer zoom out factor, i.e. 1:5
    int hBase, vBase;
    unsigned char value;
    // for every _scale_ lines in a source bitmap we will get one line
    // in the destination bitmap. Similarly for _scale_ columns in the
    // source we'll obtain one destination column.
    for (int strip=0; strip < bmihDst.biHeight; strip++) { // for every dst line
        curDstLineBits = pDstBits + strip * WIDTHBYTES(bmihDst.biWidth * bmihDst.biBitCount);
        vBase = int(strip * zoom + 0.5);
        for (i=0; i < scale; i++) {  // accumulate _scale_ rows
            curSrcLineBits = pSrcBits + (vBase + i) * WIDTHBYTES(bmihSrc.biWidth * bmihSrc.biBitCount);
            // prepare horizontally condensed lines for this strip
            for (j=0; j < bmihDst.biWidth; j++) { // for all bits in line
                hBase = int(j * zoom + 0.5); // mapped index on source
                for (k=0; k < scale; k++) { // accumulate _scale_ columns
                    value = (curSrcLineBits[(hBase+k)/8] & (1 << (7 - (hBase+k)%8))) ? 0xff : 0;
                    curDstLineBits[j] += value / scale / scale; // main accumulator
    // unlock memory
    ::GlobalUnlock((HGLOBAL) hSrcDIB);
    ::GlobalUnlock((HGLOBAL) hDstDIB);
    return hDstDIB;

This function may be improved in several ways, for example, working with a weighted fraction of a pixel or centering the accumulated source pixels. However, the resulting visual impovement is not significant.

Download demo project - 38 KB


Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds