Quantcast
Viewing all 51 articles
Browse latest View live

MFC Support for DirectWrite (Part 4) – Inline Images

DirectWrite allows inserting objects in a block of text, e.g. images, as shown in the picture.

Image may be NSFW.
Clik here to view.
MFC Support for DirectWrite – Inline Images

MFC Support for DirectWrite (Part 4) – Inline Images

Let’s see how can be done!

Implement IDWriteInlineObject interface methods to insert inline images

Once we have created a CD2DTextLayout object, remains the following to do:

  1. Define a class for implementing IDWriteInlineObject interface, e.g. CInlineImage. We can derive it from IDWriteInlineObject then override and implement IDWriteInlineObject and IUnknown methods. However, if we already are using MFC, it’s a little bit handier to derive from CCmdTarget, then use MFC’s interface maps DECLARE_INTERFACE_MAP, BEGIN_INTERFACE_PART and so on.
    class CInlineImage : public CCmdTarget
    {
    public:
        CInlineImage();
        virtual ~CInlineImage();
        HRESULT Create(CHwndRenderTarget* pRenderTarget, UINT nResID, LPCTSTR pszResType);
        HRESULT Create(CHwndRenderTarget* pRenderTarget, LPCTSTR pszFileName);
        BOOL IsValid();
        operator IDWriteInlineObject*();
    
    public:
        DECLARE_INTERFACE_MAP()
        BEGIN_INTERFACE_PART(InlineImage, IDWriteInlineObject)
            STDMETHOD(Draw)(void*, IDWriteTextRenderer*, FLOAT, FLOAT, BOOL, BOOL, IUnknown*);
            STDMETHOD(GetMetrics)(DWRITE_INLINE_OBJECT_METRICS*);
            STDMETHOD(GetOverhangMetrics)(DWRITE_OVERHANG_METRICS*);
            STDMETHOD(GetBreakConditions)(DWRITE_BREAK_CONDITION*, DWRITE_BREAK_CONDITION*);
            CHwndRenderTarget* m_pRenderTarget;
            CD2DBitmap* m_pBitmap;
        END_INTERFACE_PART(InlineImage)
    };

    Detailed implementation can be found in the demo application attached to this article. Here is listed just an implementation example of overridden IDWriteInlineObject::Draw.
    STDMETHODIMP CInlineImage::XInlineImage::Draw(void* clientDrawingContext, 
        IDWriteTextRenderer* textRenderer, FLOAT fOriginX, FLOAT fOriginY,
        BOOL bIsSideways, BOOL bIsRightToLeft, IUnknown* clientDrawingEffect)
    {
        METHOD_PROLOGUE(CInlineImage, InlineImage);
        ASSERT_VALID(m_pRenderTarget);
        ASSERT_VALID(m_pBitmap);
    
        if(m_pRenderTarget->IsValid() && m_pBitmap->IsValid())
        {
            CD2DSizeF sizeBitmap = m_pBitmap->GetSize();
            m_pRenderTarget->DrawBitmap(m_pBitmap, 
                CD2DRectF(fOriginX, fOriginY, 
                    fOriginX + sizeBitmap.width, fOriginY + sizeBitmap.height));
        }
        return S_OK;
    }
  2. Create and initialize a CInlineImage object.
    class CDirectWriteDemoView : public CView
    {
        // ...
        CInlineImage m_inlineImage;
        // ...
    };

    int CDirectWriteDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        // ...
        // Enable D2D support for this window
        EnableD2DSupport();
    
        CHwndRenderTarget* pRenderTarget = GetRenderTarget();
    
        // create inline image objects
        HRESULT hr = m_inlineImageRings.Create(pRenderTarget, IDB_RINGS, T("PNG"));
        ASSERT(SUCCEEDED(hr));
        // ...
    }
  3. Pass the CInlineImage object to IDWriteTextLayout::SetInlineObject before drawing the text layout.
    LRESULT CDirectWriteDemoView::OnDraw2D(WPARAM wParam, LPARAM lParam)
    {
        CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;
        // ...
    
        // init the inline objects
        _InitInlineObjects(pRenderTarget, spTextLayout);
    
        // Draw the text in the render target.
        pRenderTarget->DrawTextLayout(CD2DPointF(),         // upper-left corner of the text 
                                      spTextLayout.get(),   // text layout object
                                      &CD2DSolidColorBrush  // brush used for text
                                      (pRenderTarget, 
                                      D2D1::ColorF(D2D1::ColorF::DarkRed)));
    }

    void CDirectWriteDemoView::_InitInlineObjects(CHwndRenderTarget* pRenderTarget, 
        const std::shared_ptr<CD2DTextLayout> spTextLayout)
    {
        // ...
        CDirectWriteDemoDoc* pDoc = _GetDocument();
    
        DWRITE_TEXT_RANGE textRange;
    
        if(pDoc->GetTextRange(_T("[IMAGE:RINGS]"), textRange) && m_inlineImageRings.IsValid())
        {
            spTextLayout->Get()->SetInlineObject(m_inlineImageRings, textRange);
        }
        // ...
    }

Demo application

Download: MFC Support for DirectWrite Demo (Part 4).zip (13)

Notes

  • CInlineImage class implementation is intentionally left as simple as possible, for learning purpose. Of course, it can be improved, for example, by scaling images to fit the text height.
  • Pictures used in the demo application are taken from http://www.icondrawer.com/gift-icons.php

Resources and related articles


MFC Support for DirectWrite (Part 5) – Typography

Some fonts like Microsoft’s Gabriola support a number of typographic features which allow users to control how they look, e.g by enabling some fancy stylistic text rendering. With DirectWrite we can do that by passing a IDWriteTypography object to IDWriteTextLayout::SetTypography method.

Set text layout typography with DirectWrite in an MFC application

We have not an MFC class which wraps IDWriteTypography but that’s not a problem. First, we can get a pointer to IDWriteFactory from a global AFX_GLOBAL_DATA structure.  Further create an IDWriteTypography instance, add desired font feature(s) and finally, set the typography in a range of the text layout. Here is an example:

void CDirectWriteDemoView::_SetTextLayoutFontTypographicFeature(
    std::shared_ptr<CD2DTextLayout> spTextLayout, 
    DWRITE_FONT_FEATURE_TAG featureTag, DWRITE_TEXT_RANGE textRange)
{
    // get the global IDWriteFactory pointer
    IDWriteFactory*  pIDWriteFactory = afxGlobalData.GetWriteFactory();

    // create IDWriteTypography instance
    CComPtr<IDWriteTypography> spIDWriteTypography;
    HRESULT hr = pIDWriteFactory->CreateTypography(&spIDWriteTypography);
    ASSERT(SUCCEEDED(hr));

    // add font feature
    DWRITE_FONT_FEATURE fontFeature;
    fontFeature.nameTag = featureTag;
    fontFeature.parameter = 1;
    spIDWriteTypography->AddFontFeature(fontFeature);

    // set the typography in a range of the text layout
    spTextLayout->Get()->SetTypography(spIDWriteTypography, textRange);
}

Demo application

Download: MFC Support for DirectWrite Demo (Part 5).zip (3)

Image may be NSFW.
Clik here to view.
MFC Support for DirectWrite (Part 5) – Typography

MFC Support for DirectWrite (Part 5) – Typography

Resources and related articles

MFC Support for DirectWrite (Part 6) – Effects

We can change the color of a text range by passing a brush object to IDWriteTextLayout::SetDrawingEffect. Here is a simple example.

An example of using IDWriteTextLayout::SetDrawingEffect

LRESULT CDirectWriteDemoView::OnDraw2D(WPARAM wParam, LPARAM lParam)
{
   CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;

   // Please, see the demo application for more details.
   // ...

   // init color effects 
   _InitColorEffects(pRenderTarget, spTextLayout);

   // Draw the text in the render target.
   pRenderTarget->DrawTextLayout(CD2DPointF(),         // upper-left corner of the text 
                                 spTextLayout.get(),   // text layout object
                                 &CD2DSolidColorBrush  // default brush used for text
                                 (pRenderTarget, 
                                 D2D1::ColorF(D2D1::ColorF::Black))); 
   return 0;
}

