c - Save HBITMAP to *.bmp file using only Win32

ID : 274551

viewed : 24

Tags : cwindowswinapibitmapimagehbitmapc





Top 5 Answer for c - Save HBITMAP to *.bmp file using only Win32

vote vote

96

There is no API to save into file directly because, generally, having a bitmap handle does not mean you have direct access to bitmap data. Your solution is to copy bitmap into another bitmap with data access (DIB) and then using it data to write into file.

You typically either create another bitmap using CreateDIBSection, or you get bitmap data with GetDIBits.

CreateFile, WriteFile writes data into file.

You write: BITMAPFILEHEADER, then BITMAPINFOHEADER, then palette (which you typically don't have when bits/pixel is over 8), then data itself.

See also:

The Code

This is the code from the MSDN article (Note that you need to define the errhandler() function):

PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp) {      BITMAP bmp;      PBITMAPINFO pbmi;      WORD    cClrBits;       // Retrieve the bitmap color format, width, and height.       if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))          errhandler("GetObject", hwnd);       // Convert the color format to a count of bits.       cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);      if (cClrBits == 1)          cClrBits = 1;      else if (cClrBits <= 4)          cClrBits = 4;      else if (cClrBits <= 8)          cClrBits = 8;      else if (cClrBits <= 16)          cClrBits = 16;      else if (cClrBits <= 24)          cClrBits = 24;      else cClrBits = 32;       // Allocate memory for the BITMAPINFO structure. (This structure       // contains a BITMAPINFOHEADER structure and an array of RGBQUAD       // data structures.)        if (cClrBits < 24)          pbmi = (PBITMAPINFO) LocalAlloc(LPTR,          sizeof(BITMAPINFOHEADER) +          sizeof(RGBQUAD) * (1<< cClrBits));       // There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel       else          pbmi = (PBITMAPINFO) LocalAlloc(LPTR,          sizeof(BITMAPINFOHEADER));       // Initialize the fields in the BITMAPINFO structure.        pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);      pbmi->bmiHeader.biWidth = bmp.bmWidth;      pbmi->bmiHeader.biHeight = bmp.bmHeight;      pbmi->bmiHeader.biPlanes = bmp.bmPlanes;      pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;      if (cClrBits < 24)          pbmi->bmiHeader.biClrUsed = (1<<cClrBits);       // If the bitmap is not compressed, set the BI_RGB flag.       pbmi->bmiHeader.biCompression = BI_RGB;       // Compute the number of bytes in the array of color       // indices and store the result in biSizeImage.       // The width must be DWORD aligned unless the bitmap is RLE      // compressed.      pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8         * pbmi->bmiHeader.biHeight;      // Set biClrImportant to 0, indicating that all of the       // device colors are important.       pbmi->bmiHeader.biClrImportant = 0;      return pbmi;  }   void CreateBMPFile(HWND hwnd, LPTSTR 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;       pbih = (PBITMAPINFOHEADER) pbi;      lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);      if (!lpBits)          errhandler("GlobalAlloc", hwnd);       // 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);      }      // 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);      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);      }      // 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);       // Close the .BMP file.       if (!CloseHandle(hf))          errhandler("CloseHandle", hwnd);       // Free memory.       GlobalFree((HGLOBAL)lpBits); } 
vote vote

81

Yet another minimalistic option is to use OLE's IPicture. It's always been around, still a part of Win32 API:

#define _S(exp) (([](HRESULT hr) { if (FAILED(hr)) _com_raise_error(hr); return hr; })(exp));  PICTDESC pictdesc = {}; pictdesc.cbSizeofstruct = sizeof(pictdesc); pictdesc.picType = PICTYPE_BITMAP; pictdesc.bmp.hbitmap = hBitmap;  CComPtr<IPicture> picture; _S( OleCreatePictureIndirect(&pictdesc, __uuidof(IPicture), FALSE, (LPVOID*)&picture) );  // Save to a stream  CComPtr<IStream> stream; _S( CreateStreamOnHGlobal(NULL, TRUE, &stream) ); LONG cbSize = 0; _S( picture->SaveAsFile(stream, TRUE, &cbSize) );  // Or save to a file  CComPtr<IPictureDisp> disp; _S( picture->QueryInterface(&disp) ); _S( OleSavePictureFile(disp, CComBSTR("C:\\Temp\\File.bmp")) ); 
vote vote

78

One function code for HBITMAP to *.bmp file.

BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName) {     HDC hDC;     int iBits;     WORD wBitCount;     DWORD dwPaletteSize = 0, dwBmBitsSize = 0, dwDIBSize = 0, dwWritten = 0;     BITMAP Bitmap0;     BITMAPFILEHEADER bmfHdr;     BITMAPINFOHEADER bi;     LPBITMAPINFOHEADER lpbi;     HANDLE fh, hDib, hPal, hOldPal2 = NULL;     hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);     iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);     DeleteDC(hDC);     if (iBits <= 1)         wBitCount = 1;     else if (iBits <= 4)         wBitCount = 4;     else if (iBits <= 8)         wBitCount = 8;     else         wBitCount = 24;     GetObject(hBitmap, sizeof(Bitmap0), (LPSTR)&Bitmap0);     bi.biSize = sizeof(BITMAPINFOHEADER);     bi.biWidth = Bitmap0.bmWidth;     bi.biHeight = -Bitmap0.bmHeight;     bi.biPlanes = 1;     bi.biBitCount = wBitCount;     bi.biCompression = BI_RGB;     bi.biSizeImage = 0;     bi.biXPelsPerMeter = 0;     bi.biYPelsPerMeter = 0;     bi.biClrImportant = 0;     bi.biClrUsed = 256;     dwBmBitsSize = ((Bitmap0.bmWidth * wBitCount + 31) & ~31) / 8         * Bitmap0.bmHeight;     hDib = GlobalAlloc(GHND, dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));     lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);     *lpbi = bi;      hPal = GetStockObject(DEFAULT_PALETTE);     if (hPal)     {         hDC = GetDC(NULL);         hOldPal2 = SelectPalette(hDC, (HPALETTE)hPal, FALSE);         RealizePalette(hDC);     }       GetDIBits(hDC, hBitmap, 0, (UINT)Bitmap0.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)         + dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);      if (hOldPal2)     {         SelectPalette(hDC, (HPALETTE)hOldPal2, TRUE);         RealizePalette(hDC);         ReleaseDC(NULL, hDC);     }      fh = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,         FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);      if (fh == INVALID_HANDLE_VALUE)         return FALSE;      bmfHdr.bfType = 0x4D42; // "BM"     dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;     bmfHdr.bfSize = dwDIBSize;     bmfHdr.bfReserved1 = 0;     bmfHdr.bfReserved2 = 0;     bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;      WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);      WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);     GlobalUnlock(hDib);     GlobalFree(hDib);     CloseHandle(fh);     return TRUE; } 
vote vote

