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 (1399 downloads)
Resources and related articles
- MSDN: Sharing Render Target Resources
- MSDN: How to save Direct2D content to an image file
- MSDN: Direct2D Error Codes
- MSDN: ID2D1Factory::CreateWicBitmapRenderTarget method
- Stackoverflow: Wrong render target ID2D1Bitmap
- Codexpert: WIC topics
- Codexpert: Direc2D topics
This solution works ok but disabled the hardware acceleration completely, i’ve tried to find another solution but no way. Any ideas?