void CDirectWriteDemoView::_InitColorEffects(CHwndRenderTarget* pRenderTarget, 
                                   std::shared_ptr<CD2DTextLayout> spTextLayout)
{
   CDirectWriteDemoDoc* pDoc = _GetDocument();
   DWRITE_TEXT_RANGE textRange;

   if(pDoc->FindTextRange(_T("BLUE"), textRange))
       _SetTextLayoutDrawingEffect(pRenderTarget, spTextLayout, 
                                   D2D1::ColorF(D2D1::ColorF::Blue), textRange);

   if(pDoc->FindTextRange(_T("YELLOW"), textRange))
       _SetTextLayoutDrawingEffect(pRenderTarget, spTextLayout,
                                   D2D1::ColorF(D2D1::ColorF::Yellow), textRange);

   if(pDoc->FindTextRange(_T("RED"), textRange))
       _SetTextLayoutDrawingEffect(pRenderTarget, spTextLayout,
                                   D2D1::ColorF(D2D1::ColorF::Red), textRange);
}

void CDirectWriteDemoView::_SetTextLayoutDrawingEffect(CHwndRenderTarget* pRenderTarget, 
            std::shared_ptr<CD2DTextLayout> spTextLpayout,
            D2D1_COLOR_F color, DWRITE_TEXT_RANGE textRange)
{
    CD2DSolidColorBrush* pBrush = new CD2DSolidColorBrush(pRenderTarget, color);
    pBrush->Create(pRenderTarget);
    spTextLayout->Get()->SetDrawingEffect(static_cast<IUnknown*>(pBrush->Get()), textRange);
}

So far, there’s no much “special effects” in the above example. Although a little bit more difficult, it is possible to make more customized rendering like text highlighting, double/triple underline/strikethrough and so on. This will be the subject of a future article.

Demo application

Download: MFC Support for DirectWrite Demo - Part 6.zip (50)

Image may be NSFW.
Clik here to view.
MFC Support for DirectWrite (Part 6) – Effects

MFC Support for DirectWrite (Part 6) – Effects

Resources and related articles

Codexpert – 2015 Articles Summary

Microsoft Libraries and C++ Programming Language

What’s next?

We planned to continue the Direct2D/DirectWrite series but also other articles about programming with C++ using Microsoft Windows libraries like MFC, ATL, Windows API and so on.

See also

Easy PNG Resource Loading with MFC

If ask Internet about how to “load PNG from resource” it gives in return a lot of examples using a bunch of code. We can use some stuff found there, but once using MFC and enabling Direct2D support, that becomes easier: just have to construct and create a CD2DBitmap object, passing the resource ID and the resource type.
Here is an example:

Create a CD2DBitmap object from a PNG resource

HRESULT CSomeWindow::LoadDemoImage(CHwndRenderTarget* pRenderTarget)
{
    // Load Direct2D bitmap from "PNG" resource
    m_pBitmamLogo = new CD2DBitmap(pRenderTarget,
        IDB_PNG_DEMO,                             // resource ID
        _T("PNG"));                               // resource type

    return m_pBitmamLogo->Create(pRenderTarget);
}

It’s also possible to easily load a Direct2D brush.

Create a CD2DBitmapBrush object from a PNG resource

HRESULT CSomeWindow::LoadDemoBrush(CHwndRenderTarget* pRenderTarget)
{
    // Load Direct2D brush from "PNG" resource
    m_pBrushBackground = new CD2DBitmapBrush(pRenderTarget,
        IDB_PNG_DEMO,                                       // resource ID
        _T("PNG"),                                          // resource type
        CD2DSizeU(0, 0),
        &D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, 
        D2D1_EXTEND_MODE_WRAP));

    return m_pBrushBackground->Create(pRenderTarget);
}

Notes

  • Loading PNG is just an example. We can load other WIC-supported formats (includes “standard” formats like JPEG, TIFF etc. and other graphic formats which have a WIC decoder installed).
  • To see how to enable Direct2D support and further render the loaded images, see the previous articles and/or have a look in the demo application attached here.

Demo application

Download: PNG Resource Loading Demo.zip (52)

References and related articles

MFC Static Control Using Direct2D and DirectWrite – version 1.0

While doing some refactoring on sample source code presented in MFC Support for DirectWrite series, I had the idea to make a control easy to be used for rendering text. It could be a custom MFC control derived from generic CWnd class or an ActiveX but finally I chose to derive from CStatic, just for simplicity.
By short, it consist in two main classes:

  • CDirect2DStatic extends MFC class CStatic, doing the following:
    • enables Direct2D MFC support;
    • handles AFX_WM_DRAW2D and AFX_WM_RECREATED2DRESOURCES registered messages, which are sent by the MFC framework to a window with Direct2D MFC support enabled.
  • CDirectWriteStaticCtrl, derived from CDirect2DStatic, effectively implements text formatting and rendering; it has the following groups of public methods:
    • operations: setting text and background brush;
    • text block format: set the format for the entire text;
    • text range format: set the format in a given range;
    • data access.

Notes

  • Implementation details can be found in the demo application, attached here for download.
  • So far, it doesn’t yet implement inline images, typography and effects but these will be done in a future version along with other Direct2D/DirectWrite features.

Demo application

Download: DirectWrite Static Control.zip (21)

Image may be NSFW.
Clik here to view.
DirectWrite Static Control

DirectWrite Static Control

 

Resources

See also

MFC Static Control Using Direct2D and DirectWrite (updated)

I have update the MFC static control presented in the previous articles by adding methods for setting text range color, typography and inline images.

Code samples

Setting text range color

void CDemoDlg::_DoDemo_Effects()
{
    m_staticDemo.Clear();

    const CString strTagRed = _T("RED");
    const CString strTagGreen = _T("GREEN");
    const CString strTagBlue = _T("BLUE");

    CString strText;
    strText.Format(_T("IDWriteTextLayout::SetDrawingEffect can change the color")
        _T(" of a text range as for example: \n\t%s\n\t%s\n\t%s"),
        strTagRed, strTagGreen, strTagBlue);

    m_staticDemo.SetText(strText);

    // set text range colors
    m_staticDemo.SetTextColor(strText.Find(strTagRed), strTagRed.GetLength(), RGB(255,0,0));
    m_staticDemo.SetTextColor(strText.Find(strTagGreen), strTagGreen.GetLength(), RGB(0, 255, 0));
    m_staticDemo.SetTextColor(strText.Find(strTagBlue), strTagBlue.GetLength(), RGB(0, 0, 255));
}

Setting text range typography

void CDemoDlg::_DoDemo_Typography()
{
    m_staticDemo.Clear();

    const CString strTagFontTypography = _T("Fancy Typography Rendering");

    CString strText;
    strText.Format(_T("Some OpenType fonts (e.g. Microsoft’s Gabriola) support %s"), 
        strTagFontTypography);

    m_staticDemo.SetText(strText);
    const UINT32 nStartPos = strText.Find(strTagFontTypography);
    const UINT32 nLength = strTagFontTypography.GetLength();

    // set an OpenType font which supports typography features
    m_staticDemo.SetFontFamilyName(nStartPos, nLength, _T("Gabriola"));
    // set feature
    m_staticDemo.SetFontTypography(nStartPos, nLength, DWRITE_FONT_FEATURE_TAG_STYLISTIC_SET_7);
}

Setting inline images

void CDemoDlg::_DoDemo_InlineImages()
{
    m_staticDemo.Clear();

    const CString strTagImageRings = _T("[IMAGE:RINGS]");
    const CString strTagImageRose = _T("[IMAGE:ROSE]");
    const CString strTagImageDog = _T("[IMAGE:DOG]");

    CString strText;
    strText.Format(_T("Here are two rings %s a beautiful rose %s  and a little cute dog %s"),
        strTagImageRings, strTagImageRose, strTagImageDog);

    m_staticDemo.SetText(strText);

    // set inline images
    m_staticDemo.SetInlineImage(strText.Find(strTagImageRings), strTagImageRings.GetLength(), 
        _T("PNG"), IDB_PNG_RINGS);
    m_staticDemo.SetInlineImage(strText.Find(strTagImageRose), strTagImageRose.GetLength(), 
        _T("PNG"), IDB_PNG_ROSE);
    m_staticDemo.SetInlineImage(strText.Find(strTagImageDog), strTagImageDog.GetLength(), 
        _T("PNG"), IDB_PNG_DOG);
}

