Quantcast
Channel: MFC | codexpert blog
Viewing all 44 articles
Browse latest View live

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

$
0
0

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 (26)

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

$
0
0

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.

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. :) 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.

    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

    Debug performance message

    Debug performance message

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

    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.

    Debugging symbols options

    Debugging symbols options

  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).

    Optimize for debugging

    Optimize for debugging

  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

$
0
0

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

$
0
0

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

$
0
0

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)

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

Codexpert – 2016 Articles Summary

$
0
0

MFC Support for DirectWrite – Part 8: Trimming

$
0
0

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 (25)

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.

MFC DirectWrite Demo Project

MFC DirectWrite Demo Project

 

Resources and related articles

MFC Support for Direct2D – Part 3: Multithreading

$
0
0

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 (24)

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.

MFC Direct2D Multithreading - Demo

MFC Direct2D Multithreading – Demo

 

Resources and related articles


MFC Support for DirectWrite – Part 9: Hit-Test

$
0
0

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 (13)

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 (12)

Resources and related articles

MFC Support for DirectWrite – Part 10: Outlined Text

$
0
0

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 (18)

Outlined Text demo project

Outlined Text demo project

Resources and related articles

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

$
0
0

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 (12)

Gaussian Blur Effect - Demo Project

Notes

Resources and related articles

MFC Support for Direct2D – Part 5: Interoperability with GDI

$
0
0

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 (8)

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

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

Double-click in MDI Client

$
0
0

It’s hard to believe that handling mouse double-clicks in MDI Client windows can be super useful, but I found this problem in a discussion forum. I presumed that it was not only for fun, so I tried to resolve it. First, I subclassed the MDI Client window in a CWnd-derived class. That is possible for a standard MFC project in this way:

class CMainFrame : public CMDIFrameWnd
{
    // ...
    // Attributes
private:
    CMDIClientWnd m_wndMDIClient;
    // ...

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // NOTE: m_hWndMDIClient is a public member of CMDIFrameWnd
    // keeping the MDI Client window handle
    m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
    // ...
    return 0;
}

But no success: WM_LBUTTONDBLCLK message handler is not called in CMDIClientWnd class. Then I tried to catch it in overridden PreTranslateMessage or even in a WH_MOUSE hook with the same result. Finally, I had a look using Spy++ tool. Bingo! The MDI Client (of predefined window class named “MdiClient“) has no CS_DBLCLKS style so it does not deal with mouse double-clicks.

MDIClient

MDIClient

All wat he have to do is to create the The MDI Client window with our own registered window class. This is possible by overriding CMDIFrameWnd::CreateClient.

Create a MDI client window which belongs to a window class having CS_DBLCLKS style

BOOL CMainFrame::CreateClient(LPCREATESTRUCT lpCreateStruct, CMenu* pWindowMenu)
{
    // NOTE: do not call CMDIFrameWnd::CreateClient

    // get MDiCLient predefined window class info
    WNDCLASS wndClass = { 0 };
    if (!::GetClassInfo(AfxGetInstanceHandle(), _T("MdiClient"), &wndClass))
        return FALSE;

    // put our own window class name in WNDCLASS structure
    wndClass.lpszClassName = OUR_OWN_CLASS_NAME;
    // set CS_DBLCLKS flag in class style
    wndClass.style |= CS_DBLCLKS;
    // register window class
    AfxRegisterClass(&wndClass);

    ////////////////////////////////////////////////////////////////////////
    // Next is copy-pasted from MFC framework implementation, 
    // except that use our own registered window class
 
    // ...
    // ...

    // Create MDICLIENT control with special IDC
    if ((m_hWndMDIClient = CreateWindowEx(dwExStyle,
        /*_T("mdiclient")*/ OUR_OWN_CLASS_NAME, // use our own window class
        NULL,
        dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)AFX_IDW_PANE_FIRST,
        AfxGetInstanceHandle(), (LPVOID)&ccs)) == NULL)
    {
        TRACE(traceAppMsg, 0, 
            _T("Warning: CMDIFrameWnd::OnCreateClient: failed to create MDICLIENT.")
            _T(" GetLastError returns 0x%8.8X\n"), ::GetLastError());
        return FALSE;
    }
    // Move it to the top of z-order
    ::BringWindowToTop(m_hWndMDIClient);

    return TRUE;
}

