Click here to Skip to main content
15,867,956 members
Articles / Desktop Programming / MFC
Article

Comparing Two Bitmaps for Equality

Rate me:
Please Sign up or sign in to vote.
3.60/5 (13 votes)
19 Dec 2006CPOL 100.2K   37   18
Code snippet that compares two HBITMAP handles to see if the bitmaps they contain are the same

Introduction

Presented here is a simple method to compare two HBITMAP handles to see if they contain the same bitmap. The code itself is nothing fancy, so I will just include it in the article text. No separate download or demo application is needed.

How to Compare Two Bitmaps for Equality

The first thing to do is compare the HBITMAP handles themselves. If they are the same handle, then the two bitmaps are obviously equal. If one of the handles is NULL, then they are obviously not equal. If the HBITMAP handles are different and not NULL, then use GetDIBits to fill two BITMAPINFO structures -- one for each bitmap -- and compare the BITMAPINFOHEADERs. If the BITMAPINFOHEADERs contain different data, then the bitmaps are different. If they contain the same data, then the next step is to compare the actual bitmap data bits.

The Code

C++
/////////////////////////////////////////////////////////////////////////////
//
//  CompareBitmaps
//    Compares two HBITMAPs to see if they contain the same image
//
//  Parameters :
//    HBitmapLeft  [in] : The HBITMAP handles that are to be compared
//    HBitmapRight [in] :
//
//  Returns :
//    true if the bitmaps are the same
//    false if they are different
//
/////////////////////////////////////////////////////////////////////////////


bool CompareBitmaps(HBITMAP HBitmapLeft, HBITMAP HBitmapRight)
{
    if (HBitmapLeft == HBitmapRight)
    {
        return true;
    }

    if (NULL == HBitmapLeft || NULL == HBitmapRight)
    {
        return false;
    }

    bool bSame = false;

    HDC hdc = GetDC(NULL);
    BITMAPINFO BitmapInfoLeft = {0};
    BITMAPINFO BitmapInfoRight = {0};

    BitmapInfoLeft.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    BitmapInfoRight.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

    if (0 != GetDIBits(hdc, HBitmapLeft, 0, 0, NULL, &BitmapInfoLeft, DIB_RGB_COLORS) &&
        0 != GetDIBits(hdc, HBitmapRight, 0, 0, NULL, &BitmapInfoRight, DIB_RGB_COLORS))
    {
        // Compare the BITMAPINFOHEADERs of the two bitmaps

        if (0 == memcmp(&BitmapInfoLeft.bmiHeader, &BitmapInfoRight.bmiHeader, 
            sizeof(BITMAPINFOHEADER)))
        {
            // The BITMAPINFOHEADERs are the same so now compare the actual bitmap bits

            BYTE *pLeftBits = new BYTE<BitmapInfoLeft.bmiHeader.biSizeImage>;
            BYTE *pRightBits = new BYTE<BitmapInfoRight.bmiHeader.biSizeImage>;
            BYTE *pByteLeft = NULL;
            BYTE *pByteRight = NULL;

            PBITMAPINFO pBitmapInfoLeft = &BitmapInfoLeft;
            PBITMAPINFO pBitmapInfoRight = &BitmapInfoRight;

            // calculate the size in BYTEs of the additional

            // memory needed for the bmiColor table

            int AdditionalMemory = 0;
            switch (BitmapInfoLeft.bmiHeader.biBitCount)
            {
            case 1:
                AdditionalMemory = 1 * sizeof(RGBQUAD);
                break;
            case 4:
                AdditionalMemory = 15 * sizeof(RGBQUAD);
                break;
            case 8:
                AdditionalMemory = 255 * sizeof(RGBQUAD);
                break;
            case 16:
            case 32:
                AdditionalMemory = 2 * sizeof(RGBQUAD);
            }

            if (AdditionalMemory)
            {
                // we have to allocate room for the bmiColor table that will be

                // attached to our BITMAPINFO variables

                pByteLeft = new BYTE[sizeof(BITMAPINFO) + AdditionalMemory];
                if (pByteLeft)
                {
                    memset(pByteLeft, 0, sizeof(BITMAPINFO) + AdditionalMemory);
                    memcpy(pByteLeft, pBitmapInfoLeft, sizeof(BITMAPINFO));
                    pBitmapInfoLeft = (PBITMAPINFO)pByteLeft;
                }

                pByteRight = new BYTE[sizeof(BITMAPINFO) + AdditionalMemory];
                if (pByteRight)
                {
                    memset(pByteRight, 0, sizeof(BITMAPINFO) + AdditionalMemory);
                    memcpy(pByteRight, pBitmapInfoRight, sizeof(BITMAPINFO));
                    pBitmapInfoRight = (PBITMAPINFO)pByteRight;
                }
            }

            if (pLeftBits && pRightBits && pBitmapInfoLeft && pBitmapInfoRight)
            {
                // zero out the bitmap bit buffers

                memset(pLeftBits, 0, BitmapInfoLeft.bmiHeader.biSizeImage);
                memset(pRightBits, 0, BitmapInfoRight.bmiHeader.biSizeImage);

                // fill the bit buffers with the actual bitmap bits

                if (0 != GetDIBits(hdc, HBitmapLeft, 0, 
                    pBitmapInfoLeft->bmiHeader.biHeight, pLeftBits, pBitmapInfoLeft, 
                    DIB_RGB_COLORS) && 0 != GetDIBits(hdc, HBitmapRight, 0, 
                    pBitmapInfoRight->bmiHeader.biHeight, pRightBits, pBitmapInfoRight, 
                    DIB_RGB_COLORS))
                {
                    // compare the actual bitmap bits of the two bitmaps

                    bSame = 0 == memcmp(pLeftBits, pRightBits, 
                        pBitmapInfoLeft->bmiHeader.biSizeImage);
                }
            }

            // clean up

            delete[] pLeftBits;
            delete[] pRightBits;
            delete[] pByteLeft;
            delete[] pByteRight;
        }
    }

    ReleaseDC(NULL, hdc);

    return bSame;
}

