Сравнить два растровых изображения (контекст устройства - файл) - PullRequest
0 голосов
/ 26 марта 2012

Мне нужно сравнить два растровых изображения. Одна битовая карта загружается из файла, вторая - это битовая карта из контекста устройства. Растровое изображение файла генерируется той же программой для целей тестирования.

Я программирую на 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.

После отладки он проходит первое тестовое условие (в методе Compare), где мы проверяем BITMAPINFOHEADER. Сбой на последнем memcmp (), где мы сравниваем биты двух растровых изображений.

В отладчике структура одинакова для обоих растровых изображений, у меня есть только небольшая разница между двумя полями pBitmapInfoLeft \ pBitmapInfoRight-> bmiColors.

Проверка битов из двух заголовков битовых карт одинакова (pLeftBits \ pRightBits).

Идея, альтернатива, пример? дай мне знать! спасибо!

  • JE

Ответы [ 2 ]

2 голосов
/ 26 марта 2012

Хотя есть своего рода ошибка.

Вы используете структуру BITMAPINFO, которая на самом деле является фальшивкой и не предназначена для использования как есть.

Фактический заголовок растрового изображения состоит из фиксированной BITMAPINFOHEADER структуры и массива переменного размера * из 1009 * структур, тогда как размер этого массива зависит от данных в 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 будет содержать неинициализированные данные, либо будет недоступен, либо будет иметь больший размер.

PS Кстати, полное сравнение растровых изображений несколько грязное ИМХО.Сохранение растрового изображения в файл, просто для сравнения - выглядит безумно.Кроме того, вам фактически не нужно выделять память для всего растрового изображения, его можно сравнивать построчно.

Кроме того, если одно из растровых изображений является DIB, вы можете напрямую получить указатель на егобиты, следовательно, выделение дополнительной памяти и копирование не требуется.

1 голос
/ 26 марта 2012

Полагаю, вы могли бы использовать SoIL Library (или любую другую, кроме WinApi, на самом деле) для загрузки и работы с растровыми файлами.Он бесплатный и легкий и сократит код примерно на 90%.

...