Handle MDI Client double-clicks in standard MFC applications

Once we have created the MDI Client window and subclassed it as shown above, can simply handle (for example) WM_LBUTTONDBLCLK in our CWnd-derived class.

BEGIN_MESSAGE_MAP(CMDIClientWnd, CWnd)
    ON_WM_LBUTTONDBLCLK()
END_MESSAGE_MAP()

// CMDIClientWnd message handlers

void CMDIClientWnd::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    // Do something cool!
    // ...
    CWnd::OnLButtonDblClk(nFlags, point);
}

Handle MDI Client double-clicks in Visual Studio or Office-style MFC applications

CMDIFrameWndEx uses its own CWnd-derived class for the MDI Client window, so we cannot use another one in  main frame class. However, this is not so big problem because we can now catch WM_LBUTTONDBLCLK in overridden PreTranslateMessage method.

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
    if ((pMsg->message == WM_LBUTTONDBLCLK) && (pMsg->hwnd == m_hWndMDIClient))
    {
        // do something cool!
    }
    return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}

Or, you can catch it in a WH_MOUSE hook, if think that can be more exciting. :)

class CMainFrame : public CMDIFrameWndEx
{
    static HHOOK m_hHook;
    static HWND m_hWndClient;
    static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);
// ...
};

    // ...

    DWORD dwThreadID = ::GetCurrentThreadId();
    m_hHook = ::SetWindowsHookEx(WH_MOUSE, &CMainFrame::MouseProc, NULL, dwThreadID);

         // ...

LRESULT CALLBACK CMainFrame::MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0) 
        return ::CallNextHookEx(m_hHook, nCode, wParam, lParam); 

    UINT nMsg = (UINT)wParam; 
    MOUSEHOOKSTRUCT* pMHS = (MOUSEHOOKSTRUCT*)lParam; 
    if (m_hWndClient && pMHS && (WM_LBUTTONDBLCLK == nMsg) && (pMHS->hwnd == m_hWndClient))
    {
        // do something coo!
    }
    return ::CallNextHookEx(m_hHook, nCode, wParam, lParam);
}
    // ...

    if (NULL != m_hHook)
    {
        ::UnhookWindowsHookEx(m_hHook);
        m_hHook = NULL;
    }

Note that creating the MDI Client window in overridden CMDIFrameWnd::CreateClient cand be done in all cases in the same way.

Demo solution

Download: MDI client double-click samples.zip (21)
The sample Visual Studio solution contains two projects: one is a standard MDI application, and the other one is Office-style. Just enjoy of MDI Client double-clicks! :)

Resources and related articles

MFC Support for DirectWrite – Part 11: About Trimming Again

$
0
0

In a previous article, I showed how to trim a text which overflows the layout box. In the example presented there, the ellipsis is added at the end of truncated text. But if, let’s say, we have to show a long path and file name, it is not meaningful if the file name is not displayed. No problem, this can be easily resolved if have a look at DWRITE_TRIMMING structure which passed to IDWriteTextFormat::SetTrimming. The second parameter is a character code used as delimiter and the third one is the delimiter count. The text is preserved from the end until is found the delimiter which coresponds to delimiter count. For example, if the delimiter is backslash and the delimiter count is 2, a text like “e:\Articles\MFC Support for DirectWrite\DirectWrite Trimming Demo\D2DStaticCtrl.cpp” may be displayed as follows “e:\Articles…\DirectWrite Trimming Demo\D2DStaticCtrl.cpp”.