History

  • 19 December, 2006 -- Original version posted
  • 12 March, 2008 -- Article edited and moved to The Code Project main article base

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President
Canada Canada
Father of two, brother of two, child of two.
Spouse to one, uncle to many, friend to lots.
Farmer, carpenter, mechanic, electrician, but definitely not a plumber.
Likes walks with the wife, board games, card games, travel, and camping in the summer.
High school graduate, college drop-out.
Hobby programmer who knows C++ with MFC and the STL.
Has dabbled with BASIC, Pascal, Fortran, COBOL, C#, SQL, ASM, and HTML.
Realized long ago that programming is fun when there is nobody pressuring you with schedules and timelines.

Comments and Discussions

 
QuestionCan we use this for cursor? Pin
bala boy26-Aug-14 0:11
bala boy26-Aug-14 0:11 
GeneralSmall Addition - detect minor changes Pin
Alexander Silonosov10-Jun-10 0:16
Alexander Silonosov10-Jun-10 0:16 
Newsported to C [modified] Pin
Mikke828-Jan-10 13:49
Mikke828-Jan-10 13:49 
Questionam I doing somthing wrong? It crashes. Pin
Member 137233911-Mar-08 4:09
Member 137233911-Mar-08 4:09 
GeneralRe: am I doing somthing wrong? It crashes. Pin
PJ Arends12-Mar-08 4:43
professionalPJ Arends12-Mar-08 4:43 
GeneralExtending this Idea Pin
ceejeeb27-Jan-07 0:36
ceejeeb27-Jan-07 0:36 
GeneralRe: Extending this Idea Pin
PJ Arends27-Jan-07 9:52
professionalPJ Arends27-Jan-07 9:52 
GeneralRe: Extending this Idea Pin
ceejeeb28-Jan-07 2:43
ceejeeb28-Jan-07 2:43 
GeneralRe: Extending this Idea Pin
PJ Arends28-Jan-07 9:17
professionalPJ Arends28-Jan-07 9:17 
GeneralRe: Extending this Idea Pin
ceejeeb5-Feb-07 0:35
ceejeeb5-Feb-07 0:35 
GeneralRe: Extending this Idea Pin
PJ Arends6-Feb-07 9:58
professionalPJ Arends6-Feb-07 9:58 
GeneralRe: Extending this Idea Pin
ceejeeb7-Feb-07 3:55
ceejeeb7-Feb-07 3:55 
GeneralRe: Extending this Idea Pin
PJ Arends7-Feb-07 7:43
professionalPJ Arends7-Feb-07 7:43 
GeneralRe: Extending this Idea Pin
Rafal Struzyk11-Jul-07 7:09
Rafal Struzyk11-Jul-07 7:09 
GeneralRe: Extending this Idea Pin
PJ Arends23-Mar-07 18:56
professionalPJ Arends23-Mar-07 18:56 
Ok, it has taken me a while. But I liked your idea so much I wanted to implement it in my Image Viewer[^] application. Here is what I came with:

note - pja::CBitmap and pja::CCompatibleDC are simple wrappers around the HBITMAP and HDC types

pja::CBitmap Compare(const pja::CBitmap &Bitmap1, const pja::CBitmap &Bitmap2, int x = 0, int y = 0, COLORREF ClrSame = 0x00FFFFFF, COLORREF ClrDiff = 0x00000000);

pja::CBitmap Compare(const pja::CBitmap &Bitmap1,   // The first bitmap
                     const pja::CBitmap &Bitmap2,   // The second bitmap
                     int x, int y,                  // The offset of the second bitmap from the first bitmap
                     COLORREF ClrSame,              // Colour used for pixels that are the same
                     COLORREF ClrDiff)              // Colour used for pixels that are different
{
    // Sanity check
    ASSERT (x > -Bitmap2.Width() && x < Bitmap1.Width());
    ASSERT (y > -Bitmap2.Height() && y < Bitmap1.Height());
 
    if (x <= -Bitmap2.Width() || x >= Bitmap1.Width() || y <= -Bitmap2.Height() || y >= Bitmap1.Height())
    {
        return pja::CBitmap();
    }

    // Setup the rectangles for where stuff gets drawn
    CRect Bitmap1Rect(0, 0, Bitmap1.Width(), Bitmap1.Height());
    CRect Bitmap2Rect(0, 0, Bitmap2.Width(), Bitmap2.Height());

    if (x < 0)
        Bitmap1Rect.OffsetRect(-x, 0);
    else
        Bitmap2Rect.OffsetRect(x, 0);

    if (y < 0)
        Bitmap1Rect.OffsetRect(0, -y);
    else
        Bitmap2Rect.OffsetRect(0, y);

    CRect FinalRect = Bitmap1Rect | Bitmap2Rect;
    CRect CombinedRect = Bitmap1Rect & Bitmap2Rect;

    // Create the bitmaps needed
    pja::CBitmap FinalBitmap(NULL, FinalRect.Width(), FinalRect.Height());
    pja::CBitmap CombinedBitmap(NULL, CombinedRect.Width(), CombinedRect.Height());
    pja::CBitmap MonoBitmap(CreateBitmap(CombinedRect.Width(), CombinedRect.Height(), 1, 1, NULL));

    // DCs for all the bitmaps
    pja::CCompatibleDC Bitmap1DC;
    pja::CCompatibleDC Bitmap2DC;
    pja::CCompatibleDC FinalBitmapDC;
    pja::CCompatibleDC CombinedBitmapDC;
    pja::CCompatibleDC MonoBitmapDC;

    // Select the bitmaps into the DCs
    SelectObject(Bitmap1DC, Bitmap1);
    SelectObject(Bitmap2DC, Bitmap2);
    SelectObject(FinalBitmapDC, FinalBitmap);
    SelectObject(CombinedBitmapDC, CombinedBitmap);
    SelectObject(MonoBitmapDC, MonoBitmap);

    // Combine the two supplied bitmaps,
    // same pixels will be white - all others will be whatever
    BitBlt(CombinedBitmapDC,
           0, 0, CombinedRect.Width(), CombinedRect.Height(),
           Bitmap1DC,
           CombinedRect.left > Bitmap1Rect.left ? CombinedRect.left : 0,
           CombinedRect.top > Bitmap1Rect.top ? CombinedRect.top : 0,
           SRCCOPY);

    BitBlt(CombinedBitmapDC, 0, 0, CombinedRect.Width(), CombinedRect.Height(),
           Bitmap2DC,
           CombinedRect.left > Bitmap2Rect.left ? CombinedRect.left : 0,
           CombinedRect.top > Bitmap2Rect.top ? CombinedRect.top : 0,
           0x00990066); //DSxn ROP

    // On 32 bit bitmaps we have to reset the alpha channel to zero
    if (CombinedBitmap.BitsPixel() >= 32)
    {
        SelectObject(CombinedBitmapDC, GetStockObject(WHITE_BRUSH));
        PatBlt(CombinedBitmapDC, 0, 0, CombinedBitmap.Width(), CombinedBitmap.Height(), 0x00A000C9); // DPa ROP
    }

    // transfer the combined bitmap to a black and white monochrome bitmap
    // white stays white, the rest turns black
    SetBkColor(CombinedBitmapDC, 0x00FFFFFF);
    BitBlt(MonoBitmapDC, 0, 0, CombinedRect.Width(), CombinedRect.Height(), CombinedBitmapDC, 0, 0, SRCCOPY);

    // Fill the background of the final bitmap with our background colour
    // TODO: Change this line when this function is imported into the Image Viewer
    //FillRect(FinalBitmapDC, FinalRect, CBrush(reinterpret_cast<COLORREF>(AfxGetMainWnd()->SendMessage(WMU_GETBGCOLOUR, 0, 0);
    FillRect(FinalBitmapDC, FinalRect, CBrush(RGB(236, 233, 216)));

    // Draw our two bitmaps
    if (FinalRect != CombinedRect)
    {
        BitBlt(FinalBitmapDC, Bitmap1Rect.left, Bitmap1Rect.top, Bitmap1Rect.Width(), Bitmap1Rect.Height(), Bitmap1DC, 0, 0, SRCCOPY);
        BitBlt(FinalBitmapDC, Bitmap2Rect.left, Bitmap2Rect.top, Bitmap2Rect.Width(), Bitmap2Rect.Height(), Bitmap2DC, 0, 0, SRCCOPY);
    }

    // Draw the differences using the supplied colours
    SetBkColor(FinalBitmapDC, ClrSame);
    SetTextColor(FinalBitmapDC, ClrDiff);
    BitBlt(FinalBitmapDC, CombinedRect.left, CombinedRect.top, CombinedRect.Width(), CombinedRect.Height(), MonoBitmapDC, 0, 0, SRCCOPY);

    return FinalBitmap;
}



You may be right
I may be crazy
-- Billy Joel --


Within you lies the power for good, use it!!!

GeneralGreat but far too simple Pin
IntVestor21-Dec-06 6:53
IntVestor21-Dec-06 6:53 
GeneralRe: Great but far too simple Pin
PJ Arends21-Dec-06 7:04
professionalPJ Arends21-Dec-06 7:04 
GeneralRe: Great but far too simple Pin
Lampros Giampouras10-Jan-07 5:41
Lampros Giampouras10-Jan-07 5:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.