67

i'll leave this self contained proof of concept here since I'll probably need to look it up later since it's not obvious. It takes a screenshot of the desktop window and saves it into bitmap.bmp:

#include <Windows.h> #include <stdio.h> #include <assert.h>  /* forward declarations */ PBITMAPINFO CreateBitmapInfoStruct(HBITMAP); void CreateBMPFile(LPTSTR pszFile, HBITMAP hBMP);  int main(int argc, char **argv);  PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp) {      BITMAP bmp;      PBITMAPINFO pbmi;      WORD    cClrBits;       // Retrieve the bitmap color format, width, and height.       assert(GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp));       // Convert the color format to a count of bits.       cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);      if (cClrBits == 1)          cClrBits = 1;      else if (cClrBits <= 4)          cClrBits = 4;      else if (cClrBits <= 8)          cClrBits = 8;      else if (cClrBits <= 16)          cClrBits = 16;      else if (cClrBits <= 24)          cClrBits = 24;      else cClrBits = 32;       // Allocate memory for the BITMAPINFO structure. (This structure       // contains a BITMAPINFOHEADER structure and an array of RGBQUAD       // data structures.)         if (cClrBits < 24)           pbmi = (PBITMAPINFO) LocalAlloc(LPTR,                      sizeof(BITMAPINFOHEADER) +                      sizeof(RGBQUAD) * (1<< cClrBits));        // There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel        else           pbmi = (PBITMAPINFO) LocalAlloc(LPTR,                      sizeof(BITMAPINFOHEADER));       // Initialize the fields in the BITMAPINFO structure.        pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);      pbmi->bmiHeader.biWidth = bmp.bmWidth;      pbmi->bmiHeader.biHeight = bmp.bmHeight;      pbmi->bmiHeader.biPlanes = bmp.bmPlanes;      pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;      if (cClrBits < 24)          pbmi->bmiHeader.biClrUsed = (1<<cClrBits);       // If the bitmap is not compressed, set the BI_RGB flag.       pbmi->bmiHeader.biCompression = BI_RGB;       // Compute the number of bytes in the array of color       // indices and store the result in biSizeImage.       // The width must be DWORD aligned unless the bitmap is RLE      // compressed.      pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8                                   * pbmi->bmiHeader.biHeight;      // Set biClrImportant to 0, indicating that all of the       // device colors are important.        pbmi->bmiHeader.biClrImportant = 0;       return pbmi;   }   void CreateBMPFile(LPTSTR pszFile, HBITMAP hBMP)   {       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;          PBITMAPINFO pbi;     HDC hDC;      hDC = CreateCompatibleDC(GetWindowDC(GetDesktopWindow()));     SelectObject(hDC, hBMP);      pbi = CreateBitmapInfoStruct(hBMP);      pbih = (PBITMAPINFOHEADER) pbi;      lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);      assert(lpBits) ;      // Retrieve the color table (RGBQUAD array) and the bits       // (array of palette indices) from the DIB.       assert(GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,          DIB_RGB_COLORS));      // Create the .BMP file.       hf = CreateFile(pszFile,                     GENERIC_READ | GENERIC_WRITE,                     (DWORD) 0,                      NULL,                     CREATE_ALWAYS,                     FILE_ATTRIBUTE_NORMAL,                     (HANDLE) NULL);      assert(hf != INVALID_HANDLE_VALUE) ;      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.       assert(WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),          (LPDWORD) &dwTmp,  NULL));       // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.       assert(WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)                    + pbih->biClrUsed * sizeof (RGBQUAD),                    (LPDWORD) &dwTmp, ( NULL)));      // Copy the array of color indices into the .BMP file.       dwTotal = cb = pbih->biSizeImage;      hp = lpBits;      assert(WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL));       // Close the .BMP file.        assert(CloseHandle(hf));       // Free memory.       GlobalFree((HGLOBAL)lpBits); }  int main(int argc, char **argv) {     HWND hwnd;     HDC hdc[2];     HBITMAP hbitmap;     RECT rect;      hwnd = GetDesktopWindow();     GetClientRect(hwnd, &rect);     hdc[0] = GetWindowDC(hwnd);     hbitmap = CreateCompatibleBitmap(hdc[0], rect.right, rect.bottom);      hdc[1] = CreateCompatibleDC(hdc[0]);     SelectObject(hdc[1], hbitmap);          BitBlt (             hdc[1],         0,         0,         rect.right,         rect.bottom,         hdc[0],         0,         0,         SRCCOPY     );      CreateBMPFile("bitmap.bmp", hbitmap);     return 0; } 
vote vote

