Smart resize for monochrome bitmaps | CodeGuru

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 […]

Written By
CodeGuru Staff
CodeGuru Staff
Jan 24, 1999
1 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

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

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.