C++: Hbitmap/BITMAP into .bmp file [duplicate]

2019-05-23 07:42发布

问题:

This question already has an answer here:

  • Save HBITMAP to *.bmp file using only Win32 4 answers

Ok, whole story is, I am trying to use Leptonica+Tesseract OCR in C++ to take a screenshot, save it to a *.bmp file, then load it back up to OCR with it. I won't need to do this frequently, but as I cannot seem to copy the screenshot data directly into a Leptonica PIX structure, I need to save it to a file first..actually a solution to this would be preferably.

Here's some code I've found online, trying to help me out.

Screen cap:

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
}

Attempt to write function:

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;
}

Shamelessly copied from people's posts over the years. Ok! Problem I face is, I cannot seem to understand how to GlobalAlloc the Hbitmap into a globally accessible Handle, that can be converted or use with LPBITMAPINFOHEADER. Soon as lpbi is created, every single field inside of it is "Unable to read memory" error in Visual Studio 2012 debugging. It's inaccessible, despite being created.

Solutions.. Go straight from screencap to PIX, inside of memory.. Find a way to save to bitmap and create them periodically to read.. Find another way entirely that makes more sense..

Preferring first, but, I'm asking for a solution in this, to the second one..or third.

If you need more info I can try to provide it. This mostly boils down to "I've never done code like this before and it wasn't taught in my classes so I'm trying to learn as I go".

回答1:

A much easier way to save an HBITMAP to file is to make use of GDI+. This gives you the advantage of being able to save to any format that windows supports natively, while freeing you from the muck of playing around with or even needing to understand, various image formats.

In the below example, I've just used LoadImage as a quik and dirty way of loading a pre-existing image - you could simply use the HBITMAP you've already captured.

Here's an example that loads a bitmap and saves it again. (I had initially used "image/png" as the output type, along with an appropriate output filename)

#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;
}


回答2:

I recently had to do the same thing you are doing and successfully used GlobalAlloc.
The basis of this code is from This MSDN Article.

It looks like you Got your example code from here.

MSDN is really reliable for win32 operations, definitely prefer it over other sites in my experaince.

What seems to be happening is that the sizeof(hDIBtochange) is returning 4, so you are only allocating 4 bytes of memory. which would not be enough to hold a pbi structure.

Here is my code with a GlobalAlloc which hopefully will show the correct usage.

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);
}

Here is the top function in that MSDN article, if you need it (again modified by me).

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;
}


回答3:

I'm guessing you got your code from here Storing an Image. A while back I had to modify the code to work with WinCE 5.0 and WinCE 6.0. Here is the beta-sample though it is kinda messy. It does it without the GlobalAlloc. It uses CreateDibSection instead.

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;

}