Demo application

Download: DirectWrite Static Control (updated).zip (11)

Image may be NSFW.
Clik here to view.
MFC DirectWrite Static Control

MFC DirectWrite Static Control

Resources

See also

MFC Support for DirectWrite – Part 7: A Step to Custom Rendering

The previous articles from this series show how to format the text layout using built-in DirectWrite methods. However, as said earlier, we can do more custom formatting (e.g. draw double/triple underline/strikethrough, highlight text and so on). How can be done? First, let’s note that ID2D1RenderTarget::DrawTextLayout internally calls IDWriteTextLayout::Draw which has the following prototype:

HRESULT Draw(
   void*                clientDrawingContext,
   IDWriteTextRenderer* renderer,
   FLOAT                originX,
   FLOAT                originY)

We can write our own implementation of IDWriteTextRenderer interface providing custom text rendering then directly call IDWriteTextLayout::Draw instead of CRenderTarget::DrawTextLayout.

Code samples

class CCustomTextRenderer : public CCmdTarget
{
	DECLARE_DYNAMIC(CCustomTextRenderer)
public:
	CCustomTextRenderer() = default;
	virtual ~CCustomTextRenderer() = default;
    IDWriteTextRenderer* Get();
public:
    DECLARE_INTERFACE_MAP()
    BEGIN_INTERFACE_PART(CustomTextRenderer, IDWriteTextRenderer)
        // override IDWriteTextRenderer methods
        STDMETHOD(DrawGlyphRun)(void*, FLOAT, FLOAT, DWRITE_MEASURING_MODE, const DWRITE_GLYPH_RUN*, 
                                const DWRITE_GLYPH_RUN_DESCRIPTION*, IUnknown*);
        STDMETHOD(DrawInlineObject)(void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*);
        STDMETHOD(DrawStrikethrough)(void*, FLOAT, FLOAT, const DWRITE_STRIKETHROUGH*, IUnknown*);
        STDMETHOD(DrawUnderline)(void*, FLOAT, FLOAT, const DWRITE_UNDERLINE*, IUnknown*);
        // override IDWritePixelSnapping methods
        STDMETHOD(GetCurrentTransform)(void*, DWRITE_MATRIX*);
        STDMETHOD(GetPixelsPerDip)(void*, FLOAT*);
        STDMETHOD(IsPixelSnappingDisabled)(void*, BOOL*);
        // implementation helpers
        void _FillRectangle(void*, IUnknown*, FLOAT, FLOAT, FLOAT, FLOAT, 
                            DWRITE_READING_DIRECTION, DWRITE_FLOW_DIRECTION);
    END_INTERFACE_PART(CustomTextRenderer)
};

BEGIN_INTERFACE_MAP(CCustomTextRenderer, CCmdTarget)
    INTERFACE_PART(CCustomTextRenderer, __uuidof(IDWriteTextRenderer), CustomTextRenderer)
END_INTERFACE_MAP()

STDMETHODIMP CCustomTextRenderer::XCustomTextRenderer::DrawGlyphRun(void* pClientDrawingContext, 
    FLOAT fBaselineOriginX, FLOAT fBaselineOriginY, DWRITE_MEASURING_MODE measuringMode,
    const DWRITE_GLYPH_RUN* pGlyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION* pGlyphRunDescription, 
    IUnknown* pClientDrawingEffect)
{
    // NOTE: This does the same as the default implementation.
    // In a future version will be modified in order to perform some custom rendering.

    CDrawingContext* pDrawingContext = static_cast<CDrawingContext*>(pClientDrawingContext);
    ASSERT_VALID(pDrawingContext);

    ID2D1Brush* pBrush = pDrawingContext->GetDefaultBrush()->Get();
    if(NULL != pClientDrawingEffect)
    {
        pBrush = static_cast<ID2D1Brush*>(pClientDrawingEffect);
    }

    CRenderTarget* pRenderTarget = pDrawingContext->GetRenderTarget();
    ASSERT_VALID(pRenderTarget);
    pRenderTarget->GetRenderTarget()->DrawGlyphRun(CD2DPointF(fBaselineOriginX, fBaselineOriginY), 
        pGlyphRun, pBrush, measuringMode);

    return S_OK;
}
// ...
// Please, find the other methods implementation in the attached demo project!
// ...

void CDirectWriteStaticCtrl::_DrawTextLayout(CHwndRenderTarget* pRenderTarget)
{
    CD2DTextFormat textFormat(pRenderTarget,
        m_strFontFamilyName, m_fFontSize, m_eFontWeight, m_eFontStyle, m_eFontStretch);

    textFormat.Get()->SetTextAlignment(m_eTextAlignment);
    textFormat.Get()->SetWordWrapping(m_eWordWrapping);
    textFormat.Get()->SetParagraphAlignment(m_eParagraphAlignment);

    CD2DSizeF sizeTarget = pRenderTarget->GetSize();
    CD2DSizeF sizeDraw(sizeTarget.width - 2 * m_fMargin, sizeTarget.height - 2 * m_fMargin);
    CD2DTextLayout textLayout(pRenderTarget, m_strText, textFormat, sizeDraw);

    POSITION pos = m_listTextRangeFormat.GetHeadPosition();
    while (NULL != pos)
        m_listTextRangeFormat.GetNext(pos)->Apply(textLayout);

    CD2DSolidColorBrush* pDefaultBrush = new CD2DSolidColorBrush(pRenderTarget, m_crTextColor);
    pDefaultBrush->Create(pRenderTarget);
    CDrawingContext* pDrawingContext = new CDrawingContext(pRenderTarget, pDefaultBrush);

    // call IDWriteTextLayout::Draw passing custom IDWriteTextRenderer 
    textLayout.Get()->Draw(pDrawingContext, m_textRenderer.Get(), m_fMargin, m_fMargin);
}

Demo project

So far, it contains an implementation of IDWriteTextRenderer which does the same as the default one. In a further article I will update it in order to perform custom rendering.
Download: MFC Support for DirectWrite Demo (Part 7).zip (6)

Notes

  • You can find an excellent related article on Petzold Book Blog; see the link below.

Resources and related articles


Visual Studio 2015: How to Step into MFC Framework Code

Let’s say we have installed Visual Studio 2015 and  have started an MFC application in DEBUG mode. Now, attempting to step into an MFC Framework function, e.g. CWinApp::InitInstance, the debugger simply steps over.

Image may be NSFW.
Clik here to view.
Step into MFC Framework failed

Step into MFC Framework failed

Having a look into Output window, we may discover a message like this: “…’C:\Windows\System32\mfc140ud.dll’. Cannot find or open the PDB file”. That’s clear, the necessary PDB file is missing. What can we do? We can buy one from Microsoft or get a free copy from Torrents site. Image may be NSFW.
Clik here to view.
:)
Well, don’t need to do that, I was just joking. We can easily get it from Microsoft Symbol Servers, using Visual Studio. Here are the steps:

Changing Debugging/Symbols options in Visual Studio

  1. Open the Options dialog (choose Tools/Options… menu item).
  2. Expand the tree from the left pane to Debugging/Symbols.

    Image may be NSFW.
    Clik here to view.
    Default debugging symbols options

    Default debugging symbols options

  3. Under Symbol file (.pdb) locations, check Microsoft Symbol Servers. In this moment a message box appears; it suggest that we can choose to get only the debugging symbols for modules which we want, e.g. mfc140ud.dll

  4. …so let’s check Only specified modules, then click on Specify modules link and add mfc140ud.dll to the list.

    Image may be NSFW.
    Clik here to view.
    Symbols to load automatically

    Symbols to load automatically

  5. Type or browse for a folder for caching the symbols, e.g. C:\Symbols . Here is how the debugging symbols options finally looks.

  6. Hit OK to close the Options dialog.

