书法字典:https://www.shufadict.com

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。

// Create path geometry
ID2D1PathGeometry* pathGeometry = NULL ;
hr
= g_pD2DFactory->CreatePathGeometry(&pathGeometry) ;
if (SUCCEEDED(hr))
{
// ...
}

获取ID2D1GeometrySink对象

真正负责创建图形的函数是ID2D1GeometrySink对象,使用Path geometry的Open方法可以获取该对象,使用完毕之后,调用Close函数关闭之。

ID2D1GeometrySink *pSink = NULL;
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->BeginFigure(D2D1::Point2F(346,255), D2D1_FIGURE_BEGIN_FILLED);
// 添加图形 
pSink
->EndFigure(D2D1_FIGURE_END_CLOSED);

所以完整的创建代码应该是下面这个样子,主要包括三部分,创建Path geometry,获取Sink对象,添加图形。

// 创建 path geometry
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函数,定义如下,该函数有两个参数,第一个是顶点数组,第二个是数组长度。

virtualvoid 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,用来描述圆弧的属性。

virtualvoid AddArc(
    [
in] D2D1_ARC_SEGMENT *arc
)
0;

D2D1_ARC_SEGMENT的定义如下,第一个参数表示弧的终点,第二个参数size表示弧的x轴和y轴,第三个参数表示弧的旋转角度,按顺时针方向计算,第四个参数指出了扫描方向,顺时针或者逆时针。最后一个参数指出了弧的大小是否超过180度。

struct D2D1_ARC_SEGMENT {
    D2D1_POINT_2F point;
    D2D1_SIZE_F size;
    FLOAT rotationAngle;
    D2D1_SWEEP_DIRECTION sweepDirection;
    D2D1_ARC_SIZE arcSize;
};

可能有人会问,既然是描述两点之间的弧,为什么参数中只有终点呢?起点在哪里?因为像弧线,贝塞尔曲线这类图形,通常是为了和其他图形进行拼接的,所以起点就是上次绘制的终点。如果是第一次绘制,也就是没有点可接,那么起点就有BeginFigure函数的第一个参数指定。

virtualvoid AddBezier(
    [
in] const D2D1_BEZIER_SEGMENT *bezier
)
0;

D2D1_BEZIER_SEGMENT的定义如下,注意这里实际上省略了一个参数,就是曲线的起点。因为这个函数默认起点就是该曲线要添加到的点。所以这里point1和point2是控制点,而point3是曲线的终点。

struct D2D1_BEZIER_SEGMENT {
    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 ==

posted on 2011-05-27 09:02  翰墨小生  阅读(13582)  评论(24编辑  收藏  举报

导航

书法字典:https://www.shufadict.com