C++:将Hbitmap / BITMAP转换为.bmp文件

5
好的,整个故事是这样的,我正在尝试在C++中使用Leptonica + Tesseract OCR来截取屏幕截图,将其保存为*.bmp文件,然后再加载它以进行OCR。我不需要经常这样做,但由于我似乎无法直接将屏幕截图数据复制到Leptonica PIX结构中,因此我需要先将其保存到一个文件中..实际上,找到这个问题的解决方案会更好一些。
以下是我在网上找到的一些代码,希望能帮助我解决问题。
屏幕捕获:
HBITMAP ScreenCapture(){
  int width=100;
  int height=100;
  // get the device context of the screen
  HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);     
  // and a device context to put it in
  HDC hMemoryDC = CreateCompatibleDC(hScreenDC);

  int x = GetDeviceCaps(hScreenDC, HORZRES);
  int y = GetDeviceCaps(hScreenDC, VERTRES);

  // maybe worth checking these are positive values
  HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, x, y);

  // get a new bitmap
  HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);

  BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
  hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);

  //GlobalAlloc(GPTR, hBitmap)

  WriteDIB(L"test.bmp", (HGLOBAL)hBitmap);

  // clean up
  DeleteDC(hMemoryDC);
  DeleteDC(hScreenDC);

  return hBitmap;
  // now your image is held in hBitmap. You can save it or do whatever with it
}

尝试编写函数:
BOOL WriteDIB( LPTSTR szFile, HANDLE hDIB)
{
  cout<<endl<<"Running save function";
  /*HANDLE hDIB=GlobalAlloc(GPTR, sizeof(hDIBtochange));//this doesn't work, the result is four. Also the HANDLE parameter's name would be changed to hDIBtochange, so that the rest of the function uses the old 'hDIB' throughout
  cout<<endl<<sizeof(hDIBtochange);*/
  BITMAPFILEHEADER  hdr;
  LPBITMAPINFOHEADER    lpbi;
  if (!hDIB)
    return FALSE;
  CFile file;
  if( !file.Open( szFile, CFile::modeWrite|CFile::modeCreate) )
    return FALSE;
  lpbi = (LPBITMAPINFOHEADER)hDIB;
  int nColors = 1 << lpbi->biBitCount;
  // Fill in the fields of the file header 
  hdr.bfType        = ((WORD) ('M' << 8) | 'B');    // is always "BM"
  hdr.bfSize        = GlobalSize (hDIB) + sizeof( hdr );
  hdr.bfReserved1   = 0;
  hdr.bfReserved2   = 0;
  hdr.bfOffBits     = (DWORD) (sizeof( hdr ) + lpbi->biSize + nColors * sizeof(RGBQUAD));
  // Write the file header 
  file.Write( &hdr, sizeof(hdr) );
  // Write the DIB header and the bits 
  file.Write( lpbi, GlobalSize(hDIB) );
  return TRUE;
}

无耻地抄袭别人多年来的文章。好吧!我面临的问题是,我似乎无法理解如何将Hbitmap GlobalAlloc到全局可访问的句柄中,该句柄可以转换或与LPBITMAPINFOHEADER一起使用。一旦创建了lpbi,它内部的每个字段都会在Visual Studio 2012调试中出现“无法读取内存”的错误。尽管被创建了,但它是不可访问的。
解决方案.. 直接从屏幕截图到PIX,在内存中进行。 找到一种保存为位图并定期创建以读取的方法。 找到另一种更合理的方式。
首选第一个,但我请求在第二个或第三个解决方案中提供一个解决方案。
如果需要更多信息,我可以尝试提供。这主要归结为“我以前从未编写过此类代码,并且在我的课程中没有教授过,所以我正在学习”。
3个回答

14

使用GDI+是将HBITMAP保存到文件的更简单的方法,这使您能够保存到Windows支持的任何格式,同时使您免于玩弄或甚至需要理解各种图像格式的麻烦。

在下面的示例中,我只是使用LoadImage作为一种快速便捷的方式来加载预先存在的图像 - 您可以简单地使用您已经捕获的HBITMAP。

以下是加载位图并再次保存它的示例。(最初我使用了“image / png”作为输出类型,以及适当的输出文件名)

#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
   UINT  num = 0;          // number of image encoders
   UINT  size = 0;         // size of the image encoder array in bytes

   ImageCodecInfo* pImageCodecInfo = NULL;

   GetImageEncodersSize(&num, &size);
   if(size == 0)
      return -1;  // Failure

   pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
   if(pImageCodecInfo == NULL)
      return -1;  // Failure

   GetImageEncoders(num, size, pImageCodecInfo);

   for(UINT j = 0; j < num; ++j)
   {
      if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
      {
         *pClsid = pImageCodecInfo[j].Clsid;
         free(pImageCodecInfo);
         return j;  // Success
      }
   }
   free(pImageCodecInfo);
   return -1;  // Failure
}