Notes

  • The same steps can be applied for Visual Studio 2013, except that it needs the symbols for mfc120ud.dll.
  • First time the PDB file is needed it may take a pretty long time for downloading. However, next times it is taken from the cache folder, so symbol loading time becomes irrelevant.
  • If have Visual Studio 2015 with at least Update 1, still may not step into the MFC code, even the symbols has been successfully loaded. For this issue see the next topic.

Changing Linker/Debugging project properties

The Update 1 for Visual Studio 2015 comes with /DEBUG:FASTLINK liker option. That’s pretty cool for improving link times but unfortunately, if it’s set, it makes not possible stepping into the MFC Framework code, although the necessary symbols has been loaded. So let’s change it, following these steps:

  1. Open the project’s Property Pages.
  2. Choose Debug configuration and All Platforms.
  3. Expand the tree from left pane to Configuration Properties/Linker/Debugging.
  4. Change Generate Debug Info option from Optimize for faster linking, (/DEBUG:FASTLINK) to Optimize for debugging (/DEBUG).

  5. Hit OK to close project’s Property Pages.

Notes

  • The above option can also be changed for all projects in the Property Manager window.

Resources and related articles

Getting Direct2D, DirectWrite and WIC Factories in MFC

MFC library offers a series of wrapper classes over Direct2D and DirectWrite interfaces (see CRenderTarget and CD2D classes). That’s pretty cool because allows to easily load and render images and draw texts, without care too much of direct dealing with COM interfaces.
However, let’s say we have to implemented something different so we need to start creating Direct2D or DirectWrite factories. Of course, we can call D2D1CreateFactory or DWriteCreateFactory functions, but a little bit handier is to use the factories instances created and kept by the MFC framework.

Getting Direct2D, DirectWrite and WIC factories in Visual Studio 2010

In MFC framework shipped with Visual Studio 2010, the instances of ID2D1Factory and IDWriteFactory are created and kept in a global AFX_GLOBAL_DATA structure named afxGlobalData. So, we have to do something like this:

ID2D1Factory* pDirect2dFactory = afxGlobalData.GetDirect2dFactory();
    IDWriteFactory* pDirectWriteFactory = afxGlobalData.GetWriteFactory();
    IWICImagingFactory* pImagingFactory = afxGlobalData.GetWICFactory();
    // ...

Getting Direct2D, DirectWrite and WIC factories in Visual Studio 2012 – 2015

In Visual Studio 2012, these factory instances have been moved in a D2D-specific structure of type _AFX_D2D_STATE that can be accessed by calling AfxGetD2DState.

_AFX_D2D_STATE* pD2DState = AfxGetD2DState();

    ID2D1Factory* pDirect2dFactory = pD2DState->GetDirect2dFactory();
    IDWriteFactory* pDirectWriteFactory = pD2DState->GetWriteFactory();
    IWICImagingFactory* pImagingFactory = pD2DState->GetWICFactory();
    // ...

Now, we can note that MFC framework keeps also an instance of IWICImagingFactory which is internally used by CD2D classes. That’s also pretty cool; we’ll use it in the following example.

An example of getting and using IWICImagingFactory

Here is a brief example of getting IWICImagingFactory to further read metadata stored in JPEG, TIFF and other image formats.

void CWhateverMFCClass::ListImageFileMetadata(LPCWSTR wzFilePath)
{
    // get IWICImagingFactory from global _AFX_D2D_STATE structure
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    IWICImagingFactory* pImagingFactory = pD2DState->GetWICFactory();
    ATLASSERT(pImagingFactory);

    try
    {
        // create bitmap decoder based on the given file
        CComPtr<IWICBitmapDecoder> spBitmapDecoder;
        HRESULT hr = pImagingFactory->CreateDecoderFromFilename(wzFilePath, NULL,
            GENERIC_READ, WICDecodeMetadataCacheOnLoad, &spBitmapDecoder);
        ATLENSURE_SUCCEEDED(hr);

        // get first image frame
        CComPtr<IWICBitmapFrameDecode> spBitmapFrameDecode;
        hr = spBitmapDecoder->GetFrame(0, &spBitmapFrameDecode);
        ATLENSURE_SUCCEEDED(hr);

        // get metadata reader
        CComPtr<IWICMetadataQueryReader> spMetadataQueryReader;
        hr = spBitmapFrameDecode->GetMetadataQueryReader(&spMetadataQueryReader);
        ATLENSURE_SUCCEEDED(hr);

        // further, just a little sweat to read metadata :)
        // ...
    }
    catch (CException* e)
    {
        e->ReportError();
        e->Delete();
    }
}

References and related articles

Read IFD and EXIF Metadata with WIC and MFC

A graphic file can store additional information abaout image (camera model, camera manufacturer, date and time, etc). Windows Imaging Component (WIC) provides interfaces which allow dealing with image metadata. Basically, for reading metadata we need an IWICMetadataQueryReader instance to call its GetMetadataByName method. The first argument of GetMetadataByName is based in Metadata Query Language and is composed by a path in metadata tree and an item name/identifier or a block name/identifier (a block contains itself items and/or other blocks). For more details, please see Metadata Query Language Overview in MSDN library. The second argument is a PROPVARIANT structure that gets the value. If returned value is of type VT_UNKNOWN then we’ve got a block, otherwise we’ve got an item value.

Among other metadata blocks defined in TIFF standard, which are are common to other graphic file formats (e.g. JPEG and raw image file formats like NEF) there are IFD (Image File Directory) and EXIF metadata blocks. I’ll not provide more details here (they can be found in MSDN or in other documents and articles). Just to note that EXIF is nested into IFD block and, while IFD block is placed in the metadata root for TIFF, it is nested/embedded into APP1 block for JPEG file format. For example, to get the ISOSpeed EXIF value, we can call IWICMetadataQueryReader::GetMetadataByName passing “/ifd/exif/{ushort=34867}” in case of TIFF or “/app1/ifd/exif/{ushort=34867}” for JPEG format.

Reading IFD and EXIF metadata from a JPEG file

Here is a brief example:

