Search code examples
directxdirect2dwicdxgi

Direct2D: How to save content of ID2D1RenderTarget to an image file?


The question is very similar to this, but that one didn't get answered yet. My question is, I have a D2D DXGI RenderTarget from d2dfactory->CreateDxgiSurfaceRenderTarget(), and I want to save its content to an image file using WIC. I was just reading this and this, so it looks to me that I can not just create a ID2D1Bitmap on a WIC render target and use ID2D1Bitmap::CopyFromRenderTarget() to copy from the input render target I want to save, because they are using different resources. So here is what I came up with using ID2D1RenderTarget::CreateSharedBitmap():

HRESULT SaveRenderTargetToFile(
    ID2D1RenderTarget* pRTSrc,
    LPCWSTR uri
    )
{
    HRESULT hr = S_OK;

    ComPtr<IWICBitmap> spWICBitmap;
    ComPtr<ID2D1RenderTarget> spRT;
    ComPtr<IWICBitmapEncoder> spEncoder;
    ComPtr<IWICBitmapFrameEncode> spFrameEncode;
    ComPtr<IWICStream> spStream;

    //
    // Create WIC bitmap to save and associated render target
    //

    UINT bitmapWidth = static_cast<UINT>(pRTSrc->GetSize().width + .5f);
    UINT bitmapHeight = static_cast<UINT>(pRTSrc->GetSize().height + .5f);

    HR(m_spWICFactory->CreateBitmap(
        bitmapWidth,
        bitmapHeight,
        GUID_WICPixelFormat32bppPBGRA,
        WICBitmapCacheOnLoad,
        &spWICBitmap
        ));

    D2D1_RENDER_TARGET_PROPERTIES prop = D2D1::RenderTargetProperties();
    prop.pixelFormat = D2D1::PixelFormat(
        DXGI_FORMAT_B8G8R8A8_UNORM,
        D2D1_ALPHA_MODE_PREMULTIPLIED
        );
    prop.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
    prop.usage = D2D1_RENDER_TARGET_USAGE_NONE;
    HR(m_spD2D1Factory->CreateWicBitmapRenderTarget(
        spWICBitmap,
        prop,
        &spRT
        ));

    //
    // Create a shared bitmap from this RenderTarget
    //
    ComPtr<ID2D1Bitmap> spBitmap;
    D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
    bp.pixelFormat = prop.pixelFormat;

    HR(spRT->CreateSharedBitmap(
        __uuidof(IWICBitmap),
        static_cast<void*>(spWICBitmap.GetRawPointer()),
        &bp,
        &spBitmap
        ));    // <------------------------- This fails with E_INVALIDARG

    //
    // Copy the source RenderTarget to this bitmap
    //
    HR(spBitmap->CopyFromRenderTarget(nullptr, pRTSrc, nullptr));

    //
    // Draw this bitmap to the output render target
    //

    spRT->BeginDraw();
    spRT->Clear(D2D1::ColorF(D2D1::ColorF::GreenYellow));
    spRT->DrawBitmap(spBitmap);
    HR(spRT->EndDraw());

    //
    // Save image to file
    //

    HR(m_spWICFactory->CreateStream(&spStream));
    WICPixelFormatGUID format = GUID_WICPixelFormat32bppPBGRA;
    HR(spStream->InitializeFromFilename(uri, GENERIC_WRITE));

    HR(m_spWICFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &spEncoder));
    HR(spEncoder->Initialize(spStream, WICBitmapEncoderNoCache));
    HR(spEncoder->CreateNewFrame(&spFrameEncode, nullptr));
    HR(spFrameEncode->Initialize(nullptr));
    HR(spFrameEncode->SetSize(bitmapWidth, bitmapHeight));
    HR(spFrameEncode->SetPixelFormat(&format));
    HR(spFrameEncode->WriteSource(spWICBitmap, nullptr));
    HR(spFrameEncode->Commit());
    HR(spEncoder->Commit());
    HR(spStream->Commit(STGC_DEFAULT));

done:
    return hr;
}

Anything wrong with this code? (I'm sure there's a lot :)) Somewhere on MSDN it says that WIC render target only supports software mode, while DXGI render target only supports hardware mode. Is this the reason why the above call to CreateSharedBitmap() fails? How should I save a DXGI surface content to an image file with D2D then?


Solution

    1. With some limitations, you can use D3DX11SaveTextureToFile. Use QI on your surface to get the ID3D11Resource.

    2. On the same page they are recommending DirectXTex library as a replacement, CaptureTexture then SaveToXXXFile (where XXX is WIC, DDS, or TGA). So that's another option.

    3. Also, if your surface has been created as GDI compatible, you can use IDXGISurface1::GetDC. (Use QI on your IDXGISurface to get the IDXGISurface1). Saving DC to a file is left as an exercise to the reader.

    Remember to use the Debug Layer for help with cryptic return codes like E_INVALIDARG.