int main()
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    HBITMAP hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), "babe.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
    Bitmap *image = new Bitmap(hBitmap, NULL);

    CLSID myClsId;
    int retVal = GetEncoderClsid(L"image/bmp", &myClsId);

    image->Save(L"output.bmp", &myClsId, NULL);
    delete image;

    GdiplusShutdown(gdiplusToken);
    return 0;
}

谢谢。如果我能弄明白的话,其他人的代码可能会起作用,但是你的代码非常简单,而且恰好做到了我需要它做的事情。 - user3285714
不用谢。GDI+已经存在了10多年,对于许多事情来说比自己动手解决要容易/好得多。 - enhzflep

4
我最近也需要做与您相同的事情,并成功使用了GlobalAlloc。
这段代码的基础来自于这篇MSDN文章
看起来您是从这里获取示例代码
在win32操作中,MSDN非常可靠,根据我的经验,我肯定会优先选择它而不是其他网站。
似乎发生的情况是sizeof(hDIBtochange)返回4,因此您只分配了4个字节的内存。这将无法容纳pbi结构。
以下是带有GlobalAlloc的代码,希望能展示正确的用法。
void
WriteBmpTofile(const bool remote, LPSTR pszFile, PBITMAPINFO pbi, HBITMAP hBmp, HDC hDC)
{
    HANDLE hFile;
    BITMAPFILEHEADER hdr;
    PBITMAPINFOHEADER pbih;
    LPBYTE lpBits;
    DWORD dwTemp;

    pbih = (PBITMAPINFOHEADER)pbi;
    lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);

    if(!lpBits)
    {
        return; // could not allocate bitmap
    }

    GetDIBits(hDC, hBmp, 0, (WORD)pbih->biHeight, lpBits, pbi, DIB_RGB_COLORS);

    hFile = CreateFile(pszFile,
                        GENERIC_READ | GENERIC_WRITE,
                        0,
                        NULL,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);

    if(hFile == INVALID_HANDLE_VALUE)
    {
        return; // Could not open screenshot file
    }

    // type == BM
    hdr.bfType = 0x4d42;

    hdr.bfSize = (sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
    hdr.bfReserved1 = 0;
    hdr.bfReserved2 = 0;

    hdr.bfOffBits = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);

    // write the bitmap file header to file
    WriteFile(hFile, (LPVOID)&hdr, sizeof(BITMAPFILEHEADER), &dwTemp, NULL);

    // write the bitmap header to file
    WriteFile(hFile, (LPVOID)pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof(RGBQUAD), &dwTemp, NULL);

    // copy the bitmap colour data into the file
    WriteFile(hFile, (LPSTR)lpBits, pbih->biSizeImage, &dwTemp, NULL);

    CloseHandle(hFile);

    GlobalFree((HGLOBAL)lpBits);
}

这是那篇MSDN文章中的顶级函数,如果需要的话(再次经过我的修改)。

PBITMAPINFO
Print::CreateBitmapInfo(HBITMAP hBmp)
{
    BITMAP bmp;
    PBITMAPINFO pbmi;

    GetObject(hBmp, sizeof(BITMAP), &bmp);

    pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)));

    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = bmp.bmWidth;
    pbmi->bmiHeader.biHeight = bmp.bmHeight;
    pbmi->bmiHeader.biPlanes = bmp.bmPlanes; // we are assuming that there is only one plane
    pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;

    // no compression this is an rgb bitmap
    pbmi->bmiHeader.biCompression = BI_RGB;

    // calculate size and align to a DWORD (8bit), we are assuming there is only one plane.
    pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * bmp.bmBitsPixel +31) & -31) * pbmi->bmiHeader.biHeight;

    // all device colours are important
    pbmi->bmiHeader.biClrImportant = 0;

    return pbmi;
}