An example of trimming by using a delimiter

LRESULT CD2DStaticCtrl::OnAfxDraw2D(WPARAM wParam, LPARAM lParam)
{
    // ...

    // Set text trimming
    CComPtr<IDWriteInlineObject> spInlineObject;
    IDWriteFactory* pDirectWriteFactory = AfxGetD2DState()->GetWriteFactory();
    hr = pDirectWriteFactory->CreateEllipsisTrimmingSign(textFormat.Get(), 
        &spInlineObject);
    if (FAILED(hr))
        return FALSE;

    DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, '\\', 2 };

    hr = textFormat.Get()->SetTrimming(&trimming, spInlineObject);
    if (FAILED(hr))
        return FALSE;

    // ...

    return TRUE;
}

Demo project

Download: DirectWrite Trimming Demo.zip (19)
The demo project is a simple MFC application which demonstrate how to trim text rendered with DirectWrite. You can choose the granularity, the delimiter and the delimiter count and see what is displayed.

DirectWrite Trimming Demo

DirectWrite Trimming Demo

Resources and related articles

MFC Support for Direct2D – Part 6: Composite Effects

$
0
0

We can combine two or more images and/or effects by using the composite effect. Here is an example.

Drawing shadows using Direct2D composite effects

void CD2DStaticCtrl::_DrawImageWithShadow(CHwndRenderTarget* pRenderTarget)
{
    ASSERT(pRenderTarget && pRenderTarget->IsValid());
    ASSERT(m_pBitmap && m_pBitmap->IsValid());

    // get ID2D1DeviceContext interface; note: must include <d2d1_1.h>
    CComQIPtr<ID2D1DeviceContext> spDeviceContext =
        pRenderTarget->GetRenderTarget();

    // create shadow effect and set the input image
    CComPtr<ID2D1Effect> spShadowEffect;
    spDeviceContext->CreateEffect(CLSID_D2D1Shadow, &spShadowEffect);
    spShadowEffect->SetInput(0, m_pBitmap->Get());
    spShadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, m_fBlurStandardDeviation);
    spShadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, CRenderTarget::COLORREF_TO_D2DCOLOR(m_shadowColor));

    // create flood effect
    CComPtr<ID2D1Effect> spFloodEffect;
    spDeviceContext->CreateEffect(CLSID_D2D1Flood, &spFloodEffect);
    // Note: shadow will be composed in the top of a surface having the background color
    D2D1_COLOR_F color = CRenderTarget::COLORREF_TO_D2DCOLOR(m_backColor);
    spFloodEffect->SetValue(D2D1_FLOOD_PROP_COLOR, D2D1::Vector4F(color.r, color.g, color.b, color.a));

    // create 2D affine transform effect in order to translate the shadow
    CComPtr<ID2D1Effect> spAffineTransformEffect;
    spDeviceContext->CreateEffect(CLSID_D2D12DAffineTransform, &spAffineTransformEffect);
    spAffineTransformEffect->SetInputEffect(0, spShadowEffect);
    D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(m_fShadowDistance, m_fShadowDistance);
    spAffineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, translation);
    
    // create the composite effect and combine the effects
    CComPtr<ID2D1Effect> spCompositeEffect;
    spDeviceContext->CreateEffect(CLSID_D2D1Composite, &spCompositeEffect);
    // set input effects
    spCompositeEffect->SetInputEffect(0, spFloodEffect);
    spCompositeEffect->SetInputEffect(1, spAffineTransformEffect);
    // set input image; because the default input count is 1, he have to set it to 3
    spCompositeEffect->SetInputCount(3);
    spCompositeEffect->SetInput(2, m_pBitmap->Get());

    // finally perform drawing
    spDeviceContext->DrawImage(spCompositeEffect);
}

Demo project

Download: Composite Effect Demo.zip (8)
You can set the amount of blur and the color as well as the distance of the shadow.

Composite Effect Demo

