// 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 #include "SimpleDirect2dApplication.h" // // Provides the entry point to the application. // int WINAPI WinMain( HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, LPSTR /* lpCmdLine */, int /* nCmdShow */ ) { // Ignore the return value because we want to continue running 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; } // // Initialize members. // DemoApp::DemoApp() : m_hwnd(NULL), m_pD2DFactory(NULL), m_pWICFactory(NULL), m_pDWriteFactory(NULL), m_pRenderTarget(NULL), m_pTextFormat(NULL), m_pPathGeometry(NULL), m_pLinearGradientBrush(NULL), m_pBlackBrush(NULL), m_pGridPatternBitmapBrush(NULL), m_pBitmap(NULL), m_pAnotherBitmap(NULL) { } // // Release resources. // DemoApp::~DemoApp() { SafeRelease(&m_pD2DFactory); SafeRelease(&m_pWICFactory); SafeRelease(&m_pDWriteFactory); SafeRelease(&m_pRenderTarget); SafeRelease(&m_pTextFormat); SafeRelease(&m_pPathGeometry); SafeRelease(&m_pLinearGradientBrush); SafeRelease(&m_pBlackBrush); SafeRelease(&m_pGridPatternBitmapBrush); SafeRelease(&m_pBitmap); SafeRelease(&m_pAnotherBitmap); } // // Creates the application window and initializes // 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); // Create the application window. m_hwnd = CreateWindow( L"D2DDemoApp", L"Direct2D Demo Application", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, static_cast(ceil(640.f * dpiX / 96.f)), static_cast(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; } // // Create resources which are not bound // to any device. Their lifetime effectively extends for the // duration of the app. These resources include the Direct2D, // DirectWrite, and WIC factories; and a DirectWrite Text Format object // (used for identifying particular font characteristics) and // a Direct2D geometry. // HRESULT DemoApp::CreateDeviceIndependentResources() { static const WCHAR msc_fontName[] = L"Verdana"; static const FLOAT msc_fontSize = 50; HRESULT hr; ID2D1GeometrySink *pSink = NULL; // Create a Direct2D factory. hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory); if (SUCCEEDED(hr)) { // Create WIC factory. hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, reinterpret_cast(&m_pWICFactory) ); } if (SUCCEEDED(hr)) { // Create a DirectWrite factory. hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(m_pDWriteFactory), reinterpret_cast(&m_pDWriteFactory) ); } if (SUCCEEDED(hr)) { // Create a DirectWrite text format object. hr = m_pDWriteFactory->CreateTextFormat( msc_fontName, NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, msc_fontSize, L"", //locale &m_pTextFormat ); } if (SUCCEEDED(hr)) { // Center the text horizontally and vertically. m_pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); m_pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER); // Create a path geometry. hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometry); } if (SUCCEEDED(hr)) { // Use the geometry sink to write to the path geometry. hr = m_pPathGeometry->Open(&pSink); } if (SUCCEEDED(hr)) { pSink->SetFillMode(D2D1_FILL_MODE_ALTERNATE); pSink->BeginFigure( D2D1::Point2F(0, 0), D2D1_FIGURE_BEGIN_FILLED ); pSink->AddLine(D2D1::Point2F(200, 0)); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(150, 50), D2D1::Point2F(150, 150), D2D1::Point2F(200, 200)) ); pSink->AddLine(D2D1::Point2F(0, 200)); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(50, 150), D2D1::Point2F(50, 50), D2D1::Point2F(0, 0)) ); pSink->EndFigure(D2D1_FIGURE_END_CLOSED); hr = pSink->Close(); } SafeRelease(&pSink); return hr; } // // This method creates resources which are bound to a particular // Direct3D device. It's all centralized here, in case the resources // need to be recreated in case of Direct3D 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( static_cast(rc.right - rc.left), static_cast(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)) { // Create a black brush. hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::Black), &m_pBlackBrush ); } if (SUCCEEDED(hr)) { ID2D1GradientStopCollection *pGradientStops = NULL; // Create a linear gradient. static const D2D1_GRADIENT_STOP stops[] = { { 0.f, { 0.f, 1.f, 1.f, 0.25f } }, { 1.f, { 0.f, 0.f, 1.f, 1.f } }, }; hr = m_pRenderTarget->CreateGradientStopCollection( stops, ARRAYSIZE(stops), &pGradientStops ); if (SUCCEEDED(hr)) { hr = m_pRenderTarget->CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties( D2D1::Point2F(100, 0), D2D1::Point2F(100, 200)), D2D1::BrushProperties(), pGradientStops, &m_pLinearGradientBrush ); pGradientStops->Release(); } } // Create a bitmap from an application resource. hr = LoadResourceBitmap( m_pRenderTarget, m_pWICFactory, L"SampleImage", L"Image", 100, 0, &m_pBitmap ); if (SUCCEEDED(hr)) { // Create a bitmap by loading it from a file. hr = LoadBitmapFromFile( m_pRenderTarget, m_pWICFactory, L".\\sampleImage.jpg", 100, 0, &m_pAnotherBitmap ); } if (SUCCEEDED(hr)) { hr = CreateGridPatternBrush(m_pRenderTarget, &m_pGridPatternBitmapBrush); } } return hr; } // // Creates a pattern brush. // 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.1f, 1.0f, 10.0f), pGridBrush); 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; } // // Discard device-specific resources which need to be recreated // when a Direct3D device is lost // void DemoApp::DiscardDeviceResources() { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pBitmap); SafeRelease(&m_pBlackBrush); SafeRelease(&m_pLinearGradientBrush); SafeRelease(&m_pAnotherBitmap); SafeRelease(&m_pGridPatternBitmapBrush); } // // The main window message loop. // void DemoApp::RunMessageLoop() { MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // // Called whenever the application needs to display the client // window. This method draws a bitmap a couple times, draws some // geometries, and writes "Hello, World" // // Note that this function will not render anything if the window // is occluded (e.g. when the screen is locked). // Also, this function will automatically discard device-specific // resources if the Direct3D device disappears during function // invocation, and will recreate the resources the next time it's // invoked. // HRESULT DemoApp::OnRender() { HRESULT hr; hr = CreateDeviceResources(); if (SUCCEEDED(hr) && !(m_pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED)) { static const WCHAR sc_helloWorld[] = L"Hello, World!"; // Retrieve the size of the render target. D2D1_SIZE_F renderTargetSize = m_pRenderTarget->GetSize(); m_pRenderTarget->BeginDraw(); m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); // Paint a grid background. m_pRenderTarget->FillRectangle( D2D1::RectF(0.0f, 0.0f, renderTargetSize.width, renderTargetSize.height), m_pGridPatternBitmapBrush ); D2D1_SIZE_F size = m_pBitmap->GetSize(); // Draw a bitmap in the upper-left corner of the window. m_pRenderTarget->DrawBitmap( m_pBitmap, D2D1::RectF(0.0f, 0.0f, size.width, size.height) ); // Draw a bitmap at the lower-right corner of the window. size = m_pAnotherBitmap->GetSize(); m_pRenderTarget->DrawBitmap( m_pAnotherBitmap, D2D1::RectF( renderTargetSize.width - size.width, renderTargetSize.height - size.height, renderTargetSize.width, renderTargetSize.height) ); // Set the world transform to a 45 degree rotation at the center of the render target // and write "Hello, World". m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Rotation( 45, D2D1::Point2F( renderTargetSize.width / 2, renderTargetSize.height / 2)) ); m_pRenderTarget->DrawText( sc_helloWorld, ARRAYSIZE(sc_helloWorld) - 1, m_pTextFormat, D2D1::RectF(0, 0, renderTargetSize.width, renderTargetSize.height), m_pBlackBrush ); // // Reset back to the identity transform // m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation(0, renderTargetSize.height - 200) ); // Fill the hour glass geometry with a gradient. m_pRenderTarget->FillGeometry(m_pPathGeometry, m_pLinearGradientBrush); m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Translation(renderTargetSize.width - 200, 0) ); // Fill the hour glass geometry with a gradient. m_pRenderTarget->FillGeometry(m_pPathGeometry, m_pLinearGradientBrush); hr = m_pRenderTarget->EndDraw(); if (hr == D2DERR_RECREATE_TARGET) { hr = S_OK; DiscardDeviceResources(); } } return hr; } // // If the application receives a WM_SIZE message, this method // resize the render target appropriately. // void DemoApp::OnResize(UINT width, UINT height) { if (m_pRenderTarget) { D2D1_SIZE_U size; size.width = width; size.height = height; // Note: This method can fail, but it's okay to ignore the // error here -- it will be repeated on the next call to // EndDraw. m_pRenderTarget->Resize(size); } } // // The 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, reinterpret_cast(pDemoApp) ); result = 1; } else { DemoApp *pDemoApp = reinterpret_cast( ::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); } result = 0; wasHandled = true; break; case WM_PAINT: case WM_DISPLAYCHANGE: { PAINTSTRUCT ps; BeginPaint(hwnd, &ps); pDemoApp->OnRender(); EndPaint(hwnd, &ps); } result = 0; wasHandled = true; break; case WM_DESTROY: { PostQuitMessage(0); } result = 1; wasHandled = true; break; } } if (!wasHandled) { result = DefWindowProc(hwnd, message, wParam, lParam); } } return result; } // // Creates a Direct2D bitmap from a resource in the // application resource file. // HRESULT DemoApp::LoadResourceBitmap( ID2D1RenderTarget *pRenderTarget, IWICImagingFactory *pIWICFactory, PCWSTR resourceName, PCWSTR resourceType, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap **ppBitmap ) { HRESULT hr = S_OK; IWICBitmapDecoder *pDecoder = NULL; IWICBitmapFrameDecode *pSource = NULL; IWICStream *pStream = NULL; IWICFormatConverter *pConverter = NULL; IWICBitmapScaler *pScaler = NULL; HRSRC imageResHandle = NULL; HGLOBAL imageResDataHandle = NULL; void *pImageFile = NULL; DWORD imageFileSize = 0; // Locate the resource. imageResHandle = FindResourceW(HINST_THISCOMPONENT, resourceName, resourceType); hr = imageResHandle ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { // Load the resource. imageResDataHandle = LoadResource(HINST_THISCOMPONENT, imageResHandle); hr = imageResDataHandle ? S_OK : E_FAIL; } if (SUCCEEDED(hr)) { // Lock it to get a system memory pointer. pImageFile = LockResource(imageResDataHandle); hr = pImageFile ? S_OK : E_FAIL; } if (SUCCEEDED(hr)) { // Calculate the size. imageFileSize = SizeofResource(HINST_THISCOMPONENT, imageResHandle); hr = imageFileSize ? S_OK : E_FAIL; } if (SUCCEEDED(hr)) { // Create a WIC stream to map onto the memory. hr = pIWICFactory->CreateStream(&pStream); } if (SUCCEEDED(hr)) { // Initialize the stream with the memory pointer and size. hr = pStream->InitializeFromMemory( reinterpret_cast(pImageFile), imageFileSize ); } if (SUCCEEDED(hr)) { // Create a decoder for the stream. hr = pIWICFactory->CreateDecoderFromStream( pStream, NULL, WICDecodeMetadataCacheOnLoad, &pDecoder ); } if (SUCCEEDED(hr)) { // Create the initial frame. hr = pDecoder->GetFrame(0, &pSource); } if (SUCCEEDED(hr)) { // Convert the image format to 32bppPBGRA // (DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED). hr = pIWICFactory->CreateFormatConverter(&pConverter); } if (SUCCEEDED(hr)) { // If a new width or height was specified, create an // IWICBitmapScaler and use it to resize the image. if (destinationWidth != 0 || destinationHeight != 0) { UINT originalWidth, originalHeight; hr = pSource->GetSize(&originalWidth, &originalHeight); if (SUCCEEDED(hr)) { if (destinationWidth == 0) { FLOAT scalar = static_cast(destinationHeight) / static_cast(originalHeight); destinationWidth = static_cast(scalar * static_cast(originalWidth)); } else if (destinationHeight == 0) { FLOAT scalar = static_cast(destinationWidth) / static_cast(originalWidth); destinationHeight = static_cast(scalar * static_cast(originalHeight)); } hr = pIWICFactory->CreateBitmapScaler(&pScaler); if (SUCCEEDED(hr)) { hr = pScaler->Initialize( pSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic ); if (SUCCEEDED(hr)) { hr = pConverter->Initialize( pScaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut ); } } } } else { hr = pConverter->Initialize( pSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut ); } } if (SUCCEEDED(hr)) { //create a Direct2D bitmap from the WIC bitmap. hr = pRenderTarget->CreateBitmapFromWicBitmap( pConverter, NULL, ppBitmap ); } SafeRelease(&pDecoder); SafeRelease(&pSource); SafeRelease(&pStream); SafeRelease(&pConverter); SafeRelease(&pScaler); return hr; } // // Creates a Direct2D bitmap from the specified // file name. // HRESULT DemoApp::LoadBitmapFromFile( ID2D1RenderTarget *pRenderTarget, IWICImagingFactory *pIWICFactory, PCWSTR uri, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap **ppBitmap ) { HRESULT hr = S_OK; IWICBitmapDecoder *pDecoder = NULL; IWICBitmapFrameDecode *pSource = NULL; IWICStream *pStream = NULL; IWICFormatConverter *pConverter = NULL; IWICBitmapScaler *pScaler = NULL; hr = pIWICFactory->CreateDecoderFromFilename( uri, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder ); if (SUCCEEDED(hr)) { // Create the initial frame. hr = pDecoder->GetFrame(0, &pSource); } if (SUCCEEDED(hr)) { // Convert the image format to 32bppPBGRA // (DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED). hr = pIWICFactory->CreateFormatConverter(&pConverter); } if (SUCCEEDED(hr)) { // If a new width or height was specified, create an // IWICBitmapScaler and use it to resize the image. if (destinationWidth != 0 || destinationHeight != 0) { UINT originalWidth, originalHeight; hr = pSource->GetSize(&originalWidth, &originalHeight); if (SUCCEEDED(hr)) { if (destinationWidth == 0) { FLOAT scalar = static_cast(destinationHeight) / static_cast(originalHeight); destinationWidth = static_cast(scalar * static_cast(originalWidth)); } else if (destinationHeight == 0) { FLOAT scalar = static_cast(destinationWidth) / static_cast(originalWidth); destinationHeight = static_cast(scalar * static_cast(originalHeight)); } hr = pIWICFactory->CreateBitmapScaler(&pScaler); if (SUCCEEDED(hr)) { hr = pScaler->Initialize( pSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic ); } if (SUCCEEDED(hr)) { hr = pConverter->Initialize( pScaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut ); } } } else // Don't scale the image. { hr = pConverter->Initialize( pSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut ); } } if (SUCCEEDED(hr)) { // Create a Direct2D bitmap from the WIC bitmap. hr = pRenderTarget->CreateBitmapFromWicBitmap( pConverter, NULL, ppBitmap ); } SafeRelease(&pDecoder); SafeRelease(&pSource); SafeRelease(&pStream); SafeRelease(&pConverter); SafeRelease(&pScaler); return hr; }