1
我猜你的代码是从这里获得的 存储图像。一段时间以前,我不得不修改代码以适用于WinCE 5.0和WinCE 6.0。这是beta-sample,虽然有点混乱。它不使用GlobalAlloc,而是使用CreateDibSection。
int CreateBMPFile(HWND hwnd, LPCTSTR pszFile, PBITMAPINFO pbi, 
                  HBITMAP hBMP, HDC hDC) 
 { 
    HANDLE hf;                  // file handle  
    BITMAPFILEHEADER hdr;       // bitmap file-header  
    PBITMAPINFOHEADER pbih;     // bitmap info-header  
    //LPBYTE lpBits;            // memory pointer  
    DWORD dwTotal;              // total count of bytes  
    DWORD cb;                   // incremental count of bytes  
    BYTE *hp;                   // byte pointer  
    DWORD dwTmp; 
    int ret = 0;


    pbi = CreateBitmapInfoStruct(NULL, hBMP);
    if(pbi == NULL)
    {
        return ret;
    }
    pbih = (PBITMAPINFOHEADER) pbi; 

    /*
    lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
    if (!lpBits) 
    {
         //errhandler("GlobalAlloc", hwnd); 
        return;
    }
    */

    RGBQUAD *rgbq;
    rgbq = pbi->bmiColors;
    PALETTEENTRY pe[256];
    GetSystemPaletteEntries(hDC, 0, pbih->biClrUsed, pe);
    for(DWORD i = 0; i < pbih->biClrUsed; i++)
    {
        rgbq[i].rgbRed = pe[i].peRed;
        rgbq[i].rgbBlue = pe[i].peBlue;
        rgbq[i].rgbGreen = pe[i].peGreen;
        rgbq[i].rgbReserved = 0;
    }

    // CE5.0 + CE6.0
    HDC tHDC;
    tHDC = CreateCompatibleDC(hDC);
    HBITMAP h = CreateDIBSection(hDC, pbi, DIB_PAL_COLORS, (void **)&hp, NULL, 0);
    if(h == NULL)
    {
        goto close_bmp;
    }
    SelectObject(tHDC, h);
    BitBlt(tHDC, 0, 0, SCREEN_W, SCREEN_H, hDC, 0, 0, SRCCOPY);

    /*
    // Retrieve the color table (RGBQUAD array) and the bits  
    // (array of palette indices) from the DIB.  
    if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi, 
        DIB_RGB_COLORS)) 
    {
        //errhandler("GetDIBits", hwnd); 
        return;
    }
    */

    // Create the .BMP file.  
    hf = CreateFile(pszFile, 
                   GENERIC_READ | GENERIC_WRITE, 
                   (DWORD) 0, 
                    NULL, 
                   CREATE_ALWAYS, 
                   FILE_ATTRIBUTE_NORMAL, 
                   (HANDLE) NULL); 
    if (hf == INVALID_HANDLE_VALUE) 
    {
        //errhandler("CreateFile", hwnd); 
        goto close_bmp;
    }
    hdr.bfType = 0x4d42;        // 0x42 = "B" 0x4d = "M"  
    // Compute the size of the entire file.  
    hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + 
                 pbih->biSize + pbih->biClrUsed 
                 * sizeof(RGBQUAD) + pbih->biSizeImage); 
    hdr.bfReserved1 = 0; 
    hdr.bfReserved2 = 0; 

    // Compute the offset to the array of color indices.  
    hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + 
                    pbih->biSize + pbih->biClrUsed 
                    * sizeof (RGBQUAD); 

    // Copy the BITMAPFILEHEADER into the .BMP file.  
    if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), 
        (LPDWORD) &dwTmp,  NULL)) 
    {
       //errhandler("WriteFile", hwnd); 
        goto close_bmp;
    }

    // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.  
    if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) 
                  + pbih->biClrUsed * sizeof (RGBQUAD), 
                  (LPDWORD) &dwTmp, ( NULL)))
    {
        //errhandler("WriteFile", hwnd); 
    }

    // Copy the array of color indices into the .BMP file.  
    dwTotal = cb = pbih->biSizeImage; 

    //hp = lpBits;     
    if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL)) 
    {
        //errhandler("WriteFile", hwnd); 
        goto close_bmp;
    }



close_bmp:
    // Close the .BMP file.  
    if(hf != INVALID_HANDLE_VALUE)
    {
        if (!CloseHandle(hf)) 
        {
           //errhandler("CloseHandle", hwnd); 
        }
        else
        {
            ret = 1;
        }
    }
    // Free memory.  
    // GlobalFree((HGLOBAL)lpBits); 
    if(tHDC != NULL)
        DeleteObject(tHDC);
    if(h != NULL)
        DeleteObject(h);
    if(pbi != NULL)
    {
        //LocalFree(pbi);
        free(pbi);
    }

    return ret;

}

我会在明天早上测试这段代码,如果可以的话。但是不,我的代码都是从论坛帖子中获取的,没有直接来自MSDN。其中一个函数来自Stack Overflow,另一个来自CodeGuru。我忘记哪个是哪个了。 - user3285714

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接