void CWhateverMFCClass::ReadJPEGFileMetadata(LPCWSTR wzFilePath)
{
    // get IWICImagingFactory from global _AFX_D2D_STATE structure
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    IWICImagingFactory* pImagingFactory = pD2DState->GetWICFactory();
    ATLASSERT(pImagingFactory);

    try
    {
        // create bitmap decoder based on the given file
        CComPtr<IWICBitmapDecoder> spBitmapDecoder;
        HRESULT hr = pImagingFactory->CreateDecoderFromFilename(wzFilePath, NULL,
            GENERIC_READ, WICDecodeMetadataCacheOnDemand, &spBitmapDecoder);
        ATLENSURE_SUCCEEDED(hr);

        // get first image frame
        CComPtr<IWICBitmapFrameDecode> spBitmapFrameDecode;
        hr = spBitmapDecoder->GetFrame(0, &spBitmapFrameDecode);
        ATLENSURE_SUCCEEDED(hr);

        // get root metadata reader
        CComPtr<IWICMetadataQueryReader> spRootQueryReader;
        hr = spBitmapFrameDecode->GetMetadataQueryReader(&spRootQueryReader);
        ATLENSURE_SUCCEEDED(hr);

        PROPVARIANT value;
        ::PropVariantInit(&value);

        // get camera model
        CString strModel;
        hr = spRootQueryReader->GetMetadataByName(L"/app1/ifd/{ushort=272}", &value);
        if (SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strModel = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // get the date and time when the image was digitized
        CString strDateTimeDigitized;
        hr = spRootQueryReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=36868}", &value);
        if(SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strDateTimeDigitized = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // and so on, and so on...
    }
    catch (COleException* e)
    {
        e->ReportError();
        e->Delete();
    }
}

As can be seen, this example assumes that we have a JPEG file and uses the root metadata query reader by passing the item full path.
Let’s improve it in order to read metadata also from TIFF, NEF and other formats. Let’s also get and use the embedded metadata readers both for IFD and EXIF blocks.

Reading IFD and EXIF metadata from an image file

First, we need a functions which finds out if we are dealing with JPEG or other format.

BOOL CWhateverMFCClass::IsJPEGFileFormat(CComPtr<IWICBitmapDecoder> spBitmapDecoder)
{
    GUID guidFormat = {0};
    HRESULT hr = spBitmapDecoder->GetContainerFormat(&guidFormat);
    ATLENSURE_SUCCEEDED(hr);

    return IsEqualGUID(guidFormat, GUID_ContainerFormatJpeg);
}

Next, we can write a function that gets the IFD query reader.

HRESULT CWhateverMFCClass::GetIfdQueryReader(
    CComPtr<IWICBitmapDecoder> spBitmapDecoder,
    CComPtr<IWICMetadataQueryReader> spRootQueryReader,
    CComPtr<IWICMetadataQueryReader>& spQueryReader)
{
    LPWSTR wszIFDPath = L"/ifd";
    if (IsJPEGFileFormat(spBitmapDecoder))
        wszIFDPath = L"/app1/ifd";

    PROPVARIANT value;
    ::PropVariantInit(&value);
    HRESULT hr = spRootQueryReader->GetMetadataByName(wszIFDPath, &value);
    if (FAILED(hr))
        return hr;
    else if (value.vt != VT_UNKNOWN)
        return E_FAIL;

    return value.punkVal->QueryInterface(IID_IWICMetadataQueryReader, (void**)&spQueryReader);
}

Also we need a function to get the embedded EXIF query reader. Here we can write a generic one, which takes the parent query reader and the block name.

HRESULT CWhateverMFCClass::GetEmbeddedQueryReader(CComPtr<IWICMetadataQueryReader> spParentQueryReader,
    LPCWSTR wszBlockName,
    CComPtr<IWICMetadataQueryReader>& spQueryReader)
{
    PROPVARIANT value;
    ::PropVariantInit(&value);
    HRESULT hr = spParentQueryReader->GetMetadataByName(wszBlockName, &value);
    if (FAILED(hr))
        return hr;
    else if (value.vt != VT_UNKNOWN)
        return E_FAIL;

    return value.punkVal->QueryInterface(IID_IWICMetadataQueryReader, (void**)&spQueryReader);
}

Let’a also write a function that gets the root query reader.

HRESULT CWhateverMFCClass::GetRootQueryReader(CComPtr<IWICBitmapDecoder> spBitmapDecoder,
    CComPtr<IWICMetadataQueryReader>& spRootQueryReader)
{
    // get first image frame
    CComPtr<IWICBitmapFrameDecode> spBitmapFrameDecode;
    HRESULT hr = spBitmapDecoder->GetFrame(0, &spBitmapFrameDecode);
    if (FAILED(hr))
        return hr;

    return spBitmapFrameDecode->GetMetadataQueryReader(&spRootQueryReader);
}

Now, the function which reads the image IFD and EXIF metadata may look like this:

void ReadImageFileMetadata(LPCWSTR wzFilePath)
{
    // get IWICImagingFactory from global _AFX_D2D_STATE structure
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    IWICImagingFactory* pImagingFactory = pD2DState->GetWICFactory();
    ATLASSERT(pImagingFactory);

    try
    {
        // create bitmap decoder based on the given file
        CComPtr<IWICBitmapDecoder> spBitmapDecoder;
        HRESULT hr = pImagingFactory->CreateDecoderFromFilename(wzFilePath, NULL,
            GENERIC_READ, WICDecodeMetadataCacheOnDemand, &spBitmapDecoder);
        ATLENSURE_SUCCEEDED(hr);

        // get root metadata reader
        CComPtr<IWICMetadataQueryReader> spRootQueryReader; 
        hr = GetRootQueryReader(spBitmapDecoder, spRootQueryReader); 
        ATLENSURE_SUCCEEDED(hr); 

        // get IFD query reader 
        CComPtr<IWICMetadataQueryReader> spIfdQueryReader; 
        hr = GetIfdQueryReader(spBitmapDecoder, spRootQueryReader, spIfdQueryReader); 
        ATLENSURE_SUCCEEDED(hr); 

        PROPVARIANT value; 
        ::PropVariantInit(&value); 

        // get camera model 
        CString strModel; 
        hr = spIfdQueryReader->GetMetadataByName(L"/{ushort=272}", &value);
        if (SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strModel = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // read other IFD values...

        // get embedded EXIF query reader
        CComPtr<IWICMetadataQueryReader> spExifQueryReader;
        hr = GetEmbeddedQueryReader(spIfdQueryReader, L"/exif", spExifQueryReader);
        ATLENSURE_SUCCEEDED(hr);

        // get the date and time when the image was digitized
        CString strDateTimeDigitized;
        hr = spExifQueryReader->GetMetadataByName(L"/{ushort=36868}", &value);
        if (SUCCEEDED(hr) && (value.vt == VT_LPSTR))
            strDateTimeDigitized = CA2T(value.pszVal);
        ::PropVariantClear(&value);

        // read other EXIF values...
    }
    catch (CException* e)
    {
        e->ReportError();
        e->Delete();
    }
}

It’s a little bit better than the previous example but still needs to be completed and a lot of code refactoring. I’ll do that in a future article to present a “full featured” IFD/EXIF metadata reader.

References and related articles

Easy PNG Resource Loading with MFC – Part 2

A previous article demonstrates how easy is to load images from PNG resources, by using Direct2D MFC support. I have found even an easier way: CPngImage class which extends CBitmap with methods that allow loading images from PNG format files and resources.
Here is a brief example:

Using CPngImage class for loading PNG resources

class CDemoDlg : public CDialogEx
{
    // ...
    CPngImage m_imageDemo;
    CStatic m_staticPicture;
    // ...
};

BOOL CDemoDlg::OnInitDialog()
{
    // ...
    VERIFY(m_imageDemo.Load(IDB_PNG_PENCIL));
    m_staticPicture.SetBitmap(m_imageDemo);
    // ...
}

Demo project

It is a simple dialog-based MFC application that loads a bitmap from PNG resource then use the bitmap for setting the image in a static picture control.
Download: PNG Resource Loading (VS 2015).zip (43)

Image may be NSFW.
Clik here to view.
PNG Resource Loading - Demo Application

PNG Resource Loading – Demo Application

Notes

  • of course, we can follow a similar way to set images in other controls, like for example CMFCButton;
  • CPngImage was designed for internal use in the MFC framework, but so far I didn’t see any problem in using it for our own purposes.

Resources and related articles

How to Get Visual C++ Sample Projects (2)

Codexpert – 2016 Articles Summary

MFC Support for DirectWrite – Part 8: Trimming

When using “classic” GDI functions i.e. CDC::DrawText, it’s no sweat to trim with ellipsis a single-line text when that text does not fit in the drawing rectangle width, by specifying DT_WORDBREAK flag. We cannot find a similar flag for CRenderTarget::DrawText or CRenderTarget::DrawTextLayout. However, trimming a text is also possible with DirectDraw. All we have to do is the following:

  1. call IDWriteFactory::CreateEllipsisTrimmingSign to create an inline object for trimming, using ellipsis as the omission sign;
  2. pass the created inline object to IDWriteTextFormat::SetTrimming.

Here is a simple code example in an MFC-based application:

Direct2D text trimming code sample

// ...
    CD2DTextFormat textFormat(pRenderTarget, m_strFontFamilyName, m_fFontSize);
    IDWriteTextFormat* pTextFormat = textFormat.Get();

    // Set single-line text
    pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);

    // Get IDWriteFactory
    _AFX_D2D_STATE* pD2DState = AfxGetD2DState();
    IDWriteFactory* pDirectWriteFactory = pD2DState->GetWriteFactory();

    // Create an inline object for trimming
    CComPtr<IDWriteInlineObject> spInlineObject;
    pDirectWriteFactory->CreateEllipsisTrimmingSign(textFormat.Get(), &spInlineObject);

    // Call IDWriteTextFormat::SetTrimming to set trimming for text overflowing the layout width.
    // Note: use DWRITE_TRIMMING_GRANULARITY_CHARACTER for trimming at character cluster boundary
    //       or DWRITE_TRIMMING_GRANULARITY_WORD for trimming at word boundary.
    DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 };
    pTextFormat->SetTrimming(&trimming, spInlineObject);
    // ...

    // Draw the text in render target
    pRenderTarget->DrawText(m_strText, rcDraw, m_pTextBrush, &textFormat);

