Using Direct2D for Rendering WIC Bitmap Sources

Once having a WIC (Windows Imaging Component) source, we can render it by using GDI, GDI+ or Direct2D. There are many examples over the internet including MSDN Library that describes how to perform this.
This article describes how to use WIC Wrapper Library, which is designed for using WIC without worrying about directly deal with COM interfaces. The sample code as well as attached demo project uses MFC, but WIC Wrapper Library can be used in ATL or raw-Win32 projects, as well.

Let’s see the code, step by step!

  1. Add WIC and Direc2D factoy members to the aplication class.
    // DemoImageViewer.h
    // ...
    #include "WicWrapperLibrary.h"
    // ...
    class CDemoImageViewerApp : public CWinAppEx
    {
    private:
        std::shared_ptr<ns::wic::ImagingFactory> m_pWICImagingFactory;
        std::shared_ptr<ns::d2d1::Factory>       m_pDirect2DFactory;
    
    public:
        std::shared_ptr<ns::wic::ImagingFactory> GetWICImagingFactory() {return m_pWICImagingFactory;}
        std::shared_ptr<ns::d2d1::Factory> GetDirect2DFactory() {return m_pDirect2DFactory;}
    //...
    };
    
  2. Create WIC and Direc2D factory instances after initializing OLE libraries.
    BOOL CDemoImageViewerApp::InitInstance()
    {
        // ...
        // Initialize OLE libraries
        if (!AfxOleInit())
        {
            AfxMessageBox(IDP_OLE_INIT_FAILED);
            return FALSE;
        }
    
        try
        {
            // create WIC Imaging Factory instance
            m_pWICImagingFactory = std::make_shared<ns::wic::ImagingFactory>();
            m_pWICImagingFactory->CreateInstance();
    
            // Create Direct2D Factory instance
            m_pDirect2DFactory = std::make_shared<ns::d2d1::Factory>();
            m_pDirect2DFactory->CreateInstance();
        }
        catch(CException* e)
        {
            e->ReportError();
            e->Delete();
            return FALSE;
        }
        // ...
    }
    
  3. Release factories befor calling AfxOleTerm.
    int CDemoImageViewerApp::ExitInstance()
    {
        // release WIC Imaging and Direct2D Factory instances
        m_pWICImagingFactory->ReleaseInstance();
        m_pDirect2DFactory->ReleaseInstance();
    
        AfxOleTerm(FALSE);
    
        return CWinAppEx::ExitInstance();
    }
    

     

  4. Add WIC bitmap source member to document class then override CDocument::OnOpenDocument and CDocument::DeleteContents. Also add an implementation function that loads the WIC bitmap source from a file.
    // DemoImageViewerDoc.h
    // ...
    class CDemoImageViewerDoc : public CDocument
    {
        // Attributes
    private:
        std::shared_ptr<ns::wic::FormatConverter> m_pWICBitmapSource;
    
        // Operations
    public:
        std::shared_ptr<ns::wic::FormatConverter> GetWICBitmapSource() {return m_pWICBitmapSource;}
    
        // Overrides
    public:
        virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
        virtual void DeleteContents();
    
        // Implementation
    private:
        BOOL _LoadBitmapSourceFromFile(LPCTSTR pszFileName);
    // ...
    };
    
    // DemoImageViewerDoc.cpp
    // ...
    BOOL CDemoImageViewerDoc::OnOpenDocument(LPCTSTR lpszPathName)
    {
        if (!CDocument::OnOpenDocument(lpszPathName))
            return FALSE;
    
        return _LoadBitmapSourceFromFile(lpszPathName);
    }
    
    void CDemoImageViewerDoc::DeleteContents()
    {
        m_pWICBitmapSource.reset();
    
        CDocument::DeleteContents();
    }
    
    BOOL CDemoImageViewerDoc::_LoadBitmapSourceFromFile(LPCTSTR pszFileName)
    {
        BOOL bRet = FALSE;
        try
        {
            // get WIC Imaging Factory
            CDemoImageViewerApp* pApp = static_cast<CDemoImageViewerApp*>(AfxGetApp());
            std::shared_ptr pWICImagingFactory 
                = pApp->GetWICImagingFactory();
    
            // create BitmapDecoder from a file
            const std::shared_ptr<ns::wic::BitmapDecoder> pBitmapDecoder = 
                pWICImagingFactory->CreateDecoderFromFilename(CT2W(pszFileName));
    
            // get first page
            const std::shared_ptrr<ns::wic::BitmapFrameDecoder> pBitmapFrameDecoder 
                = pBitmapDecoder->GetFrame(0);
    
            // get and initialize a format converter  
            const std::shared_ptr<ns::wic::FormatConverter> pFormatConverter 
                = pWICImagingFactory->CreateFormatConverter();
            pFormatConverter->Initialize(pBitmapFrameDecoder);
    
            m_pWICBitmapSource = pFormatConverter;
            bRet = TRUE; // success
        }
        catch(CAtlException& e)
        {
            CString strErrMsg;
            strErrMsg.Format(_T("Open bitmap source failed\nError code: 0x%08X"), e.m_hr);
            AfxMessageBox(strErrMsg, MB_ICONERROR);
            DeleteContents();
        }
        return bRet;
    }
    // ...
    

    Now we have a WIC bitmap source in the document class that will be used in the view class to create a Direct2D bitmap and render it in the view’s window.

  5. Add Direct2D render target and bitmap to the view class
    // DemoImageViewerView.h
    // ...
    class CDemoImageViewerView : public CScrollView
    {
    // Attributes
    private:
        std::shared_ptr<ns::d2d1::HwndRenderTarget> m_pD2DRenderTarget;
        std::shared_ptr<ns::d2d1::Bitmap> m_pD2DBitmap;
    
    // Implementation
    private:
        bool _CreateDirect2DRenderTarget(); 
        void _Resize2DRenderTarget(int cx, int cy);
        bool _CreateDirect2DBitmapFromWICBitmap();
        void _RenderD2DBitmap(CDC* pDC);
    //...
    };
    
  6. Create a Direct2D render target for the view window.
    bool CDemoImageViewerView::_CreateDirect2DRenderTarget()
    {
        bool bRet = false;
        try
        {
            // reset the window render target
            m_pD2DRenderTarget.reset();
    
            // get Direct2D factory
            CDemoImageViewerApp* pApp = static_cast<CDemoImageViewerApp*>(AfxGetApp());
            std::shared_ptr<ns::d2d1::Factory> pDirect2DFactory = pApp->GetDirect2DFactory();
    
            // create a render target for this window
            // intial size is zero; will be resized in WM_SIZE message handler
            m_pD2DRenderTarget = pDirect2DFactory->CreateHwndRenderTarget(m_hWnd, 0, 0);
    
            bRet = true; // success
        }
        catch(CAtlException& e)
        {
            CString strError; // show what's going wrong
            strError.Format(_T("Create render target failed.\nError code: 0x%08X"), e.m_hr);
            AfxMessageBox(strError);
        }
        return bRet;
    }
    

    A good place to call _CreateDirect2DRenderTarget is in the WM_CREATE message handler.

  7. Resize the Direct2D render target each time is necessary.
    void CDemoImageViewerView::_Resize2DRenderTarget(int cx, int cy)
    {
        if(nullptr != m_pD2DRenderTarget)
        {
            try
            {
                ns::d2d1::SIZE_U sizeTarget = {cx, cy};
                m_pD2DRenderTarget->Resize(sizeTarget);
            }
            catch(CAtlException& e)
            {
                HRESULT hr = e.m_hr; // just catch it
            }
        }
    }
    

    If the render target must fit the view window, then the place to call _Resize2DRenderTarget is the WM_SIZE mesage handler.

  8. Create the Direct2D bitmap from WIC bitmap.
    bool CDemoImageViewerView::_CreateDirect2DBitmapFromWICBitmap()
    {
        bool bRet = false;
        try
        {
            // reset the Direct2D bitmap
            m_pD2DBitmap.reset();
    
            // get WIC bitmap source from document class
            CDemoImageViewerDoc* pDoc = GetDocument();
            std::shared_ptr pWICBitmapSource = pDoc->GetWICBitmapSource();
    
            if(nullptr != pWICBitmapSource)
            {
                // get Direct2D factory
                CDemoImageViewerApp* pApp = static_cast<CDemoImageViewerApp*>(AfxGetApp());
                std::shared_ptr<ns::d2d1::Factory> pDirect2DFactory = pApp->GetDirect2DFactory();
    
                // create Direct2D bitmap from WIC bitmap 
                std::shared_ptr<ns::wic::FormatConverter> pWICBitmapSource = pDoc->GetWICBitmapSource();
                m_pD2DBitmap = m_pD2DRenderTarget->CreateBitmapFromWicBitmap(pWICBitmapSource);
    
                bRet = true; // success
            }
        }
        catch(CAtlException& e)
        {
            CString strError; // show what's going wrong
            strError.Format(_T("Create Direct2D bitmap from WIC bitmap failed.")
                _T("\nError code: 0x%08X"), e.m_hr);
            AfxMessageBox(strError);
        }
        return bRet;
    }
    

    In our case, the best place to call _CreateDirect2DBitmapFromWICBitmap is the overridden CView::OnUpdate function.

  9. Finally, render the bitmap.
    void CDemoImageViewerView::_RenderD2DBitmap(CDC* pDC)
    {
        try
        {
            m_pD2DRenderTarget->BeginDraw();
    
            // clear target background using white color
            ns::d2d1::COLOR_F color = {1.f, 1.f, 1.f, 1.f}; // r, g, b, a
            m_pD2DRenderTarget->Clear(color);
    
            if(nullptr != m_pD2DBitmap)
            {
                ns::d2d1::SIZE_F sizeBitmap = m_pD2DBitmap->GetSize();
    
                CPoint point = this->GetScrollPosition();
                m_pD2DRenderTarget->SetTranslationTransform((float)-point.x, (float)-point.y);
                ns::d2d1::RECT_F rcTarget = {0, 0, sizeBitmap.width, sizeBitmap.height};
                m_pD2DRenderTarget->DrawBitmap(rcTarget, m_pD2DBitmap);
            }
    
            m_pD2DRenderTarget->EndDraw();
        }
        catch(CAtlException& e)
        {
            CRect rcClip;
            pDC->GetClipBox(rcClip);
            pDC->FillSolidRect(rcClip, RGB(255, 255, 255));
            CString strError; // display what's going wrong
            strError.Format(_T("Drawing bitmap failed. Error code: 0x%08X"), e.m_hr);
            pDC->TextOut(10, 10, strError);
        }
    }
    

    Of course, in a class derived from CView, the place for calling _RenderD2DBitmap is the overridden CView::OnDraw virtual function.

  10. Remains just few little things to ajust: handle WM_ERASEBKGND and override CScrollView::OnScrollBy. Anyway, you can find all the implementation details in the attached demo application.

