DemoApp.cpp
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//+-----------------------------------------------------------------------------
//
// $Description:
// Sample Direct2D Application
//
// $ENDTAG
//
//------------------------------------------------------------------------------
#include "demoapp.h"
/******************************************************************
* *
* WinMain *
* *
* Application entry point *
* *
******************************************************************/
int WINAPI WinMain(
HINSTANCE /* hInstance */,
HINSTANCE /* hPrevInstance */,
LPSTR /* lpCmdLine */,
int /* nCmdShow */
)
{
// Ignore the return value because we want to run the program even in the
// unlikely event that HeapSetInformation fails.
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (SUCCEEDED(CoInitialize(NULL)))
{
{
DemoApp app;
if (SUCCEEDED(app.Initialize()))
{
app.RunMessageLoop();
}
}
CoUninitialize();
}
return 0;
}
/******************************************************************
* *
* DemoApp::DemoApp constructor *
* *
* Initialize member data. *
* *
******************************************************************/
DemoApp::DemoApp() :
m_hwnd(NULL),
m_pD2DFactory(NULL),
m_pRenderTarget(NULL),
m_pOutlineBrush(NULL),
m_pTextBrush(NULL),
m_pShapeFillBrush(NULL),
m_pGridPatternBitmapBrush(NULL),
m_pCircleGeometry1(NULL),
m_pCircleGeometry2(NULL),
m_pPathGeometryUnion(NULL),
m_pPathGeometryIntersect(NULL),
m_pPathGeometryXOR(NULL),
m_pPathGeometryExclude(NULL),
m_pDWriteFactory(NULL),
m_pTextFormat(NULL),
m_pStrokeStyle(NULL)
{
}
/******************************************************************
* *
* DemoApp::~DemoApp destructor *
* *
* Release resources. *
* *
******************************************************************/
DemoApp::~DemoApp()
{
SafeRelease(&m_pD2DFactory);
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pOutlineBrush);
SafeRelease(&m_pTextBrush);
SafeRelease(&m_pShapeFillBrush);
SafeRelease(&m_pGridPatternBitmapBrush);
SafeRelease(&m_pCircleGeometry1);
SafeRelease(&m_pCircleGeometry2);
SafeRelease(&m_pPathGeometryUnion);
SafeRelease(&m_pPathGeometryIntersect);
SafeRelease(&m_pPathGeometryXOR);
SafeRelease(&m_pPathGeometryExclude);
SafeRelease(&m_pDWriteFactory);
SafeRelease(&m_pTextFormat);
SafeRelease(&m_pStrokeStyle);
}
/******************************************************************
* *
* DemoApp::Initialize *
* *
* Create application window and device-independent resources. *
* *
******************************************************************/
HRESULT DemoApp::Initialize()
{
HRESULT hr;
// Initialize device-indpendent resources, such
// as the Direct2D factory.
hr = CreateDeviceIndependentResources();
if (SUCCEEDED(hr))
{
// Register the window class.
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DemoApp::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = HINST_THISCOMPONENT;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpszClassName = L"D2DDemoApp";
RegisterClassEx(&wcex);
// Create the application window.
//
// Because the CreateWindow function takes its size in pixels, we
// obtain the system DPI and use it to scale the window size.
FLOAT dpiX, dpiY;
m_pD2DFactory->GetDesktopDpi(&dpiX, &dpiY);
m_hwnd = CreateWindow(
L"D2DDemoApp",
L"Direct2D Combine Geometries",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
NULL,
NULL,
HINST_THISCOMPONENT,
this
);
hr = m_hwnd ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
ShowWindow(m_hwnd, SW_SHOWNORMAL);
UpdateWindow(m_hwnd);
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::CreateDeviceIndependentResources *
* *
* This method is used to create resources which are not bound *
* to any device. Their lifetime effectively extends for the *
* duration of the app. *
* *
******************************************************************/
HRESULT DemoApp::CreateDeviceIndependentResources()
{
HRESULT hr;
// Create a Direct2D factory.
hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&m_pD2DFactory
);
if (SUCCEEDED(hr))
{
// Create a shared DirectWrite factory.
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&m_pDWriteFactory)
);
}
if (SUCCEEDED(hr))
{
// Create a DirectWrite text format object.
hr = m_pDWriteFactory->CreateTextFormat(
L"Verdana", // The font family name.
NULL, // The font collection (NULL sets it to use the system font collection).
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
10.0f,
L"en-us",
&m_pTextFormat
);
}
if (SUCCEEDED(hr))
{
hr = CreateGeometryResources();
}
if (SUCCEEDED(hr))
{
float dashes[] = {1.f, 1.f, 2.f, 3.f, 5.f};
m_pD2DFactory->CreateStrokeStyle(
D2D1::StrokeStyleProperties(
D2D1_CAP_STYLE_FLAT,
D2D1_CAP_STYLE_FLAT,
D2D1_CAP_STYLE_ROUND,
D2D1_LINE_JOIN_ROUND, // lineJoin
10.f, //miterLimit
D2D1_DASH_STYLE_CUSTOM,
0.f //dashOffset
),
dashes,
ARRAYSIZE(dashes)-1,
&m_pStrokeStyle
);
}
return hr;
}
HRESULT DemoApp::CreateGeometryResources()
{
HRESULT hr = S_OK;
ID2D1GeometrySink *pGeometrySink = NULL;
// Create the first ellipse geometry to merge.
const D2D1_ELLIPSE circle1 = D2D1::Ellipse(
D2D1::Point2F(75.0f, 75.0f),
50.0f,
50.0f
);
hr = m_pD2DFactory->CreateEllipseGeometry(
circle1,
&m_pCircleGeometry1
);
if (SUCCEEDED(hr))
{
// Create the second ellipse geometry to merge.
const D2D1_ELLIPSE circle2 = D2D1::Ellipse(
D2D1::Point2F(125.0f, 75.0f),
50.0f,
50.0f
);
hr = m_pD2DFactory->CreateEllipseGeometry(circle2, &m_pCircleGeometry2);
}
if (SUCCEEDED(hr))
{
//
// Use D2D1_COMBINE_MODE_UNION to combine the geometries.
//
hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometryUnion);
if (SUCCEEDED(hr))
{
hr = m_pPathGeometryUnion->Open(&pGeometrySink);
if (SUCCEEDED(hr))
{
hr = m_pCircleGeometry1->CombineWithGeometry(
m_pCircleGeometry2,
D2D1_COMBINE_MODE_UNION,
NULL,
NULL,
pGeometrySink
);
}
if (SUCCEEDED(hr))
{
hr = pGeometrySink->Close();
}
SafeRelease(&pGeometrySink);
}
}
if (SUCCEEDED(hr))
{
//
// Use D2D1_COMBINE_MODE_INTERSECT to combine the geometries.
//
hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometryIntersect);
if (SUCCEEDED(hr))
{
hr = m_pPathGeometryIntersect->Open(&pGeometrySink);
if (SUCCEEDED(hr))
{
hr = m_pCircleGeometry1->CombineWithGeometry(
m_pCircleGeometry2,
D2D1_COMBINE_MODE_INTERSECT,
NULL,
NULL,
pGeometrySink
);
}
if (SUCCEEDED(hr))
{
hr = pGeometrySink->Close();
}
SafeRelease(&pGeometrySink);
}
}
if (SUCCEEDED(hr))
{
//
// Use D2D1_COMBINE_MODE_XOR to combine the geometries.
//
hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometryXOR);
if (SUCCEEDED(hr))
{
hr = m_pPathGeometryXOR->Open(&pGeometrySink);
if (SUCCEEDED(hr))
{
hr = m_pCircleGeometry1->CombineWithGeometry(
m_pCircleGeometry2,
D2D1_COMBINE_MODE_XOR,
NULL,
NULL,
pGeometrySink
);
}
if (SUCCEEDED(hr))
{
hr = pGeometrySink->Close();
}
SafeRelease(&pGeometrySink);
}
}
if (SUCCEEDED(hr))
{
//
// Use D2D1_COMBINE_MODE_EXCLUDE to combine the geometries.
//
hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometryExclude);
if (SUCCEEDED(hr))
{
hr = m_pPathGeometryExclude->Open(&pGeometrySink);
if (SUCCEEDED(hr))
{
hr = m_pCircleGeometry1->CombineWithGeometry(
m_pCircleGeometry2,
D2D1_COMBINE_MODE_EXCLUDE,
NULL,
NULL,
pGeometrySink
);
}
if (SUCCEEDED(hr))
{
hr = pGeometrySink->Close();
}
SafeRelease(&pGeometrySink);
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::CreateDeviceResources *
* *
* This method creates resources which are bound to a particular *
* D3D device. It's all centralized here, in case the resources *
* need to be recreated in case of D3D device loss (eg. display *
* change, remoting, removal of video card, etc). *
* *
******************************************************************/
HRESULT DemoApp::CreateDeviceResources()
{
HRESULT hr = S_OK;
if (!m_pRenderTarget)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top
);
// Create a Direct2D render target.
hr = m_pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
if (SUCCEEDED(hr))
{
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::DarkSlateBlue, 1.f),
&m_pOutlineBrush
);
}
if (SUCCEEDED(hr))
{
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::CornflowerBlue, 0.5f),
&m_pShapeFillBrush
);
}
if (SUCCEEDED(hr))
{
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black, 1.f),
&m_pTextBrush
);
}
if (SUCCEEDED(hr))
{
hr = CreateGridPatternBrush(m_pRenderTarget, &m_pGridPatternBitmapBrush);
}
}
return hr;
}
HRESULT DemoApp::CreateGridPatternBrush(
ID2D1RenderTarget *pRenderTarget,
ID2D1BitmapBrush **ppBitmapBrush
)
{
HRESULT hr = S_OK;
// Create a compatible render target.
ID2D1BitmapRenderTarget *pCompatibleRenderTarget = NULL;
hr = pRenderTarget->CreateCompatibleRenderTarget(
D2D1::SizeF(10.0f, 10.0f),
&pCompatibleRenderTarget
);
if (SUCCEEDED(hr))
{
// Draw a pattern.
ID2D1SolidColorBrush *pGridBrush = NULL;
hr = pCompatibleRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF(0.93f, 0.94f, 0.96f, 1.0f)),
&pGridBrush
);
if (SUCCEEDED(hr))
{
pCompatibleRenderTarget->BeginDraw();
pCompatibleRenderTarget->FillRectangle(D2D1::RectF(0.0f, 0.0f, 10.0f, 1.0f), pGridBrush);
pCompatibleRenderTarget->FillRectangle(D2D1::RectF(0.0f, 0.0f, 1.0f, 10.0f), pGridBrush);
hr = pCompatibleRenderTarget->EndDraw();
// Retrieve the bitmap from the render target.
ID2D1Bitmap *pGridBitmap = NULL;
hr = pCompatibleRenderTarget->GetBitmap(&pGridBitmap);
if (SUCCEEDED(hr))
{
// Choose the tiling mode for the bitmap brush.
D2D1_BITMAP_BRUSH_PROPERTIES brushProperties = D2D1::BitmapBrushProperties(
D2D1_EXTEND_MODE_WRAP,
D2D1_EXTEND_MODE_WRAP
);
// Create the bitmap brush.
hr = m_pRenderTarget->CreateBitmapBrush(pGridBitmap, brushProperties, ppBitmapBrush);
pGridBitmap->Release();
}
pGridBrush->Release();
}
pCompatibleRenderTarget->Release();
}
return hr;
}
/******************************************************************
* *
* DemoApp::DiscardDeviceResources *
* *
* Discard device-specific resources which need to be recreated *
* when a Direct3D device is lost. *
* *
******************************************************************/
void DemoApp::DiscardDeviceResources()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pOutlineBrush);
SafeRelease(&m_pShapeFillBrush);
SafeRelease(&m_pGridPatternBitmapBrush);
SafeRelease(&m_pTextBrush);
}
/******************************************************************
* *
* DemoApp::RunMessageLoop *
* *
* Main window message loop *
* *
******************************************************************/
void DemoApp::RunMessageLoop()
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/******************************************************************
* *
* DemoApp::OnRender *
* *
* Called whenever the application needs to display the client *
* window. *
* *
* Note that this function will automatically discard *
* device-specific resources if the Direct3D device disappears *
* during execution, and will recreate the resources the *
* next time it's invoked. *
* *
******************************************************************/
HRESULT DemoApp::OnRender()
{
HRESULT hr = CreateDeviceResources();
if (SUCCEEDED(hr))
{
static const WCHAR szBeforeText[] = L"The circles before combining";
static const WCHAR szUnionText[] = L"D2D1_COMBINE_MODE_UNION";
static const WCHAR szIntersectText[] = L"D2D1_COMBINE_MODE_INTERSECT";
static const WCHAR szXorText[] = L"D2D1_COMBINE_MODE_XOR";
static const WCHAR szExcludeText[] = L"D2D1_COMBINE_MODE_EXCLUDE";
m_pRenderTarget->BeginDraw();
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize();
m_pRenderTarget->FillRectangle(
D2D1::RectF(0.f, 0.f, renderTargetSize.width, renderTargetSize.height),
m_pGridPatternBitmapBrush
);
// Draw the geomtries before merging.
m_pRenderTarget->FillGeometry(m_pCircleGeometry1, m_pShapeFillBrush);
m_pRenderTarget->DrawGeometry(m_pCircleGeometry1, m_pOutlineBrush, 1.0f);
m_pRenderTarget->FillGeometry(m_pCircleGeometry2, m_pShapeFillBrush);
m_pRenderTarget->DrawGeometry(m_pCircleGeometry2, m_pOutlineBrush, 1.0f);
m_pRenderTarget->DrawText(
szBeforeText,
ARRAYSIZE(szBeforeText) - 1,
m_pTextFormat,
D2D1::RectF(25.0f, 130.0f, 200.0f, 300.0f),
m_pTextBrush
);
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(200, 0));
// Draw the geometries merged using the union combine mode.
m_pRenderTarget->FillGeometry(m_pPathGeometryUnion, m_pShapeFillBrush, NULL);
m_pRenderTarget->DrawGeometry(m_pPathGeometryUnion, m_pOutlineBrush, 1.0f);
m_pRenderTarget->DrawText(
szUnionText,
ARRAYSIZE(szUnionText) - 1,
m_pTextFormat,
D2D1::RectF(25, 130, 200, 300),
m_pTextBrush
);
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(400, 0));
// Draw the geometries merged using the intersect combine mode.
m_pRenderTarget->FillGeometry(m_pPathGeometryIntersect, m_pShapeFillBrush, NULL);
m_pRenderTarget->DrawGeometry(m_pPathGeometryIntersect, m_pOutlineBrush, 1.0f);
m_pRenderTarget->DrawText(
szIntersectText,
ARRAYSIZE(szIntersectText) - 1,
m_pTextFormat,
D2D1::RectF(25, 130, 400, 300),
m_pTextBrush
);
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(200, 150));
// Draw the geometries merged using the XOR combine mode.
m_pRenderTarget->FillGeometry(m_pPathGeometryXOR, m_pShapeFillBrush, NULL);
m_pRenderTarget->DrawGeometry(m_pPathGeometryXOR, m_pOutlineBrush, 1.0f);
m_pRenderTarget->DrawText(
szXorText,
ARRAYSIZE(szXorText) - 1,
m_pTextFormat,
D2D1::RectF(25, 130, 200, 470),
m_pTextBrush
);
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(400, 150));
// Draw the geometries merged using the exclude combine mode.
m_pRenderTarget->FillGeometry(m_pPathGeometryExclude, m_pShapeFillBrush, NULL);
m_pRenderTarget->DrawGeometry(m_pPathGeometryExclude, m_pOutlineBrush, 1.0f);
m_pRenderTarget->DrawText(
szExcludeText,
ARRAYSIZE(szExcludeText) - 1,
m_pTextFormat,
D2D1::RectF(25, 130, 400, 470),
m_pTextBrush
);
// The following code demonstrates how to call various geometric operations. Depending on
// your needs, it lets you decide how to use those output values.
D2D1_GEOMETRY_RELATION result = D2D1_GEOMETRY_RELATION_UNKNOWN;
// Compare circle1 with circle2
hr = m_pCircleGeometry1->CompareWithGeometry(
m_pCircleGeometry2,
D2D1::IdentityMatrix(),
0.1f,
&result
);
if (SUCCEEDED(hr))
{
static const WCHAR szGeometryRelation[] = L"Two circles overlap.";
m_pRenderTarget->SetTransform(D2D1::IdentityMatrix());
if (result == D2D1_GEOMETRY_RELATION_OVERLAP)
{
m_pRenderTarget->DrawText(
szGeometryRelation,
ARRAYSIZE(szGeometryRelation) - 1,
m_pTextFormat,
D2D1::RectF(25.0f, 160.0f, 200.0f, 300.0f),
m_pTextBrush
);
}
}
float area;
// Compute the area of circle1
hr = m_pCircleGeometry1->ComputeArea(
D2D1::IdentityMatrix(),
&area
);
float length;
// Compute the area of circle1
hr = m_pCircleGeometry1->ComputeLength(
D2D1::IdentityMatrix(),
&length
);
if (SUCCEEDED(hr))
{
// Process the length of the geometry.
}
D2D1_POINT_2F point;
D2D1_POINT_2F tangent;
hr = m_pCircleGeometry1->ComputePointAtLength(
10,
NULL,
&point,
&tangent);
if (SUCCEEDED(hr))
{
// Retrieve the point and tangent point.
}
D2D1_RECT_F bounds;
hr = m_pCircleGeometry1->GetBounds(
D2D1::IdentityMatrix(),
&bounds
);
if (SUCCEEDED(hr))
{
// Retrieve the bounds.
}
D2D1_RECT_F bounds1;
hr = m_pCircleGeometry1->GetWidenedBounds(
5.0,
m_pStrokeStyle,
D2D1::IdentityMatrix(),
&bounds1
);
if (SUCCEEDED(hr))
{
// Retrieve the widened bounds.
}
BOOL containsPoint;
hr = m_pCircleGeometry1->StrokeContainsPoint(
D2D1::Point2F(0,0),
10, // stroke width
NULL, // stroke style
NULL, // world transform
&containsPoint
);
if (SUCCEEDED(hr))
{
// Process containsPoint.
}
BOOL containsPoint1;
hr = m_pCircleGeometry1->FillContainsPoint(
D2D1::Point2F(0,0),
D2D1::Matrix3x2F::Identity(),
&containsPoint1
);
if (SUCCEEDED(hr))
{
// Process containsPoint.
}
hr = m_pRenderTarget->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
DiscardDeviceResources();
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::OnResize *
* *
* If the application receives a WM_SIZE message, this method *
* resizes the render target appropriately. *
* *
******************************************************************/
void DemoApp::OnResize(UINT width, UINT height)
{
if (m_pRenderTarget)
{
// Note: This method can fail, but it's okay to ignore the
// error here -- the error will be repeated on the next call to
// EndDraw.
m_pRenderTarget->Resize(D2D1::SizeU(width, height));
}
}
/******************************************************************
* *
* DemoApp::WndProc *
* *
* Window message handler *
* *
******************************************************************/
LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
if (message == WM_CREATE)
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
DemoApp *pDemoApp = (DemoApp *)pcs->lpCreateParams;
::SetWindowLongPtrW(
hwnd,
GWLP_USERDATA,
PtrToUlong(pDemoApp)
);
result = 1;
}
else
{
DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(static_cast<LONG_PTR>(
::GetWindowLongPtrW(
hwnd,
GWLP_USERDATA
)));
bool wasHandled = false;
if (pDemoApp)
{
switch (message)
{
case WM_SIZE:
{
UINT width = LOWORD(lParam);
UINT height = HIWORD(lParam);
pDemoApp->OnResize(width, height);
}
wasHandled = true;
result = 0;
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect(hwnd, NULL, FALSE);
}
wasHandled = true;
result = 0;
break;
case WM_PAINT:
{
pDemoApp->OnRender();
ValidateRect(hwnd, NULL);
}
wasHandled = true;
result = 0;
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
wasHandled = true;
result = 1;
break;
}
}
if (!wasHandled)
{
result = DefWindowProc(hwnd, message, wParam, lParam);
}
}
return result;
}