Quantcast
Channel: Direct2D | codexpert blog
Viewing all 29 articles
Browse latest View live

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

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


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

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

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

MFC Support for Direct2D – Part 8: Lost Render Target

$
0
0

As stated in the Direct2D documentation, the graphics device might become unavailable. If the device is lost, the render target also becomes invalid, along with any device-dependent resources that were associated with the device. That happens infrequently (e.g. if the user removes the display adapter), but once something bad can happen, a good programmer must handle it, except case he/she likes to see happy QA-tester faces. 🙂

Handling render target loss when using raw Direct2D interfaces

Direct2D signals a lost device by returning the error code D2DERR_RECREATE_TARGET from the ID2D1RenderTarget::EndDraw method. This case we have to release the render target and all its associated device-dependent resources (bitmaps, brushes and so on) in order to be further re-created. Here is a simplified example:

HRESULT CSomeWindow::OnRender()
{
    HRESULT hr = S_OK;
    m_pRenderTarget->BeginDraw();

    // perform Direct2D drawing here...

    hr = m_pRenderTarget->EndDraw();
    if (D2DERR_RECREATE_TARGET == hr)
    {
        // The render target has become invalid, along with its 
        // associated resources, so we have to discard all of them in order to be
        // further re-created.
        DiscardDeviceResources();
        hr = S_OK;
    }
    return hr;
}

In a real program it’s a lot of work to do (discard, then re-create the render target and resources) but fortunately, the programmer’s life becomes much easier if he/she is using MFC.

Handling render target loss when using MFC

MFC does the work for you: if CRenderTarget::EndDraw function returns D2DERR_RECREATE_TARGET, the framework calls CRenderTarget::ReCreate which discards the render target, then re-creates all its associated resources. Additionally, it sends AFX_WM_RECREATED2DRESOURCES registered message. So usually, we have nothing to do in this case. At most we can handle AFX_WM_RECREATED2DRESOURCES, for example, in order to write what happened in the application’s log.

///////////////////////////////////////////////////////////////////////////////////////////////////
// Function:    CDemoView::OnAfxRecreated2DResources
// Purpose:     AFX_WM_RECREATED2DRESOURCES message handler, sent by MFC framework to indicate
//              that render target has been lost and re-created.
//
LRESULT CDemoView::OnAfxRecreated2DResources(WPARAM wParam, LPARAM lParam)
{
    // TODO: write in the application's log!
    return 0;
}

A little CD2DBitmapBrush issue

Doing tests on Direct2D resources re-creation, I have discovered a little issue in CD2DBitmapBrush MFC class. It always re-creates the bitmap brush using D2D1_EXTEND_MODE_CLAMP. Usually, we use a bitmap brush to fill an area by repeating its content (i.e. having D2D1_EXTEND_MODE_WRAP extend mode) so I fix it by overriding CD2DBitmapBrush::ReCreate, as shown below.

class CD2DBitmapBrushEx : public CD2DBitmapBrush
{
public:
    using CD2DBitmapBrush::CD2DBitmapBrush;
    virtual HRESULT ReCreate(CRenderTarget* pRenderTarget) override
    {
        // do not call __super::ReCreate!
        CD2DBrush::Destroy();
        return Create(pRenderTarget);
    }
};

Demo project

The demo project is a simple MFC application which uses a Direct2D bitmap brush to fill the window’s background. In case the render target was previously lost and re-created, it also displays a message. Because the render target loss is quite hard to be reproduced, for testing purpose, you can hit the Test/Re-create render target menu item.

Lost Render Target demo

Lost Render Target demo

Download: Lost Render Target Demo (19)

Resources

MFC Support for Direct2D – Transforms (1)

$
0
0

Direct2D supports linear transforms like translation, scale, rotation and skew.
If using MFC, we can apply transforms to render targets by calling CRenderTarget::SetTransform. Here is an example that shifts the render target 100 points on x-axis and 150 points on y-axis.

FLOAT X = 100.f, Y = 150.f;
    D2D1_MATRIX_3X2_F matrix{ 1.f, 0.f, 0.f, 1.f, X, Y };
    pRenderTarget->SetTransform(matrix);

It looks not very handy to fill the matrix structure, so Matrix3x2F utility class can make it easier.

// ...
    D2D1_MATRIX_3X2_F matrix = D2D1::Matrix3x2F::Translation(X, Y);
    pRenderTarget->SetTransform(matrix);

Let’s begin to present each render target transform with a little bit less trivial examples.

Identity transform

Let’s say that we have to draw a picture in the default render target origin (top-left corner) and then, the same image moved at some x and y coordinates.
For first image, call CRenderTarget::SetTransform with an identity matrix, in order to reset the render target transforms. That’s it, any object will be drawn with its unchanged position, shape and size.

void CTranslationView::DrawImages(CRenderTarget* pRenderTarget)
{
    // Set the destination rectangle based on original bitimap  size 
    CD2DSizeF size = m_pImageBitmap->GetSize();
    CD2DRectF rcDraw(0, 0, size.width, size.height);
    
    // Apply the identity transform to the render target.
    D2D1_MATRIX_3X2_F identityMatrix = D2D1::Matrix3x2F::Identity();
    pRenderTarget->SetTransform(identityMatrix);
    // Draw the image at (0, 0) coordinates
    pRenderTarget->DrawBitmap(m_pImageBitmap, rcDraw, 0.25f);
    // ...
}

Translation transform

Now let’s complete the above code and draw the second image.

void CTranslationView::DrawImages(CRenderTarget* pRenderTarget)
{
    // ...
    // Apply the translation transform to the render target.
    D2D1_MATRIX_3X2_F translationMatrix = 
        D2D1::Matrix3x2F::Translation(m_fXAxis, m_fYAxis);[download id="73"]
    pRenderTarget->SetTransform(translationMatrix);
    // Draw the image at (m_fXAxis, m_fYAxis) coordinates
    pRenderTarget->DrawBitmap(m_pImageBitmap, rcDraw);
}

Someone can say: “Why applying render target transforms, while we can simply recalculate the destination drawing rectangle?” Well, in this simple case it’s easy to be done, but in the real programming world may be required to render much more complicated objects, and recalculating the drawing parameters may be an overkill. No mention the case of the other transform types (scale, rotation and skew)…

Demo application

Download: Direct2DTranslationDemo.zip (12)

Direct2D Translation Demo

Direct2D Translation Demo

Resources and related articles

(to be continued)


Codexpert blog has been moved to a new location

Viewing all 29 articles
Browse latest View live