Save HBITMAP to *.bmp file using only Win32

前端 未结 5 1284
醉酒成梦
醉酒成梦 2020-11-29 05:25

I have a HBITMAP in my pure Win32 project (no external libraries are used). Can I export it to a *.bmp file using only Winapi and/or CRT functions so I don\'t incur any extr

5条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-11-29 06:03

    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 
    #include 
    #include 
    #include 
    
    #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.

提交回复
热议问题