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

  • Moving from an on-premises environment to Office 365 does not remove the need to plan for disruptions or reduce the business risk requirements for protecting email services. If anything, some risks increase with a move to the cloud. Read how to ease the transition every business faces if considering or already migrating to cloud email. This white paper discusses: Setting expectations when migrating to Office 365 Understanding the implications of relying solely on Exchange Online security Necessary archiving …

  • Enterprises are increasingly looking to platform as a service (PaaS) to lower their costs and speed their time to market for new applications. Developing, deploying, and managing applications in the cloud eliminates the time and expense of managing a physical infrastructure to support them. PaaS offerings must deliver additional long-term benefits, such as a lower total cost of ownership (TCO), rapid scalability, and ease of integration, all while providing robust security and availability. This report …

Most Popular Programming Stories

More for Developers

RSS Feeds

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