MFC Support for Direct2D – Saving to files

Once have enabled Direct2D support in an MFC application, there is no sweat to load an image from a file, by using one of CD2DBitmap constructors.
Unfortunately, we cannot find a CD2DBitmap method to save it into a image file (something similar to CImage::Save or Gdiplus::Image::Save).
No problem, we can try to do it ourselves. Here is an example:

Create and use a WIC bitmap render target

void CDemoView::SaveImageToFile(CD2DBitmap* pBitmap, const CString& strFilePath, GUID containerFormat)
{
    if (!pBitmap)
        AfxThrowOleException(E_FAIL);

    // Get global Direct2D and WIC factories
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    ID2D1Factory* pD2DFactory = pD2DState->GetDirect2dFactory();
    IWICImagingFactory* pWICFactory = pD2DState->GetWICFactory();
    if (!pD2DFactory || !pWICFactory)
        AfxThrowOleException(E_FAIL);

    // Create a WIC bitmap
    const CD2DSizeF size = pBitmap->GetSize();
    const UINT nWidth = static_cast<UINT>(size.width);
    const UINT nHeight = static_cast<UINT>(size.height);
    CComPtr<IWICBitmap> spWicBitmap;
    HRESULT hr = pWICFactory->CreateBitmap(nWidth, nHeight,
        GUID_WICPixelFormat32bppPBGRA, WICBitmapCacheOnLoad, &spWicBitmap);
    ATLENSURE_SUCCEEDED(hr);

    // Create a Direct2D render target that renders to a WIC bitmap
    ID2D1RenderTarget* pRenderTarget = NULL;
    hr = pD2DFactory->CreateWicBitmapRenderTarget(spWicBitmap,
        &D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_SOFTWARE,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            0.f, 0.f, D2D1_RENDER_TARGET_USAGE_NONE),
        &pRenderTarget);
    ATLENSURE_SUCCEEDED(hr);

    // Draw the Direct2D bitmap into created WIC bitmap render target
    CRenderTarget renderTarget;
    renderTarget.Attach(pRenderTarget);
    renderTarget.BeginDraw();
    renderTarget.DrawBitmap(pBitmap, CD2DRectF(0, 0, size.width, size.height));
    hr = renderTarget.EndDraw();
    ATLENSURE_SUCCEEDED(hr);

    // Use WIC stuff to save the WIC bitmap into a file
    CComPtr<IWICStream> spStream;
    hr = pWICFactory->CreateStream(&spStream);
    ATLENSURE_SUCCEEDED(hr);

    hr = spStream->InitializeFromFilename(strFilePath, GENERIC_WRITE);
    ATLENSURE_SUCCEEDED(hr);

    CComPtr<IWICBitmapEncoder> spEncoder;
    hr = pWICFactory->CreateEncoder(containerFormat, NULL, &spEncoder.p);
    ATLENSURE_SUCCEEDED(hr);

    hr = spEncoder->Initialize(spStream, WICBitmapEncoderNoCache);
    ATLENSURE_SUCCEEDED(hr);
    CComPtr<IWICBitmapFrameEncode> spFrameEncode;

    hr = spEncoder->CreateNewFrame(&spFrameEncode, NULL);
    ATLENSURE_SUCCEEDED(hr);

    hr = spFrameEncode->Initialize(NULL);
    ATLENSURE_SUCCEEDED(hr);

    hr = spFrameEncode->SetSize(nWidth, nHeight);
    ATLENSURE_SUCCEEDED(hr);

    WICPixelFormatGUID pixelFormat = GUID_WICPixelFormatDontCare;
    hr = spFrameEncode->SetPixelFormat(&pixelFormat);
    ATLENSURE_SUCCEEDED(hr);

    hr = spFrameEncode->WriteSource(spWicBitmap, NULL);
    ATLENSURE_SUCCEEDED(hr);

    hr = spFrameEncode->Commit();
    ATLENSURE_SUCCEEDED(hr);

    hr = spEncoder->Commit();
    ATLENSURE_SUCCEEDED(hr);
}

Looks good although it can be refactorized and/or can be improved for saving multiple frames, metadata and so on. However, it’s a problem: if the Direct2D bitmap (pBitmap) has been created in the window’s render target, EndDraw returns D2DERR_WRONG_RESOURCE_DOMAIN error (the resource was realized on the wrong render target). That is because CWnd::EnableD2DSupport creates a render target of type D2D1_RENDER_TARGET_TYPE_DEFAULT and as stated in the documentation, it cannot share its resources with another render targets. So, let’s replace EnableD2DSupport call with a call to another one (let’s say it EnableD2DSoftwareSupport).

Replace CWnd::EnableD2DSupport

int CDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (__super::OnCreate(lpCreateStruct) == -1)
        return -1;

    try
    {
        // EnableD2DSupport(); // <-- TODO: remove this!
        EnableD2DSoftwareSupport();
    }
    catch (COleException* e)
    {
        e->ReportError();
        e->Delete();
        return -1;
    }
    return 0;
}
void CDemoView::EnableD2DSoftwareSupport()
{
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    if (pD2DState == NULL || !pD2DState->InitD2D())
        AfxThrowOleException(E_FAIL); // D2D is not supported by system

    if (IsD2DSupportEnabled())
        return; // Already enabled

    ID2D1Factory* pDirect2DFactory = AfxGetD2DState()->GetDirect2dFactory();
    ID2D1HwndRenderTarget* pHwndRenderTarget = NULL;
    D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED);
    D2D1_HWND_RENDER_TARGET_PROPERTIES hwndTargetProperties = D2D1::HwndRenderTargetProperties(m_hWnd);
    D2D1_RENDER_TARGET_PROPERTIES targetProperties = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_SOFTWARE, pixelFormat);

    HRESULT hr = pDirect2DFactory->CreateHwndRenderTarget(
        targetProperties,
        hwndTargetProperties,
        &pHwndRenderTarget);
   
    ATLENSURE_SUCCEEDED(hr);

    // instantiate new CHwndRenderTarget and attach the GDI-compatible render target
    m_pRenderTarget = new CHwndRenderTarget;
    GetRenderTarget()->Attach(pHwndRenderTarget);
}

More details can be found in the demo application attached below. Just to note that forcing software rendering for window’s render target leads in performance penalty but generally, we can live with that until finding a better solution. 🙂

Demo application

The demo application is a simple image viewer using MFC support for Direct2D and WIC to load/save images from/into into files.
Download Save Direct2D bitmap in a file - Demo.zip (1234 downloads)

Resources and related articles

1 thought on “MFC Support for Direct2D – Saving to files”

  1. This solution works ok but disabled the hardware acceleration completely, i’ve tried to find another solution but no way. Any ideas?

    Reply

Leave a Comment