Composite Effect Demo

Resources and related articles


Three Ways to Find Files

$
0
0

Let’ say we have to implement a function that search a folder to recursively find files having an extension from a given list of extensions. This article shows three possible implementations: one using FindFirstFile and FindNextFile WinAPI functions, one using CFileFind MFC class and one using Filesystem Library.

Find files using FindFirstFile and FindNextFile WinAPI functions

Althugh this implementation uses some ATL stuff to make things easier, it appears still quite complicated.

void FindFiles(
    const CString& strRootPath,        // initial path to search
    const std::list<CString> listExt,  // a list of file extensions
    std::list<CString>& listFiles)     // output list of files found
{
    CString strFileToFind = strRootPath;
    ATLPath::Append(CStrBuf(strFileToFind, MAX_PATH), _T("*.*"));

    WIN32_FIND_DATA findData = { 0 };
    HANDLE hFileFind = ::FindFirstFile(strFileToFind, &findData);
    if (INVALID_HANDLE_VALUE != hFileFind)
    {
        do
        {
            CString strFileName = findData.cFileName;
            if ((strFileName == _T(".")) || (strFileName == _T("..")))
                continue;

            CString strFilePath = strRootPath;
            ATLPath::Append(CStrBuf(strFilePath, MAX_PATH), strFileName);
            if (ATLPath::IsDirectory(strFilePath))
            {
                // call recursive
                FindFiles(strFilePath, listExt, listFiles);
            }
            else
            {
                CString strExt = ATLPath::FindExtension(strFilePath);
                strExt.TrimLeft(_T('.'));

                if (IsStringInListNoCase(strExt, listExt))
                {
                    listFiles.push_back(strFilePath);
                }
            }

        } while (::FindNextFile(hFileFind, &findData));

        ::FindClose(hFileFind);
    }
}

Find files using CFileFind MFC class

Using CFileFind MFC class can make code a little bit shorter and the programmer’s life easier.

void FindFiles(
    const CString& strRootPath,        // initial path to search
    const std::list<CString> listExt,  // a list of file extensions
    std::list<CString>& listFiles)     // output list of files found
{
    CString strFileToFind = strRootPath + _T("\\*.*");

    CFileFind fileFind;
    BOOL bMoreFiles = fileFind.FindFile(strFileToFind);
    while (bMoreFiles)
    {
        bMoreFiles = fileFind.FindNextFile();
        if (fileFind.IsDots())
            continue;

        CString strFilePath = fileFind.GetFilePath();
        if (fileFind.IsDirectory())
        {
            // call recursive
            FindFiles(strFilePath, listExt, listFiles);
        }
        else
        {
            int nPos = strFilePath.ReverseFind(_T('.'));
            if (-1 != nPos)
            {
                CString strExt = strFilePath.Right(strFilePath.GetLength() - nPos - 1);
                if (IsStringInListNoCase(strExt, listExt))
                {
                    listFiles.push_back(strFilePath);
                }
            }
        }
    }
}

Note: IsStringInListNoCase searches a list for a string, not case sensitive. You can find its implementation in the attached demo solution.

Find files using Filesystem Library

Here it is:

void FindFiles(
    const CString& strRootPath,        // initial path to search
    const std::list<CString> listExt,  // a list of file extensions
    std::list<CString>& listFiles)     // output list of files found
{
    _FSPFX path root_path(strRootPath.GetString());
    _FSPFX recursive_directory_iterator end;
    _FSPFX recursive_directory_iterator iter(root_path);
    while (iter != end)
    {
        const auto& path = iter->path();
        CString strExt = path.extension().c_str();
        strExt.TrimLeft(_T('.'));
        if (IsStringInListNoCase(strExt, listExt))
        {
            listFiles.push_back(path.c_str());
        }
        ++iter;
    }
}

Hurray, we did it in just few lines of code! :)
Of course, it can be used in MFC, ATL, Win32 and Console applications, as well.

