微软文档:Geometries overview
本篇通过官方文档学习,整理出来的demo,初始样本请先创建一个普通的desktop app。
// Test_Direct2D_Brush.cpp : Defines the entry point for the application. // #include "framework.h" #include "Test_Direct2D_Brush.h" #include <d2d1.h> #include <wincodec.h> #pragma comment(lib, "D2d1.lib") #pragma comment(lib, "Windowscodecs.lib") #define MAX_LOADSTRING 100 template <class T> void SafeRelease(T** ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } } HWND hWnd; ID2D1Factory* l; ID2D1HwndRenderTarget* m_pRenderTarget; ID2D1SolidColorBrush* m_pSceneBrush; ID2D1GradientStopCollection* pGradientStops = NULL; ID2D1RadialGradientBrush* m_pRadialGradientBrush; ID2D1EllipseGeometry* m_pEllipseGeometry1, * m_pEllipseGeometry2, * m_pEllipseGeometry3, * m_pEllipseGeometry4, *m_pCircleGeometry1,*m_pCircleGeometry2; ID2D1GeometryGroup* m_pGeoGroup_AlternateFill,*m_pGeoGroup_WindingFill; ID2D1RectangleGeometry* m_pRectangleGeometry; ID2D1TransformedGeometry *m_pTransformedGeometry; ID2D1GeometrySink* pGeometrySink = NULL; ID2D1PathGeometry* m_pPathGeometryUnion,* m_pLeftMountainGeometry,* m_pRightMountainGeometry,* m_pSunGeometry,* m_pRiverGeometry; ID2D1Geometry* pGeometry; ID2D1SimplifiedGeometrySink* pGeometrySink1; // Global Variables: HINSTANCE hInst; // current instance WCHAR szTitle[MAX_LOADSTRING]; // The title bar text WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name // Forward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); void Render(); void DiscardGraphicsResources(); void Geometry(); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: Place code here. // Initialize global strings LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_TESTDIRECT2DBRUSH, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTDIRECT2DBRUSH)); MSG msg; // Main message loop: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTDIRECT2DBRUSH)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TESTDIRECT2DBRUSH); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&wcex); } // // FUNCTION: InitInstance(HINSTANCE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // Store instance handle in our global variable HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &l); hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } // // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { int wmId = LOWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: { // PAINTSTRUCT ps; // HDC hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code that uses hdc here... Render(); ValidateRect(hWnd, NULL); // EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Message handler for about box. INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } void Render() { RECT rc; GetClientRect(hWnd, &rc); D2D1_SIZE_U size = D2D1::SizeU( rc.right - rc.left, rc.bottom - rc.top ); HRESULT hr = l->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hWnd, size), &m_pRenderTarget ); m_pRenderTarget->BeginDraw(); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); /*************************************************************************************/ Geometry(); /*************************************************************************************/ hr = m_pRenderTarget->EndDraw(); if (FAILED(hr) || hr == D2DERR_RECREATE_TARGET) { DiscardGraphicsResources(); } } void DiscardGraphicsResources() { SafeRelease(&m_pRenderTarget); } void Geometry() { HRESULT hr = l->CreateEllipseGeometry( D2D1::Ellipse(D2D1::Point2F(160.f, 120.f), 25.f, 25.f), &m_pEllipseGeometry1); if (SUCCEEDED(hr)) { hr = l->CreateEllipseGeometry( D2D1::Ellipse(D2D1::Point2F(160.f, 120.f), 50.f, 50.f), &m_pEllipseGeometry2); } if (SUCCEEDED(hr)) { hr = l->CreateEllipseGeometry( D2D1::Ellipse(D2D1::Point2F(160.f, 120.f), 75.f, 75.f), &m_pEllipseGeometry3); } if (SUCCEEDED(hr)) { hr = l->CreateEllipseGeometry( D2D1::Ellipse(D2D1::Point2F(160.f, 120.f), 100.f, 100.f), &m_pEllipseGeometry4); } ID2D1Geometry* ppGeometries[] = { m_pEllipseGeometry1, m_pEllipseGeometry2, m_pEllipseGeometry3, m_pEllipseGeometry4 }; if (SUCCEEDED(hr)) { hr = l->CreateGeometryGroup( D2D1_FILL_MODE_ALTERNATE, ppGeometries, ARRAYSIZE(ppGeometries), &m_pGeoGroup_AlternateFill ); } if (SUCCEEDED(hr)) { hr = l->CreateGeometryGroup( D2D1_FILL_MODE_WINDING, ppGeometries, ARRAYSIZE(ppGeometries), &m_pGeoGroup_WindingFill ); } if (SUCCEEDED(hr)) { hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::Black, 1.0F), &m_pBlackBrush); } // m_pRenderTarget->DrawGeometry(m_pGeoGroup_AlternateFill, m_pBlackBrush, 2); // m_pRenderTarget->DrawGeometry(m_pGeoGroup_WindingFill, m_pBlackBrush, 2); /****************************************************************************************/ //变换后的几何 hr = l->CreateRectangleGeometry( D2D1::RectF(150.f, 150.f, 200.f, 200.f), &m_pRectangleGeometry ); // Draw the untransformed rectangle geometry. // m_pRenderTarget->DrawGeometry(m_pRectangleGeometry, m_pBlackBrush, 1); //Transform the render target, then draw the rectangle geometry again. m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Scale( D2D1::SizeF(3.f, 3.f), D2D1::Point2F(175.f, 175.f)) //上面小矩形的中心点坐标 ); // m_pRenderTarget->DrawGeometry(m_pRectangleGeometry, m_pBlackBrush, 1); hr = l->CreateTransformedGeometry( m_pRectangleGeometry, D2D1::Matrix3x2F::Scale( D2D1::SizeF(3.f, 3.f), D2D1::Point2F(175.f, 175.f)), &m_pTransformedGeometry ); // Replace the previous render target transform. m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); //顾名思义,上面的m_pRenderTarget->SetTransform的设置无效了 // Draw the transformed geometry. // m_pRenderTarget->DrawGeometry(m_pTransformedGeometry, m_pBlackBrush, 1); //另一种缩放方法,不同之处在于笔的粗细没有受影响 /*****************************************************************************************/ //组合几何 // Create the first ellipse geometry to merge. const D2D1_ELLIPSE circle1 = D2D1::Ellipse( D2D1::Point2F(75.0f, 75.0f), 50.0f, 50.0f ); hr = l->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 = l->CreateEllipseGeometry(circle2, &m_pCircleGeometry2); } if (SUCCEEDED(hr)) { // // Use D2D1_COMBINE_MODE_UNION to combine the geometries. // hr = l->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); } } // m_pRenderTarget->DrawGeometry(m_pPathGeometryUnion, m_pBlackBrush, 1); // https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-geometries-overview#widen // widen方法还没实现 /* hr = m_pPathGeometryUnion->Open(&pGeometrySink); if (SUCCEEDED(hr)) { hr = pGeometry->Widen( 2, NULL, NULL, pGeometrySink1 );*/ /****************************************************************************/ //Create a Complex Drawing //左边山 hr = l->CreatePathGeometry(&m_pLeftMountainGeometry); ID2D1GeometrySink* pSink = NULL; hr = m_pLeftMountainGeometry->Open(&pSink); pSink->SetFillMode(D2D1_FILL_MODE_WINDING); pSink->BeginFigure( D2D1::Point2F(346, 255), D2D1_FIGURE_BEGIN_FILLED ); D2D1_POINT_2F points[5] = { D2D1::Point2F(267, 177), D2D1::Point2F(236, 192), D2D1::Point2F(212, 160), D2D1::Point2F(156, 255), D2D1::Point2F(346, 255), }; pSink->AddLines(points, ARRAYSIZE(points)); pSink->EndFigure(D2D1_FIGURE_END_CLOSED); hr = pSink->Close(); //右边山 hr = l->CreatePathGeometry(&m_pRightMountainGeometry); if (SUCCEEDED(hr)) { hr = m_pRightMountainGeometry->Open(&pSink); if (SUCCEEDED(hr)) { pSink->SetFillMode(D2D1_FILL_MODE_WINDING); pSink->BeginFigure( D2D1::Point2F(575, 263), D2D1_FIGURE_BEGIN_FILLED ); D2D1_POINT_2F points[] = { D2D1::Point2F(481, 146), D2D1::Point2F(449, 181), D2D1::Point2F(433, 159), D2D1::Point2F(401, 214), D2D1::Point2F(381, 199), D2D1::Point2F(323, 263), D2D1::Point2F(575, 263) }; pSink->AddLines(points, ARRAYSIZE(points)); pSink->EndFigure(D2D1_FIGURE_END_CLOSED); } hr = pSink->Close(); } //太阳 hr = l->CreatePathGeometry(&m_pSunGeometry); if (SUCCEEDED(hr)) { hr = m_pSunGeometry->Open(&pSink); if (SUCCEEDED(hr)) { pSink->SetFillMode(D2D1_FILL_MODE_WINDING); pSink->BeginFigure( D2D1::Point2F(270, 255), D2D1_FIGURE_BEGIN_FILLED ); pSink->AddArc( D2D1::ArcSegment( D2D1::Point2F(440, 255), // end point D2D1::SizeF(85, 85), 0.0f, // rotation angle D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL )); pSink->EndFigure(D2D1_FIGURE_END_CLOSED); pSink->BeginFigure( D2D1::Point2F(299, 182), D2D1_FIGURE_BEGIN_HOLLOW ); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(299, 182), D2D1::Point2F(294, 176), D2D1::Point2F(285, 178) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(276, 179), D2D1::Point2F(272, 173), D2D1::Point2F(272, 173) )); pSink->EndFigure(D2D1_FIGURE_END_OPEN); pSink->BeginFigure( D2D1::Point2F(354, 156), D2D1_FIGURE_BEGIN_HOLLOW ); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(354, 156), D2D1::Point2F(358, 149), D2D1::Point2F(354, 142) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(349, 134), D2D1::Point2F(354, 127), D2D1::Point2F(354, 127) )); pSink->EndFigure(D2D1_FIGURE_END_OPEN); pSink->BeginFigure( D2D1::Point2F(322, 164), D2D1_FIGURE_BEGIN_HOLLOW ); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(322, 164), D2D1::Point2F(322, 156), D2D1::Point2F(314, 152) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(306, 149), D2D1::Point2F(305, 141), D2D1::Point2F(305, 141) )); pSink->EndFigure(D2D1_FIGURE_END_OPEN); pSink->BeginFigure( D2D1::Point2F(385, 164), D2D1_FIGURE_BEGIN_HOLLOW ); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(385, 164), D2D1::Point2F(392, 161), D2D1::Point2F(394, 152) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(395, 144), D2D1::Point2F(402, 141), D2D1::Point2F(402, 142) )); pSink->EndFigure(D2D1_FIGURE_END_OPEN); pSink->BeginFigure( D2D1::Point2F(408, 182), D2D1_FIGURE_BEGIN_HOLLOW ); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(408, 182), D2D1::Point2F(416, 184), D2D1::Point2F(422, 178) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(428, 171), D2D1::Point2F(435, 173), D2D1::Point2F(435, 173) )); pSink->EndFigure(D2D1_FIGURE_END_OPEN); } hr = pSink->Close(); } //河流 hr = l->CreatePathGeometry(&m_pRiverGeometry); if (SUCCEEDED(hr)) { hr = m_pRiverGeometry->Open(&pSink); if (SUCCEEDED(hr)) { pSink->SetFillMode(D2D1_FILL_MODE_WINDING); pSink->BeginFigure( D2D1::Point2F(183, 392), D2D1_FIGURE_BEGIN_FILLED ); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(238, 284), D2D1::Point2F(472, 345), D2D1::Point2F(356, 303) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(237, 261), D2D1::Point2F(333, 256), D2D1::Point2F(333, 256) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(335, 257), D2D1::Point2F(241, 261), D2D1::Point2F(411, 306) )); pSink->AddBezier( D2D1::BezierSegment( D2D1::Point2F(574, 350), D2D1::Point2F(288, 324), D2D1::Point2F(296, 392) )); pSink->EndFigure(D2D1_FIGURE_END_OPEN); } hr = pSink->Close(); SafeRelease(&pSink); } //着色 m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); //D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize(); //m_pRenderTarget->FillRectangle( // D2D1::RectF(0, 0, rtSize.width, rtSize.height), // m_pGridPatternBitmapBrush //); D2D1_GRADIENT_STOP gradientStops[2]; gradientStops[0].color = D2D1::ColorF(D2D1::ColorF::Yellow, 1); gradientStops[0].position = 0.0f; gradientStops[1].color = D2D1::ColorF(D2D1::ColorF::OrangeRed, 1); gradientStops[1].position = 1.0f; hr = m_pRenderTarget->CreateGradientStopCollection( gradientStops, 2, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, &pGradientStops ); m_pRenderTarget->CreateRadialGradientBrush( D2D1::RadialGradientBrushProperties( D2D1::Point2F(355, 255), D2D1::Point2F(0, 0), 85, 85), pGradientStops, &m_pRadialGradientBrush ); hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::Black, 1.0F), &m_pSceneBrush); m_pRenderTarget->FillGeometry(m_pSunGeometry, m_pRadialGradientBrush); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black, 1.f)); m_pRenderTarget->DrawGeometry(m_pSunGeometry, m_pSceneBrush, 1.f); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::OliveDrab, 1.f)); m_pRenderTarget->FillGeometry(m_pLeftMountainGeometry, m_pSceneBrush); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black, 1.f)); m_pRenderTarget->DrawGeometry(m_pLeftMountainGeometry, m_pSceneBrush, 1.f); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::LightSkyBlue, 1.f)); m_pRenderTarget->FillGeometry(m_pRiverGeometry, m_pSceneBrush); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black, 1.f)); m_pRenderTarget->DrawGeometry(m_pRiverGeometry, m_pSceneBrush, 1.f); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::YellowGreen, 1.f)); m_pRenderTarget->FillGeometry(m_pRightMountainGeometry, m_pSceneBrush); m_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black, 1.f)); m_pRenderTarget->DrawGeometry(m_pRightMountainGeometry, m_pSceneBrush, 1.f); }
效果图:
注意: 代码中创建的各种对象都没有释放,如在实际项目中使用该代码需要在使用完释放它们。
几何部分的内容略多,我仅对几个常用的几何形状的代码进行了整理,并且因为窗口的大小问题,一些绘制其他几何的代码被我注释了,详细可见代码。
拓展:Direct2D有专门的篇幅介绍了缩放问题,见: Scaling geometry realizations