Demo application

The demo application is a basic image file viewer that uses WIC Wrapper Library.
Download: WIC Wrapper Library v2_0 and Image Viewer Demo Project.zip (2275 downloads) (full WIC Wrapper Library source code is included).

Demo Image Viewer
Demo Image Viewer

Requirements

Resources, related articles, examples and projects

3 thoughts on “Using Direct2D for Rendering WIC Bitmap Sources”

  1. I tried the demo project .. the WICWrapperLibrary compiles ok .. but the DemoImageViewer gives a ton of com errors like:

    wic wrapper library v2.0\_interface\wic\wic_bitmapsource.h(44): error C2664: ‘ns::com::Base::Base(IUnknown *)’ : cannot convert parameter 1 from ‘ns::wic::IWICBitmapSource *’ to ‘IUnknown *’

    What wrong with the solution ? I’m calling out SDK 7.1 in the Platform Toolkit

    Reply
      • your right .. at work it builds fine, I don’t know whats wrong with my Win7 64bit at home, Work is Win10 64bit. The reason I apparently have to use WIC is I need to read/write jpeg metadata (i was storing info in the comment using LeadTools now I want to go 64bit and get rid of LeadTools) GDI+ doesn’t seem to be able to write an 8bit Greyscale JPEG. I’ve got it all to work with WIC now, but this new CD2DBitmap could be very useful.

        Thanks for responding
        Gary

        Reply

Leave a Comment