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);
    
    BITMAPINFOHEADER& bmihSrc = *(BITMAPINFOHEADER*)pSrcDIB;
    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
    HDIB hDstDIB = (HDIB) ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwDIBSize);
    
    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



Comments

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

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Is your sales and operations planning helping or hurting your bottom line? Here are 5 useful tips from the experts at Quintiq to guide you to a better S&OP strategy.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds