Direct2D教程(四)Path Geometry
概述
Direct2D支持以下几种类型的几何图形,上一篇介绍了简单几何图形,这篇介绍Path geometry。
Simple Geometry(简单几何图形)
- 矩形
- 圆角矩形
- 椭圆
Path Geometry(路径图形)
Composite Geometry(复合图形)
- Geometry Group(图形组)
- Transformed Geometry(变换的图形)
Path geometry,说白了,就是以路径来描述图形,由于翻译过来比较别扭,所以下文中出现该词的地方全部使用英文。Path geometry可以用来创建复杂的几何图形,因为无论多么复杂的图形都可以由一些基本的几何图元来表示,Path geometry中可以使用的基本图元包括圆弧,曲线和直线。先来看一个例子。下面这幅图是一座小山,它的轮廓就是由几条直线组成的。
再来看一个例子,下面是一个太阳,它由三种图元组成,光芒部分由五条曲线表示,太阳本身由一条弧线和一条直线表示。
上面的小山和太阳,都由多个基本图元构成,它们都属于Path geometry,多个Path geometry可以构成更加复杂的图形。
使用path geometry的步骤
创建Path geometry
在D2D中,Path geometry使用接口ID2D1PathGeometry来表示,创建Path geometry使用函数ID2D1Factory::CreatePathGeometry。
ID2D1PathGeometry* pathGeometry = NULL ;
hr = g_pD2DFactory->CreatePathGeometry(&pathGeometry) ;
if (SUCCEEDED(hr))
{
// ...
}
获取ID2D1GeometrySink对象
真正负责创建图形的函数是ID2D1GeometrySink对象,使用Path geometry的Open方法可以获取该对象,使用完毕之后,调用Close函数关闭之。
hr = pathGeometry->Open(&pSink); // 获取Sink对象
if (SUCCEEDED(hr))
{
//添加图形
}
pSink->Close() ; // 关闭Sink对象
使用ID2D1GeometrySink添加图形
添加图形都的代码必须放在ID2D1GeometrySink的BeginFigure函数和EndFigure函数之间。可以添加的图形有直线,弧线,二次贝塞尔曲线和三次贝塞尔曲线。BeginFigure函数有两个参数,第一个参数表示图形的起始点,第二个参数表示图形是填充的还是中空的(只有轮廓,无填充色)。D2D1_FIGURE_BEGIN_FILLED表示填充,D2D1_FIGURE_BEGIN_HOLLOW表示中空。
// 添加图形
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
所以完整的创建代码应该是下面这个样子,主要包括三部分,创建Path geometry,获取Sink对象,添加图形。
ID2D1PathGeometry* pathGeometry = NULL ;
hr = g_pD2DFactory->CreatePathGeometry(&pathGeometry) ;
if (SUCCEEDED(hr))
{
ID2D1GeometrySink *pSink = NULL;
hr = pathGeometry->Open(&pSink); // 获取Sink对象
if (SUCCEEDED(hr))
{
pSink->BeginFigure(D2D1::Point2F(100,100), D2D1_FIGURE_BEGIN_FILLED);
// 添加图形
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
}
pSink->Close() ; // 关闭Sink对象
}
Demo
下面通过一个例子详细讲解如何使用Path geometry,最终的效果图如下,这是D2D SDK中的一个例子。
分析一下这幅图,一共有四个独立的图形,两座小山,一个太阳,一条小溪。小山全部由直线构成。太阳由五条曲线和一条弧线构成。小溪由两条曲线构成。我们将每个图形作为一个独立的path geometry,单独创建。
创建小山
上面说了,小山都由直线构成,所以我们只需给出小山的顶点,在这些顶点之间连线即可,连线可以使用sink对象的AddLines函数,定义如下,该函数有两个参数,第一个是顶点数组,第二个是数组长度。
[in] const D2D1_POINT_2F *points,
UINT pointsCount
) = 0;
创建小山的代码如下,这里只给出左边的小山,右边的小山创建方法相同,只是多了几个顶点而已。
hr = g_pD2DFactory->CreatePathGeometry(&g_pLeftMountainGeometry) ; if (SUCCEEDED(hr)) { ID2D1GeometrySink *pSink = NULL; hr = g_pLeftMountainGeometry->Open(&pSink); if (SUCCEEDED(hr)) { 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); } pSink->Close() ; SAFE_RELEASE(pSink) ; }
创建太阳
太阳由圆弧和贝塞尔曲线构成,创建圆弧使用函数AddArc,它的定义如下。只有一个参数D2D1_ARC_SEGMENT,用来描述圆弧的属性。
[in] D2D1_ARC_SEGMENT *arc
) = 0;
D2D1_ARC_SEGMENT的定义如下,第一个参数表示弧的终点,第二个参数size表示弧的x轴和y轴,第三个参数表示弧的旋转角度,按顺时针方向计算,第四个参数指出了扫描方向,顺时针或者逆时针。最后一个参数指出了弧的大小是否超过180度。
D2D1_POINT_2F point;
D2D1_SIZE_F size;
FLOAT rotationAngle;
D2D1_SWEEP_DIRECTION sweepDirection;
D2D1_ARC_SIZE arcSize;
};
可能有人会问,既然是描述两点之间的弧,为什么参数中只有终点呢?起点在哪里?因为像弧线,贝塞尔曲线这类图形,通常是为了和其他图形进行拼接的,所以起点就是上次绘制的终点。如果是第一次绘制,也就是没有点可接,那么起点就有BeginFigure函数的第一个参数指定。
[in] const D2D1_BEZIER_SEGMENT *bezier
) = 0;
D2D1_BEZIER_SEGMENT的定义如下,注意这里实际上省略了一个参数,就是曲线的起点。因为这个函数默认起点就是该曲线要添加到的点。所以这里point1和point2是控制点,而point3是曲线的终点。
D2D1_POINT_2F point1;
D2D1_POINT_2F point2;
D2D1_POINT_2F point3;
};
绘制太阳的代码如下,注意这里我们只创建了一条光芒曲线,由于每一条光芒曲线由两条贝塞尔曲线拼接而成,如果全部绘制出来,需要十次AddBezier函数调用,为了简化代码,省略了其他四条曲线的创建。
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);
创建小溪
小溪的两个边缘对应两条曲线,每条曲线又由两条贝塞尔曲线构成。上面已经提到如何绘制贝塞尔曲线,代码略。
绘制场景
上面的代码是创建场景,下面开始绘制。绘制分两步完成,第一步先画出图形的轮廓,第二步进行颜色填充。以绘制小山为例,代码如下。
VOID DrawPathGeometry() { CreateD2DResource(g_Hwnd) ; g_pRenderTarget->BeginDraw() ; g_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); // 轮廓-黑色 g_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black, 1.f)); g_pRenderTarget->DrawGeometry(g_pLeftMountainGeometry, g_pSceneBrush, 1.f); // 填充-绿色 g_pSceneBrush->SetColor(D2D1::ColorF(D2D1::ColorF::OliveDrab, 1.f)); g_pRenderTarget->FillGeometry(g_pLeftMountainGeometry, g_pSceneBrush); HRESULT hr = g_pRenderTarget->EndDraw() ; if (FAILED(hr)) { MessageBox(NULL, "Draw failed!", "Error", 0) ; return ; } }
Happy Coding!!!
== THE END ==