zoukankan      html  css  js  c++  java
  • 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 ==

    作者:zdd
    出处:http://www.cnblogs.com/graphics/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    176. Second Highest Salary
    175. Combine Two Tables
    172. Factorial Trailing Zeroes
    171. Excel Sheet Column Number
    169. Majority Element
    168. Excel Sheet Column Title
    167. Two Sum II
    160. Intersection of Two Linked Lists
    个人博客记录
    <meta>标签
  • 原文地址:https://www.cnblogs.com/graphics/p/2057759.html
Copyright © 2011-2022 走看看