56

Yes, this is possible, using the Windows Imaging Component (WIC). WIC offers built-in encoders, so that you don't have to manually write out bitmap headers and data. It also allows you to choose a different encoder (e.g. PNG), by changing as little as one line of code.

The process is fairly straight forward. It consists of the following steps:

  1. Retrieve properties from the source HBITMAP using GetObject (dimensions, bit depth).
  2. Create an IWICImagingFactory instance.
  3. Create an IWICBitmap instance from the HBITMAP (IWICImagingFactory::CreateBitmapFromHBITMAP).
  4. Create an IWICStream instance (IWICImagingFactory::CreateStream), and attach it to a filename (IWICStream::InitializeFromFilename).
  5. Create an IWICBitmapEncoder instance (IWICImagingFactory::CreateEncoder), and associate it with the stream (IWICBitmapEncoder::Initialize).
  6. Create an IWICBitmapFrameEncode instance (IWICBitmapEncoder::CreateNewFrame), and initialize it in compliance with the source HBITMAP (IWICBitmapFrameEncode::Initialize, IWICBitmapFrameEncode::SetSize, IWICBitmapFrameEncode::SetPixelFormat).
  7. Write bitmap data to the frame (IWICBitmapFrameEncode::WriteSource).
  8. Commit frame and data to stream (IWICBitmapFrameEncode::Commit, IWICBitmapEncoder::Commit).

Translated to code:

#define COBJMACROS  #include <Objbase.h> #include <wincodec.h> #include <Windows.h> #include <Winerror.h>  #pragma comment(lib, "Windowscodecs.lib")  HRESULT WriteBitmap(HBITMAP bitmap, const wchar_t* pathname) {      HRESULT hr = S_OK;      // (1) Retrieve properties from the source HBITMAP.     BITMAP bm_info = { 0 };     if (!GetObject(bitmap, sizeof(bm_info), &bm_info))         hr = E_FAIL;      // (2) Create an IWICImagingFactory instance.     IWICImagingFactory* factory = NULL;     if (SUCCEEDED(hr))         hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,                               &IID_IWICImagingFactory, &factory);      // (3) Create an IWICBitmap instance from the HBITMAP.     IWICBitmap* wic_bitmap = NULL;     if (SUCCEEDED(hr))         hr = IWICImagingFactory_CreateBitmapFromHBITMAP(factory, bitmap, NULL,                                                         WICBitmapIgnoreAlpha,                                                         &wic_bitmap);      // (4) Create an IWICStream instance, and attach it to a filename.     IWICStream* stream = NULL;     if (SUCCEEDED(hr))         hr = IWICImagingFactory_CreateStream(factory, &stream);     if (SUCCEEDED(hr))         hr = IWICStream_InitializeFromFilename(stream, pathname, GENERIC_WRITE);      // (5) Create an IWICBitmapEncoder instance, and associate it with the stream.     IWICBitmapEncoder* encoder = NULL;     if (SUCCEEDED(hr))         hr = IWICImagingFactory_CreateEncoder(factory, &GUID_ContainerFormatBmp, NULL,                                               &encoder);     if (SUCCEEDED(hr))         hr = IWICBitmapEncoder_Initialize(encoder, (IStream*)stream,                                           WICBitmapEncoderNoCache);      // (6) Create an IWICBitmapFrameEncode instance, and initialize it     // in compliance with the source HBITMAP.     IWICBitmapFrameEncode* frame = NULL;     if (SUCCEEDED(hr))         hr = IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL);     if (SUCCEEDED(hr))         hr = IWICBitmapFrameEncode_Initialize(frame, NULL);     if (SUCCEEDED(hr))         hr = IWICBitmapFrameEncode_SetSize(frame, bm_info.bmWidth, bm_info.bmHeight);     if (SUCCEEDED(hr)) {         GUID pixel_format = GUID_WICPixelFormat24bppBGR;         hr = IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format);     }      // (7) Write bitmap data to the frame.     if (SUCCEEDED(hr))         hr = IWICBitmapFrameEncode_WriteSource(frame, (IWICBitmapSource*)wic_bitmap,                                                NULL);      // (8) Commit frame and data to stream.     if (SUCCEEDED(hr))         hr = IWICBitmapFrameEncode_Commit(frame);     if (SUCCEEDED(hr))         hr = IWICBitmapEncoder_Commit(encoder);      // Cleanup     if (frame)         IWICBitmapFrameEncode_Release(frame);     if (encoder)         IWICBitmapEncoder_Release(encoder);     if (stream)         IWICStream_Release(stream);     if (wic_bitmap)         IWICBitmap_Release(wic_bitmap);     if (factory)         IWICImagingFactory_Release(factory);      return hr; } 

Here's a companion test application to showcase the usage. Make sure to #define OEMRESOURCE prior to including any system headers to allow use of the OBM_ images.

int wmain(int argc, wchar_t** argv) {      HRESULT hr = S_OK;     hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);     if (FAILED(hr))         return -1;      HBITMAP bitmap = LoadImage(NULL, MAKEINTRESOURCE(OBM_ZOOM), IMAGE_BITMAP, 0, 0,                                LR_DEFAULTCOLOR);      hr = WriteBitmap(bitmap, argv[1]);      // Cleanup     if (bitmap)         DeleteObject(bitmap);      CoUninitialize();     return 0; } 

This will load a system-provided bitmap, and save it to the pathname specified as an argument on the command line.

Limitations:

  • No support for alpha channels. Although bitmaps version 5 support alpha channels, I am not aware of any way to find out, whether an HBITMAP refers to a bitmap with an alpha channel, nor would I know, how to determine, whether it is pre-multiplied. If you do want to support an alpha channel, make sure to set the EnableV5Header32bppBGRA property to VARIANT_TRUE (see BMP Format: Encoding).
  • No support for palletized bitmaps (bpp <= 8). If you are dealing with palletized bitmaps, make sure to supply an appropriate HPALETTE in the call to IWICImagingFactory::CreateBitmapFromHBITMAP.
  • The encoder is initialized with the GUID_WICPixelFormat24bppBGR pixel format constant. A more versatile implementation would deduce a compatible pixel format from the source HBITMAP.

Top 3 video Explaining c - Save HBITMAP to *.bmp file using only Win32







Related QUESTION?