Demo project

Download: MFC Support for DirectWrite Demo (Part 8).zip (10)

The demo project contains sample code for all my DirectWrite-related articles. To demonstrate this one, in Trimming granularity combo, select “Character” or “Word”. Also select “No wrap” in Word wrapping combo then have fun.

Image may be NSFW.
Clik here to view.
MFC DirectWrite Demo Project

MFC DirectWrite Demo Project

 

Resources and related articles


MFC Support for Direct2D – Part 3: Multithreading

As shown in previous articles, we can enable MFC Direct2D support for a window by a call of CWnd::EnableD2DSupport.
Example:

int CSlideShowWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // Enable MFC Direct2D support
    EnableD2DSupport();
    // ...

    return 0;
}

So far so good as long as all windows which use Direct2D are running in the same thread (usually the application main thread). But let’s say we have more than one window with intensive rendering (e.g. some images slide show). In this case we would like to create each window in a separate thread. But this could cause the drawing to freeze or even application to crash. So, what should we do?

Create multi-threaded Direct2D factory

If step into MFC code starting with CWnd::EnableD2DSupport, we can notice the following:

  1. check if Direct2D and DirectWrite (kept in a global object of type _AFX_D2D_STATE) are already created;
  2. if not, call _AFX_D2D_STATE::InitD2D with default parameters D2D1_FACTORY_TYPE_SINGLE_THREADED and DWRITE_FACTORY_TYPE_SHARED.

Now, lets see the comments from D2D1_FACTORY_TYPE enumeration.

typedef enum D2D1_FACTORY_TYPE
{
    //
    // The resulting factory and derived resources may only be invoked serially.
    // Reference counts on resources are interlocked, however, resource and render
    // target state is not protected from multi-threaded access.
    //
    D2D1_FACTORY_TYPE_SINGLE_THREADED = 0,

    //
    // The resulting factory may be invoked from multiple threads. Returned resources
    // use interlocked reference counting and their state is protected.
    //
    D2D1_FACTORY_TYPE_MULTI_THREADED = 1,
    D2D1_FACTORY_TYPE_FORCE_DWORD = 0xffffffff

} D2D1_FACTORY_TYPE;

Also, let’s see what MSDN documentation states:

  • D2D1_FACTORY_TYPE_SINGLE_THREADED
    No synchronization is provided for accessing or writing to the factory or the objects it creates. If the factory or the objects are called from multiple threads, it is up to the application to provide access locking.

  • D2D1_FACTORY_TYPE_MULTI_THREADED
    Direct2D provides synchronization for accessing and writing to the factory and the objects it creates, enabling safe access from multiple threads.

Making the synchronization is not so handy so we have to find a way to create a Direct2D factory that uses multi-threaded model. Fortunately, that’s very easy by a single call of CWinApp::EnableD2DSupport.

BOOL CDemoApp::InitInstance()
{
    // NOTE: Just for testing purpose, comment the below line of code
    //       then open more than one slide-show windows which are running 
    //       in different threads. See what happens.
    VERIFY(EnableD2DSupport(D2D1_FACTORY_TYPE_MULTI_THREADED));
    // ...

    return FALSE;
}

That’s all. Hurray!

Demo application

Download: MFC Direct2D Multithreading Demo.zip (2)

The demo application can create multiple windows that perform image slideshow with Direct2D, each one in its own thread. Just push the button then select a folder containing image files.

Image may be NSFW.
Clik here to view.
MFC Direct2D Multithreading - Demo

MFC Direct2D Multithreading – Demo

 

Resources and related articles

MFC Support for DirectWrite – Part 9: Hit-Test

DirectWrite has hit-testing support that can be useful for showing a caret, making a selection, doing some action if the user chicks in a given text range, and so on. The hit-test methods of IDWriteTextLayout interface are HitTestPoint, HitTestTextPosition and HitTestTextRange. Let me show a simple example for each one.

Hit-testing a point

This example calls IDWriteTextLayout::HitTestPoint in the WM_LBUTTONDOWN message handler and keeps in mind the text position in which the user has clicked.

void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
    // ...
    // Get  IDWriteTextLayout interface from CD2DTextLayout object
    IDWriteTextLayout* pTextLayout = m_pTextLayout->Get();

    // pixel location X to hit-test, 
    // relative to the top-left location of the layout box. 
    FLOAT fPointX = static_cast<FLOAT>(point.x);
    // pixel location Y to hit-test, 
    // relative to the top-left location of the layout box.
    FLOAT fPointY = static_cast<FLOAT>(point.y);
    // an output flag that indicates whether the hit-test location 
    // is at the leading or the trailing side of the character.
    BOOL bIsTrailingHit = FALSE;
    // an output flag that indicates whether the hit-test location 
    // is inside the text string
    BOOL bIsInside = FALSE;
    // output geometry fully enclosing the hit-test location
    DWRITE_HIT_TEST_METRICS hitTestMetrics = { 0 };

    HRESULT hr = pTextLayout->HitTestPoint(IN fPointX, IN fPointY,
        OUT &bIsTrailingHit, OUT &bIsInside, OUT &hitTestMetrics);

    if (SUCCEEDED(hr))
    {
        // keep in mind the hit-test text position
        m_nCaretPosition = hitTestMetrics.textPosition;
        Invalidate();
    }

    CWnd::OnLButtonDown(nFlags, point);
}

Hit-testing a text position

Further, use the previousy kept in mind text position and call IDWriteTextLayout::HitTestTextPosition when need to draw the caret.

void CChildView::_DrawCaret(CHwndRenderTarget* pRenderTarget)
{
    ASSERT_VALID(m_pTextLayout);
    ASSERT(m_pTextLayout->IsValid());

    // Get  IDWriteTextLayout interface from CD2DTextLayout object
    IDWriteTextLayout* pTextLayout = m_pTextLayout->Get();

    // flag that indicates whether the pixel location is of the 
    // leading or the trailing side of the specified text position
    BOOL bIsTrailingHit = FALSE;
    FLOAT fPointX = 0.0f, fPointY = 0.0f;
    DWRITE_HIT_TEST_METRICS hitTestMetrics = { 0 };
    HRESULT hr = pTextLayout->HitTestTextPosition(
        IN m_nCaretPosition,
        IN bIsTrailingHit,
        OUT &fPointX,
        OUT &fPointY,
        OUT &hitTestMetrics);

    if (SUCCEEDED(hr))
    {
        // Draw the caret
        // Note: this is just for demo purpose and
        // you may want to make something more elaborated here
        CD2DRectF rcCaret(fPointX + TEXT_MARGIN, fPointY + TEXT_MARGIN,
            fPointX + TEXT_MARGIN + 2, fPointY + TEXT_MARGIN + hitTestMetrics.height);
        pRenderTarget->FillRectangle(&rcCaret,
            &CD2DSolidColorBrush(pRenderTarget, CARET_COLOR));
    }
}

Hit-testing a text range

And finally, here is an example of using IDWriteTextLayout::HitTestTextRange

