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

  • Live Event Date: May 11, 2015 @ 1:00 p.m. ET / 10:00 a.m. PT One of the languages that have always been supported with the Intel® RealSense™ SDK (Software Developer Kit) is JavaScript, specifically so that web-enabled apps could be created. Come hear from Intel Expert Bob Duffy as he reviews his own little "space shooting" game where the orientation of your face controls the aiming reticle to help teach developers how to write apps and games in JavaScript that can use facial and gesture …

  • Several technologies are driving big changes at federal agencies, but at the forefront is cloud computing. Cloud -- the use of remote, Internet-hosted servers for storing, managing and processing data in place of on-site technology -- is helping along technologies such as those related to collaboration and enabling new ones, such as the analysis of Big Data. Cloud is complicated but also growing in importance at federal agencies. Read this technology article to learn how governmental entities are creating a …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date