Сохранить HBITMAP в *.bmp-файл, использующий только Win32

У меня есть HBITMAP в моем чистом проекте Win32 (внешние библиотеки не используются). Могу ли я экспортировать его в *.bmp-файл, использующий только функции Winapi и/или CRT, поэтому я не несу никаких дополнительных зависимостей?

4 ответов


нет API для сохранения в файл напрямую, потому что, как правило, наличие растрового дескриптора не означает, что у вас есть прямой доступ к растровым данным. Ваше решение-скопировать растровое изображение в другое растровое изображение с доступом к данным (DIB), а затем использовать его для записи в файл.

вы обычно либо создаете другое растровое изображение, используя CreateDIBSection, или вы получаете растровые данные с GetDIBits.

CreateFile, WriteFile записывает данные в файл.

вы пишете: BITMAPFILEHEADER, потом BITMAPINFOHEADER, тогда палитра (которой у вас обычно нет, когда бит/пиксель превышает 8), а затем сами данные.

Читайте также:

Код

это код из статьи MSDN (Примечание что вам нужно определить


Я оставлю это самодостаточное доказательство концепции здесь, так как мне, вероятно, нужно будет посмотреть его позже, так как это не очевидно. Он делает скриншот окна рабочего стола и сохраняет его в растровое изображение.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;
}

Да, это возможно, используя Компонент Windows Imaging (WIC). WIC предлагает встроенные кодеры, так что вам не нужно вручную записывать заголовки растровых изображений и данные. Он также позволяет выбрать другой кодировщик (например, PNG), изменяя всего лишь одну строку кода.

процесс довольно прямо вперед. Он состоит из следующих шагов:

  1. получить свойства из источника HBITMAP используя GetObject (размеры, глубина).
  2. создать IWICImagingFactory экземпляра.
  3. создать IWICBitmap экземпляр HBITMAP (IWICImagingFactory::CreateBitmapFromHBITMAP).
  4. создать IWICStream экземпляр (IWICImagingFactory::CreateStream), и прикрепить его к файлу (IWICStream::InitializeFromFilename).
  5. создать IWICBitmapEncoder экземпляр (IWICImagingFactory::CreateEncoder), и связать его с потоком (IWICBitmapEncoder::Initialize).
  6. создать IWICBitmapFrameEncode экземпляр (IWICBitmapEncoder::CreateNewFrame), и инициализировать его в соответствии с Источник HBITMAP (IWICBitmapFrameEncode::Initialize, IWICBitmapFrameEncode::SetSize, IWICBitmapFrameEncode::SetPixelFormat).
  7. запись растровых данных в кадр (IWICBitmapFrameEncode::WriteSource).
  8. зафиксировать кадр и данные в потоке (IWICBitmapFrameEncode::Commit, IWICBitmapEncoder::Commit).

переведен на код:

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

вот товарищ тестовое приложение для демонстрации использования. Убедитесь, что #define OEMRESOURCE до включения любых системных заголовков, чтобы разрешить использование OBM_ картинки.

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

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

ограничения:

  • нет поддержки alpha каналы. Хотя растровые изображения версии 5 поддерживают Альфа-каналы, я не знаю никакого способа узнать, является ли HBITMAP относится к растровому изображению с альфа-каналом, и я не знаю, как определить, является ли он предварительно умноженным. Если вы хотите поддерживать альфа-канал, ставь EnableV5Header32bppBGRA свойство VARIANT_TRUE (см. формат BMP: кодировка).
  • нет поддержки растровых изображений на паллетах (bpp HPALETTE в вызове IWICImagingFactory::CreateBitmapFromHBITMAP.
  • кодировщик инициализируется с помощью GUID_WICPixelFormat24bppBGR константа формата пиксела. Более универсальная реализация выведет совместимый формат пикселей из источника HBITMAP.

еще один минималистичный вариант-использовать Оле IPicture. Это всегда было вокруг, все еще часть 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")) );