BOOL CChildView::_TextRangeHitTest(const CPoint& point, const DWRITE_TEXT_RANGE& textRange)
{
    ASSERT_VALID(m_pTextLayout);
    ASSERT(m_pTextLayout->IsValid());

    // Get  IDWriteTextLayout interface from CD2DTextLayout object
    IDWriteTextLayout* pTextLayout = m_pTextLayout->Get();

    FLOAT nOriginX = 0.0f, nOriginY = 0.0f;
    UINT32 nActualHitTestMetricsCount = 0;
    // Call once IDWriteTextLayout::HitTestTextRange in order to find out
    // the place required for DWRITE_HIT_TEST_METRICS structures
    // See MSDN documentation:
    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371473(v=vs.85).aspx
    HRESULT hr = pTextLayout->HitTestTextRange(textRange.startPosition, textRange.length,
        nOriginX, nOriginY, NULL, 0, &nActualHitTestMetricsCount);

    if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) != hr)
        return FALSE;

    // Allocate enough room for all hit-test metrics.
    std::vector<DWRITE_HIT_TEST_METRICS> vHitTestMetrics(nActualHitTestMetricsCount);

    // Call IDWriteTextLayout::HitTestTextRange again to effectively 
    // get the DWRITE_HIT_TEST_METRICS structures
    hr = pTextLayout->HitTestTextRange(textRange.startPosition, textRange.length,
        nOriginX, nOriginY,
        &vHitTestMetrics[0],
        static_cast(vHitTestMetrics.size()),
        &nActualHitTestMetricsCount);

    if (FAILED(hr))
        return FALSE;

    for (UINT32 nIndex = 0; nIndex < nActualHitTestMetricsCount; nIndex++)
    {
        DWRITE_HIT_TEST_METRICS& hitTestMetrics = vHitTestMetrics[nIndex];
        CRect rcHit((int)hitTestMetrics.left, (int)hitTestMetrics.top,
            (int)hitTestMetrics.left + (int)hitTestMetrics.width,
            (int)hitTestMetrics.top + (int)hitTestMetrics.height);

        if (rcHit.PtInRect(point))
            return TRUE;
    }
    return FALSE;
}

It can be used, for example, to set the hand cursor when the mouse is moved over the given text range:

void CChildView::OnMouseMove(UINT nFlags, CPoint point)
{
    DWRITE_TEXT_RANGE textRange = _GetHyperlinkTextRange();

    if (_TextRangeHitTest(point, textRange))
        ::SetCursor(m_hCursorHand);
    else
        ::SetCursor(m_hCursorArrow);

    CWnd::OnMouseMove(nFlags, point);
}

or can show some internet page when te user clicks on a “hyperlink”.

void CChildView::OnLButtonUp(UINT nFlags, CPoint point)
{
    DWRITE_TEXT_RANGE textRange = _GetHyperlinkTextRange();

    if (_TextRangeHitTest(point, textRange))
    {
        ::ShellExecute(m_hWnd, _T("open"), TEXT_URL, NULL, NULL, SW_SHOWNORMAL);
    }

    CWnd::OnLButtonUp(nFlags, point);
}

More details can be found in the demo examples attached to this article.

Demo projects

I’ve added the hit-test features to DirectWrite Static Control.
Download: Download: MFC Support for DirectWrite Demo (Part 9).zip (7)

Image may be NSFW.
Clik here to view.
MFC DirectWrite - Hit-Test Demo

MFC DirectWrite – Hit-Test Demo

Also here can be found a simpler application, only showing the DirectWrite hit-test features, to be easier understand: Simple DirectWrite Hit-Test Demo.zip (7)

Resources and related articles

MFC Support for DirectWrite – Part 10: Outlined Text

In a previous article, I showed the basics of custom text rendering with DirectWrite (see MFC Support for DirectWrite – Part 7: A Step to Custom Rendering). So far so good but it just mimics the default text rendering. Let’s now modify the overridden IDWriteTextRenderer::DrawGlyphRun in order to draw outlined text.

Overridden IDWriteTextRenderer::DrawGlyphRun implementation

STDMETHODIMP CCustomTextRenderer::XCustomTextRenderer::DrawGlyphRun(
    void* pClientDrawingContext, 
    FLOAT fBaselineOriginX, 
    FLOAT fBaselineOriginY, 
    DWRITE_MEASURING_MODE measuringMode,
    const DWRITE_GLYPH_RUN* pGlyphRun, 
    const DWRITE_GLYPH_RUN_DESCRIPTION* pGlyphRunDescription, 
    IUnknown* pClientDrawingEffect)
{
    METHOD_PROLOGUE(CCustomTextRenderer, CustomTextRenderer);
    CRenderTarget* pRenderTarget = pThis->GetRenderTarget();
    ASSERT(pRenderTarget && pRenderTarget->IsValid());

    CD2DPointF point(fBaselineOriginX, fBaselineOriginY);
    CCustomEffect* pCustomEffect = static_cast<CCustomEffect*>(pClientDrawingContext);
    if (NULL == pCustomEffect)
    {
        return _DrawDefaultGlyphRun(pRenderTarget, point, pGlyphRun, measuringMode);
    }
    
    HRESULT hr = E_FAIL;
    switch (pCustomEffect->GetType())
    {
    case CustomEffectType::outlined_text:
        hr = _DrawOutlinedGlyphRun(pRenderTarget,
            dynamic_cast<COutlinedTextEffect*>(pCustomEffect),
            point,
            pGlyphRun,
            measuringMode);
    // ...
    // may add other types of custom effects here
    }

    return hr;
}

A method for drawing outlined text

HRESULT CCustomTextRenderer::XCustomTextRenderer::_DrawOutlinedGlyphRun(
    CRenderTarget* pRenderTarget,
    COutlinedTextEffect* pEffect, 
    const CD2DPointF& point,
    const DWRITE_GLYPH_RUN* pGlyphRun,
    DWRITE_MEASURING_MODE measuringMode)
{
    ASSERT(pRenderTarget && pRenderTarget->IsValid());
    ASSERT(pEffect && pEffect->IsValid());

    // get the Direct2D resources contained the COutlinedTextEffect object
    CD2DBrush* pOutlineBrush = pEffect->GetOutlineBrush();
    ASSERT(pOutlineBrush && pOutlineBrush->IsValid());

    CD2DBrush* pFillBrush = pEffect->GetFillBrush();
    ASSERT(pFillBrush && pFillBrush->IsValid());

    // get outline stroke width and RTL flag
    FLOAT fStrokeWidth = pEffect->GetStrokeWidth();
    BOOL bIsRightToLeft = pEffect->IsRightToLeft();

    // create path geometry
    CD2DPathGeometry pathGeometry = (pRenderTarget);
    HRESULT hr = pathGeometry.Create(pRenderTarget);
    ASSERT(SUCCEEDED(hr));
    if (FAILED(hr)) return hr;

    // create a geometry sink which will be called back 
    // to perform outline drawing operations
    CD2DGeometrySink geometrySink(pathGeometry);
    ASSERT(geometrySink.IsValid());
        
    // get IDWriteFontFace interface from DWRITE_GLYPH_RUN structure
    IDWriteFontFace* pFontFace = pGlyphRun->fontFace;

    // call IDWriteFontFace::GetGlyphRunOutline passing data 
    // contained in DWRITE_GLYPH_RUN and the geometry sink
    hr = pFontFace->GetGlyphRunOutline(pGlyphRun->fontEmSize,
        pGlyphRun->glyphIndices, pGlyphRun->glyphAdvances, 
        pGlyphRun->glyphOffsets, pGlyphRun->glyphCount, 
        pGlyphRun->isSideways, bIsRightToLeft, geometrySink);

    if (FAILED(hr)) return hr;

    geometrySink.Close();

    // keep in mind the render target transform matrix
    D2D1_MATRIX_3X2_F oldMatrix;
    pRenderTarget->GetTransform(&oldMatrix);
    // translate the render target according to baseline coordinates 
    pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(point.x, point.y));

    // draw the outline and fill the geometry path
    pRenderTarget->DrawGeometry(&pathGeometry, pOutlineBrush, fStrokeWidth);
    pRenderTarget->FillGeometry(&pathGeometry, pFillBrush);

    // restore the initial render target transform
    pRenderTarget->SetTransform(oldMatrix);

    return S_OK; 
    // well, maybe this method needs a little bit of refactoring. :)
}

Basically, the steps where the following:

  1. if pClientDrawingContext parameter is null then perform the default drawing;
  2. otherwhise, call _DrawOutlinedGlyphRun pasing an object that contains the brushes for text outline and fill (COutlinedTextEffect);
  3. create a path geometry (CD2DPathGeometry);
  4. instantiate a geometry sink that is used to populate the path geometry (CD2DGeometrySink);
  5. get IDWriteFontFace interface from DWRITE_GLYPH_RUN structure;
  6. call IDWriteFontFace::GetGlyphRunOutline, passing a bunch of parameters, most of them contained in DWRITE_GLYPH_RUN structure plus our geometry sink;
  7. close the geometry sink;
  8. translate the render target according to the glyph run baseline origin (baselineOriginX and baselineOriginY parameters of DrawGlyphRun);
  9. finally, call CRenderTarget::DrawGeometry and CRenderTarget::FillGeometry and…
  10. …do not forget to restore the initial render target transform.