Demo solution

The demo solution contains three projects, each having one of the above implementations.
Download: Find Files Samples (Visual Studio 2015).zip (9)

Resources and related articles

Enumerate WIC Components

$
0
0

In an older article, I presented a function that enumerates Windows Imaging Component codecs. Recently I had the idea to make a set of WIC Interfaces wrapper classes for easily enumerate and get info about all types of WIC components: bitmap decoders and encoders, metadata readers and writers, format converters, and supported pixel formats. Here are just few samples of code.

CComponentEnumT – a template class for enumerating WIC components

namespace wic
{
    template<class TCompInfoCls, DWORD dwTypes>
    class CComponentEnumT
    {
        // ...
    public:
        using CompInfoList = std::list<std::shared_ptr<TCompInfoCls>>;
        using EnumOptions = WICComponentEnumerateOptions;

        CComponentEnumT()
        {
            HRESULT hr = m_spWICFactory.CoCreateInstance(CLSID_WICImagingFactory);
            ATLENSURE_SUCCEEDED(hr);
        }
        ~CComponentEnumT() = default;
        const CompInfoList& DoEnum(EnumOptions opt = WICComponentEnumerateDefault)
        {
            m_listCompInfo.clear();
            CComPtr<IEnumUnknown> spEnum;
            HRESULT hr = m_spWICFactory->CreateComponentEnumerator(dwTypes, opt, &spEnum);
            ATLENSURE_SUCCEEDED(hr);

            ULONG cbActual = 0;
            CComPtr<IUnknown> spEnumElement = NULL;
            while (S_OK == spEnum->Next(1, &spEnumElement, &cbActual))
            {
                auto spCompInfo = std::make_shared<TCompInfoCls>(spEnumElement);
                m_listCompInfo.push_back(spCompInfo);
                spEnumElement = nullptr;
            }
            return m_listCompInfo;
        }

    private:
        CompInfoList m_listCompInfo;
        CComPtr<IWICImagingFactory> m_spWICFactory;
    };
}

CComponentInfoBaseT – base class for all other component info classes

namespace wic
{
    template <class TCompInfoInterface>
    class CComponentInfoBaseT
    {
        // ...
    protected:
        CComponentInfoBaseT(const CComPtr<IUnknown>& spEnumElement)
        {
            ATLASSERT(spEnumElement);
            m_spCompInfo = spEnumElement;
        }
        ~CComponentInfoBaseT() = default;

        // data
        CComQIPtr<TCompInfoInterface> m_spCompInfo;
    };
}

CPixelFormatInfoT – just one of classes getting WIC component info

namespace wic
{
    template <class TCompInfoInterface>
    class CPixelFormatInfoT : public CComponentInfoT<TCompInfoInterface>
    {
    public:
        CPixelFormatInfoT(const CComPtr<IUnknown>& spEnumElement);
        ~CPixelFormatInfoT();

        UINT GetBitsPerPixel() const;
        UINT GetChannelCount() const;
        GUID GetFormatGUID() const;
        bool SupportsTransparency() const;
        WICPixelFormatNumericRepresentation GetNumericRepresentation() const;
    };

    using CPixelFormatInfo = CPixelFormatInfoT<IWICPixelFormatInfo2>;
}

Just note that, for further easier using, I’ve defined here CPixelFormatInfo, an alias for CPixelFormatInfoT<IWICPixelFormatInfo2>.

CComponentEnumT specializations and aliases

