比较两个位图(设备上下文 - 文件)

时间:2012-03-26 01:02:51

标签: c++ winapi visual-c++ graphics bitmap

我需要比较两个位图。从文件加载一个位图,第二个是来自设备上下文的位图。文件位图由同一程序生成,用于测试目的。

我在vc10 / win7上编程

我故意不处理错误以清除此帖子上的代码。

第一步,我制作一个rgb24位图文件并将其保存为“test.bmp”:

void GetBitmap24FromDcToFile(HDC winDC, int x, int y, int w, int h)
{
int imgsize;

if((3 * w) % 4 > 0)
    imgsize = ((3 * w) / 4 + 1) * 4 * h;
else if((3 * w) % 4 == 0)
    imgsize = 3 * w * h;

BITMAPINFO bi;
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = w;
bi.bmiHeader.biHeight = h;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 24;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biSizeImage = imgsize;
bi.bmiHeader.biXPelsPerMeter = 0;
bi.bmiHeader.biYPelsPerMeter = 0;
bi.bmiHeader.biClrUsed = 0;
bi.bmiHeader.biClrImportant = 0;

void *pvBits = NULL;
HBITMAP hbmp = ::CreateDIBSection(winDC, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0);
HDC hdc = ::CreateCompatibleDC(winDC);
HBITMAP holdbmp = (HBITMAP)::SelectObject(hdc, hbmp);
::BitBlt(hdc, 0, 0, w, h, winDC, x, y, SRCCOPY);

HANDLE hFile = ::CreateFile(_T("test.bmp"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

DWORD dwCnt;
BITMAPFILEHEADER bmfh;

ZeroMemory(&bmfh, sizeof(BITMAPFILEHEADER));
bmfh.bfType = 0x4d42;
bmfh.bfSize = imgsize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

WriteFile(hFile, (char*)&bmfh, sizeof(BITMAPFILEHEADER), &dwCnt, NULL);
WriteFile(hFile, (char*)&bi.bmiHeader, sizeof(BITMAPINFOHEADER), &dwCnt, NULL);
WriteFile(hFile, (char*)pvBits, imgsize, &dwCnt, NULL);
CloseHandle(hFile);

::SelectObject(hdc, holdbmp);
::DeleteDC(hdc);
::DeleteObject(hbmp);
}

第二步,我从设备上下文中创建一个位图:

HBITMAP GetBitmap24FromDC(HDC winDC, int x, int y, int w, int h)
{
HDC  hMemDC = ::CreateCompatibleDC( winDC );
HBITMAP hbmp; // = ::CreateCompatibleBitmap( winDC, w, h);

BITMAPINFOHEADER infoHeader; 
infoHeader.biSize          = sizeof(infoHeader); 
infoHeader.biWidth         = (LONG)w; 
infoHeader.biHeight        = (LONG)h; 
infoHeader.biPlanes        = 1; 
infoHeader.biBitCount      = 24; 
infoHeader.biCompression   = BI_RGB; 
infoHeader.biSizeImage     = 0; 
infoHeader.biXPelsPerMeter = 0; 
infoHeader.biYPelsPerMeter = 0; 
infoHeader.biClrUsed       = 0; 
infoHeader.biClrImportant  = 0; 

BITMAPINFO info; 
info.bmiHeader = infoHeader; 

unsigned char *mem;
hbmp = CreateDIBSection(winDC, &info, DIB_RGB_COLORS, (void**)&mem, 0, 0);
HBITMAP holdbmp = (HBITMAP) ::SelectObject(hMemDC, hbmp); 
::BitBlt(hMemDC, 0, 0, w, h, winDC, x, y, SRCCOPY);
    ::SelectObject(hMemDC, holdbmp);
::DeleteDC(hMemDC);

return hbmp;
}

我使用这种方法进行比较:

// Author: PJ Arends - codeproject
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 = (BYTE*)malloc(sizeof(BYTE) * BitmapInfoLeft.bmiHeader.biSizeImage);
        BYTE *pRightBits = (BYTE*)malloc(sizeof(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
        free(pLeftBits);
        free(pRightBits);
        free(pByteLeft);
        free(pByteRight);
    }
}

ReleaseDC(NULL, hdc);

return bSame;
}

所以,在我的主要代码中,我有类似的东西:

   (...)
   HWND capture = ::FindWindow(_T("the_window_class"), NULL);
   HDC winDC = ::GetDC(capture);
   GetBitmap24FromDcToFile(winDC, 0, 0, 200, 200); // generate bitmap file "test.bmp"
   HBITMAP bmpFile = (HBITMAP)LoadImage( NULL, _T("test.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION ); 
   HBITMAP bmpMem  = GetBitmap24FromDC(winDC, 0, 0, 200, 200); // get bitmap from DC

   bool isEqual = CompareBitmaps(bmpFile, bmpMem); // test both bitmaps
   if(isEqual)
      AfxMessageBox(_T("Success"));
   (...)

两个文件之间的比较返回true;来自dc的两个位图返回true; 位图文件和直流位图之间的比较总是返回false。

调试之后,它会通过我们检查BITMAPINFOHEADER的第一个测试条件(在Compare方法中)。它在最后一个memcmp()中失败,我们比较两个位图的位。

在调试器中,两个位图的结构相同,我在两个pBitmapInfoLeft \ pBitmapInfoRight-> bmiColors字段之间只有很小的差异。

检查两个位图标头中的位是否相同(pLeftBits \ pRightBits)。

一个想法,一个替代,一个例子?让我知道!谢谢!

  • JE

2 个答案:

答案 0 :(得分:2)

但是有一种错误。

您使用的BITMAPINFO结构实际上是假的,并非设计为原样使用。

实际位图标头由固定的BITMAPINFOHEADER结构和可变大小的 RGBQUAD结构数组组成,而此数组的大小取决于数据BITMAPINFOHEADER。根据位图位数,此数组应具有以下长度:

  • 1/4/8:数组大小应为2 ^位。即 2/16/256 。位图被视为索引,此数组中的值定义实际颜色。
  • 16:像素值使用所谓的位域转换为颜色。数组大小取决于biCompression成员:
    • BI_RGB:数组应为空。使用默认位域5-5-5。
    • BI_BITFIELDS:数组应包含 3 条目。为R / G / B通道定义适当的位掩码。
  • 32:像素值直接对应于颜色,或者如果biCompression设置为BI_BITFIELDS,则使用位域进行平移。与16位情况一样,数组应该是 3 条目。

BITMAPINFO结构由BITMAPINFO结构(bmiHeader)和bmiColors组成,其总是有一个条目。 从不这个案例。

这就是BITMAPINFO实际上是假结构的原因。为了创建位图头,首先应该为BITMAPINFOHEADER和所需的数组分配所需的内存量,然后它转换为BITMAPINFO

简单来说:比较BITMAPINFO结构(即使用sizeof(BITMAPINFO))没有意义。 bmiColors将包含未初始化的数据,或者无法访问,或者实际上会包含更大的数据。

P.S。顺便说一句,整个位图比较有点脏恕我直言。将位图保存到文件,只是为了比较 - 看起来很疯狂。另外,您实际上不需要为整个位图分配内存,可以逐行进行比较。

此外,如果其中一个位图是DIB,您可以直接获取指向其位的指针,因此不需要分配额外的内存和复制。

答案 1 :(得分:1)

我相信您可以使用SoIL Library(或实际上除WinApi以外的任何其他方式)来加载和操作位图文件。它是免费且轻量级的,并且会将您的代码缩短约90%。