You can find more details in the demo application attached here. Also you can have a look in the related articles mentioned at the bottom of this page.

Demo project

The demo project draws outlined text in order to make it readable over any background image.
DownloadOutlined Text Demo.zip (15)

Image may be NSFW.
Clik here to view.
Outlined Text demo project

Outlined Text demo project

Resources and related articles

MFC Support for Direct2D – Part 4: Built-in Effects

Direct2D has built-in support for image processing like changing brightness or contrast, blurring, creating drop shadows, and so on. Basically, we can use for this purpose the CreateEffect and DrawImage methods of ID2D1DeviceContext interface. So far, there is no MFC wrapper class for ID2D1DeviceContext but that’s not so big issue. Once having a valid ID2D1RenderTarget instance (wrapped in CRenderTarget class) we can easily get the ID2D1DeviceContext interface by calling QueryInterface. No sweat!

Getting ID2D1DeviceContext interface in an MFC application

CComPtr<ID2D1DeviceContext> spDeviceContext;
    pRenderTarget->GetRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext), 
        reinterpret_cast<void**>(&spDeviceContext));
    // ...

Or, a little bit easier, use CComQIPtr which calls QueryInterface for you.

CComQIPtr<ID2D1DeviceContext> spDeviceContext = pRenderTarget->GetRenderTarget();
    // ...

Drawing an image using the Gaussian Blur built-in effect

Here is an example:

void CChildView::_DrawBlurredImage(CRenderTarget* pRenderTarget)
{
    // ...
    // get ID2D1DeviceContext interface
    CComQIPtr<ID2D1DeviceContext> spDeviceContext = 
        pRenderTarget->GetRenderTarget();

    // create Gaussian Blur effect
    CComPtr<ID2D1Effect> spEffect;
    HRESULT hr = spDeviceContext->CreateEffect(CLSID_D2D1GaussianBlur, &spEffect);
    ATLASSERT(SUCCEEDED(hr));
    // ...

    // set the input image
    spEffect->SetInput(0, m_pBitmap->Get());

    // finally, call ID2D1DeviceContext::DrawImage
    spDeviceContext->DrawImage(spEffect);
    //
    // if it doesn't blew up until here, means that it works. :)
    // ...  
}

Just note that you should additionally include d2d1_1.h header and link your project to dxguid.lib. More details can be found in the attached demo project.

The demo project

Download: Gaussian Blur Effect demo.zip (21)

Image may be NSFW.
Clik here to view.
Gaussian Blur Effect - Demo Project

Notes

Resources and related articles

MFC Support for Direct2D – Part 5: Interoperability with GDI

There are two ways to combine Direct2D with Windows GDI API in the same application:

  1. Drawing Direct2D content to a GDI device context
  2. Drawing GDI content to a Direct2D GDI-compatible render target

Let’s see each one!

Drawing Direct2D content to a GDI device context

For this purpose, use ID2D1DCRenderTarget instead of ID2D1HwndRenderTarget interface. If using MFC shipped with Visual Studio 2015 or newer, then CDCRenderTarget wrapper class makes the programmer life easier. Here are the steps:

  1. In the WM_CREATE message handler call CWnd::EnableD2DSupport passing TRUE as second parameter. This way, the MFC framework creates a CD2DRenderTarget instead of a CHwndRenderTarget object.
    int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        if (__super::OnCreate(lpCreateStruct) == -1)
            return -1;
    
        // Note: need Visual Studio 2015 or newer (_MFC_VER >= 0x0E00)
        BOOL bUseDCRenderTarget = TRUE;
        EnableD2DSupport(TRUE, bUseDCRenderTarget);
    
        if (!IsD2DSupportEnabled())
            return -1;
    
        return 0;
    }
  2. Map AFX_WM_DRAW2D registered messge.
    class CChildView : public CWnd
    {
        // ...
        afx_msg LRESULT OnAfxDraw2D(WPARAM wParam, LPARAM lParam);
    };

    BEGIN_MESSAGE_MAP(CChildView, CWnd)
        // ...
        ON_REGISTERED_MESSAGE(AFX_WM_DRAW2D, &CChildView::OnAfxDraw2D)
    END_MESSAGE_MAP()
  3. Finally, get the CDCRenderTarget* passed by MFC framework in LPARAM and enjoy.
    LRESULT CChildView::OnAfxDraw2D(WPARAM wParam, LPARAM lParam)
    {
        CDCRenderTarget* pDCRenderTarget = (CDCRenderTarget*)lParam;
        ASSERT_KINDOF(CDCRenderTarget, pDCRenderTarget);
    
        // Draw Direct2D content in GDI device context
        _DrawDirect2DContent(pDCRenderTarget);
    
        pDCRenderTarget->EndDraw();
    
        // Draw using GDI
        CWindowDC dc(this);
        _DrawGDIContent(&dc);
    
        return TRUE;
    }

Drawing GDI content to a Direct2D GDI-compatible render target

So far, MFC has not a wrapper class for ID2D1GdiInteropRenderTarget interface. Also, CWnd::EnableD2DSupport creates a CHwndRenderTarget which is not GDI-compatible. So, we must do it ourselves. Here is the sample code:

class CChildView : public CWnd
{
    // ...
    CComQIPtr<ID2D1GdiInteropRenderTarget> m_spGdiInteropRenderTarget;
    // ...
    afx_msg LRESULT OnAfxDraw2D(WPARAM wParam, LPARAM lParam);
};

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

    // get global Direct2D factory
    ID2D1Factory* pDirect2DFactory = AfxGetD2DState()->GetDirect2dFactory();

    // create GDI-compatible window render target
    D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties();
    rtProps.usage = D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE;
    rtProps.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    rtProps.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;

    ID2D1HwndRenderTarget* pHwndRenderTarget = NULL;
    HRESULT hr = pDirect2DFactory->CreateHwndRenderTarget(
        &rtProps,
        &D2D1::HwndRenderTargetProperties(m_hWnd, CD2DSizeU()),
        &pHwndRenderTarget);
    if (FAILED(hr))
        return -1;

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

    // create ID2D1GdiInteropRenderTarget instance
    m_spGdiInteropRenderTarget = pHwndRenderTarget;
    if (!m_spGdiInteropRenderTarget)
        return -1;

    return 0;
}

LRESULT CChildView::OnAfxDraw2D(WPARAM wParam, LPARAM lParam)
{
    CHwndRenderTarget* pHwndRenderTarget = (CHwndRenderTarget*)lParam;
    ASSERT_VALID(pHwndRenderTarget);

    // draw using Direct2D
    _DrawDirect2DContent(pHwndRenderTarget);

    HDC hDC = NULL;
    HRESULT hr = m_spGdiInteropRenderTarget->GetDC(
        D2D1_DC_INITIALIZE_MODE_COPY, &hDC);

    if (FAILED(hr) || (NULL == hDC))
        return FALSE;

    // draw GDI content in a GDI-compatible window render target
    _DrawGDIContent(CDC::FromHandle(hDC));

    CRect rcClient;
    GetClientRect(rcClient);
    hr = m_spGdiInteropRenderTarget->ReleaseDC(rcClient);
    if (FAILED(hr))
        return FALSE;

    return TRUE;
}

Demo projects

Download: Direct2D and GDI Interop Demo.zip (12)

The Visual C++ solution attached here has two simple projects showing the both Direct2D and GDI interoperatibily methods listed above.

Image may be NSFW.
Clik here to view.
Direct2D and GDI Interop - Demo Application

Direct2D and GDI Interop – Demo Application

 

Notes

  • can be observed that Direct2D drawing quality is higher than GDI drawing, because Direct2D is capable of rendering with antialiasing;
  • given the first note, my opinion is that GDI drawing has no much sense once using Direct2D but never know…

Resources and related articles

Viewing all 51 articles
Browse latest View live