namespace wic
{
    using CAllComponentEnum = CComponentEnumT<CComponentInfo, WICAllComponents>;
    using CBitmapDecoderEnum = CComponentEnumT<CBitmapCodecInfo, WICDecoder>;
    using CBitmapEncoderEnum = CComponentEnumT<CBitmapCodecInfo, WICEncoder>;
    using CBitmapCodecEnum = CComponentEnumT<CBitmapCodecInfo, WICDecoder | WICEncoder>;
    using CPixelFormatEnum = CComponentEnumT<CPixelFormatInfo, WICPixelFormat>;
    using CMetadataReaderEnum = CComponentEnumT<CMetadataHandlerInfo, WICMetadataReader>;
    using CMetadataWriterEnum = CComponentEnumT<CMetadataHandlerInfo, WICMetadataWriter>;
    using CMetadataHandlerEnum = CComponentEnumT<CMetadataHandlerInfo, WICMetadataReader | WICMetadataWriter>;
    using CFormatConverterEnum = CComponentEnumT<CFormatConverterInfo, WICPixelFormatConverter>;
}

Finally, let’s see how to use these classes!

Enumerating WIC pixel formats

void CDemo_MFCDlg::EnumWICPixelFormats(WICComponentEnumerateOptions opt)
{
    wic::CPixelFormatEnum wicEnum;
    const auto& list = wicEnum.DoEnum(opt);
    for (const auto& spItem : list)
    {
        m_editReport.PrintComponentAttributes(spItem);
        m_editReport.PrintPixelFormatAttributes(spItem);
    }
}

Quite easy, isn’t it?

Get the pixel format info

template<class T> void CWICReportEdit::PrintPixelFormatAttributes(T& spItem)
{
    PrintLineV(_T("Bits per pixel:       %u"), spItem->GetBitsPerPixel());
    PrintLineV(_T("Channel count:        %u"), spItem->GetChannelCount());
    // ...
}
// Easy, as well.

You can find the complete source code in the attached demo solution.

Demo solution

Download: Enumerate WIC Components - Demo Solution.zip (10)

Enumerate WIC Components - Demo

Enumerate WIC Components – Demo

The demo solution contains an MFC and a console project that use the classes described above in order to enumerate WIC components and get info about each one. The MFC one allows choosing the component type(s) as well as enum options (include the disabled components or enum only Windows built-in components). The console application is simpler and I wrote it just to demonstrate that my little library of wrapper classes can be used both in MFC and non-MFC applications.

Notes

  • I hope, this article can be useful.
  • If you have any observation or remarks, please do not hesitate to post a reply!

Resources and related articles

MFC Support for Direct2D – Part 7: Saving to files

$
0
0

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.
DownloadSave Direct2D bitmap in a file - Demo.zip (10)

Resources and related articles

MFC Support for Windows Animation

$
0
0

Let’s say we have to make a slide show presentation using Cross Fade effect. If the target system is Windows 10, that’s quite easy because Direct2D offers built-in Cross Fade effect.

Cross Fade effect example

void CAnimationWnd::Draw(CRenderTarget* pRenderTarget)
{
    // get ID2D1DeviceContext interface; note: must include 
    CComQIPtr<ID2D1DeviceContext> spDeviceContext = pRenderTarget->GetRenderTarget();

    // create cross-fade effect; note include  and link to dxguid.lib
    CComPtr<ID2D1Effect> spCrossFadeEffect;
    spDeviceContext->CreateEffect(CLSID_D2D1CrossFade, &spCrossFadeEffect);
    // ...
    // set source and destination bitmaps
    spCrossFadeEffect->SetInput(0, m_pBitmapSrc->Get());
    spCrossFadeEffect->SetInput(1, m_pBitmapDest->Get());

    // get "animated" value (uncomment the line below if use Windows Animation)
    // m_spAnimCrossFadeWeightValue->GetValue(m_dWeight);

    // set cross-fade effect weight
    // for a value of 0.0f the destination image is exclusively used
    // for a value of 1.0f the source image is exclusively used
    spCrossFadeEffect->SetValue(D2D1_CROSSFADE_PROP_WEIGHT, static_cast<FLOAT>(m_dWeight));

    // perform drawing
    spDeviceContext->DrawImage(spCrossFadeEffect);
}

Further we can set a timer to periodically change m_dWeight value from 0.0f to 1.0f. So far, so good but a better solution is to use Windows Animation interfaces that allow to implement animations that are smooth, natural, and interactive. Moreover, MFC offers wrapper classes that can make programmer’s life much more easier than in case of direct using COM interfaces.

Using MFC support for animation

To use MFC support for animation do the following:

  1. first of all we need an object of type CAnimationController which is the key class that manages animations;
  2. call CAnimationController::SetRelatedWnd in order to establish a window that will receive WM_PAINT message when animation manager status has changed or animation timer has been updated;
  3. call CAnimationController::EnableAnimationTimerEventHandler;
  4. add an animation object, in our case of type CAnimationValue as long as we only need an “animated” FLOAT to set cross-fade weight property; here is a list of MFC animation object classes defined in afxanimationcontroller.h
    • CAnimationValue
    • CAnimationPoint
    • CAnimationSize
    • CAnimationColor
    • CAnimationRect
  5. add transition(s) to animation object; here is a list of transition MFC classes:
    • CAccelerateDecelerateTransition
    • CConstantTransition
    • CCubicTransition
    • CDiscreteTransition
    • CInstantaneousTransition
    • CLinearTransition
    • CLinearTransitionFromSpeed
    • CSmoothStopTransition
    • CParabolicTransitionFromAcceleration
    • CReversalTransition
    • CSinusoidalTransitionFromRange
    • CSinusoidalTransitionFromVelocity
    • CSinusoidalTransitionFromVelocity
  6. uncomment the line containing m_spAnimCrossFadeWeightValue->GetValue(m_dWeight) in the previous example;
  7. finally, call CAnimationController::AnimateGroup.

Here is some sample code:

void CAnimationWnd::PreSubclassWindow()
{
    // ...

    // this window receive WM_PAINT when animation manager state has changed 
    // or timer post update event has occurred
    m_animController.SetRelatedWnd(this);
    // set a handler for timing events and handler for timing updates
    m_animController.EnableAnimationTimerEventHandler();

    m_spAnimCrossFadeWeightValue = std::make_unique<CAnimationValue>(DEMO_DEFAULT_CROSSFADE_WEIGHT_VALUE, DEMO_GROUP_ID);
    m_animController.AddAnimationObject(m_spAnimCrossFadeWeightValue.get());

    // ...
}

void CAnimationWnd::Animate()
{
    // ...

    // clear existing transitions
    m_spAnimCrossFadeWeightValue->ClearTransitions(TRUE);

    // set initial animation value (here is 1.0f)
    *m_spAnimCrossFadeWeightValue = DEMO_MAX_CROSSFADE_WEIGHT_VALUE;
    // add linear transition to minimum value (0.0f in our case)
    m_spAnimCrossFadeWeightValue->AddTransition(new CLinearTransition(DEMO_TRANSITION_DURATION,
        DEMO_MIN_CROSSFADE_WEIGHT_VALUE));

    // add constant transition; the value remains constant during given duration
    m_spAnimCrossFadeWeightValue->AddTransition(new CConstantTransition(DEMO_TRANSITION_DURATION));

    // add linear transition to maximum value (1.0f)
    m_spAnimCrossFadeWeightValue->AddTransition(new CLinearTransition(DEMO_TRANSITION_DURATION,
        DEMO_MAX_CROSSFADE_WEIGHT_VALUE));

    // run animation
    m_animController.AnimateGroup(DEMO_GROUP_ID);
}

More details can be found in the attached demo project.

Demo project

Download: MFC Support for Animation Demo.zip (42)

Cross Fade Effect with Animation 1

Cross Fade Effect with Animation 1

Cross Fade Effect with Animation 2

Cross Fade Effect with Animation 2

Cross Fade Effect with Animation 3

Cross Fade Effect with Animation 3

Resources and related articles

Codexpert – 2017 Articles Summary

$
0
0
Viewing all 44 articles
Browse latest View live