Direct2D 编程入门

Direct2D 编程入门

                                                        例程代码下载

      一.引言

 早就听说Direct2D和DirectWrite发布了。但由于安装的系统是XP,D2D和DWrite一直没有试——因为D2D/DWrite只支持Win7和Vista SP2。暑假重装了Win7,终于可以用了。如果你学过GDI+或Direct 3D,那么学起来比较容易,因为有很多函数、方法都和GDI+很相似。下面贴一些学习的笔记。

 

       二.开发包安装

有关D2D/DWrite的最新文档,可以在它老窝:http://msdn.microsoft.com/en-us/library/dd370990(VS.85).aspx找到。如果你没有安装D2D/DWrite开发包,可以去http://msdn.microsoft.com/en-us/windowsserver/bb980924.aspx下载最新微软Windows软件开发包。其中有D2D/DWrite所需要的头文件和库文件。安装好之后将Include、Lib目录添加到你的编译器中就可以了。当然你的操作系统必须为Win7或Vista SP2。安装之后的目录结构如下:

Direct2D 编程入门_第1张图片

 

 三.初步介绍

    D2D开发所需要的头文件有d2d1.h(其中包含了d2d1helper.h、d2dbasetypes.h、d2derr.h),库文件有d2d1.lib。DWrite所需头文件有dwrite.h,库文件为dwrite.lib。D2D根对象为ID2D1Factory和ID2D1Resource接口,它们都是一组COM接口。而ID2D1Resource又通过ID2D1Factory来创建。所有的D2D资源对象都继承自ID2D1Resource接口。

 

    ID2D1Factory接口是D2D程序的起始点。D2D资源有两种:与设备相关(Device Dependent)的和与设备无关(Device InDependent)的。与设备无关的资源在整个应用程序运行过程中一直存在,如ID2D1Factory。而与设备相关的资源当设备丢失(移除)时会停止运作。D2D的绘制工作都交由ID2D1HwndRenderTarget接口来完成,就像GDI+中的Graphics类。它是与设备相关的。其余绘制过程中所用到的资源——如Brush、Bitmap、Mesh、Layer——都由ID2D1HwndRenderTarget接口创建,它们也是设备相关资源。

 

   D2D中的接口一般为ID2D1xxxx,其中xxxx为大小写结合的单词组,如ID2D1SolidBrush。而一般的结构为单词之间用下划线连接的全大写组合(D2D1_XXX_XXX),如D2D1_GRADIENT_STOP。枚举类型则是用下划线连接的大写字母组合,如D2D1_GAMMA;具体的枚举值则是枚举类型后加上限定字符,如D2D1_GAMMA_2_2、D2D1_GAMMA_1_0。函数或方法为大小写结合的单词组,如D2D1CreateFactory。函数一般返回HRESULT值,可用SUCCEEDED(hr)或FAILED(hr)来检测函数调用成功还是失败。

 

     四.Demo

1> 框架简介:

 多说无益,我们看一个例子。创建窗口、消息处理等我们自己写就是了。我们需要关注的是D2D资源的创建、D2D绘制、D2D资源销毁。这样我们需要关注的函数只有下面几个: 

BOOL CreateDeviceIndependentResource();    //创建设备无关资源.  
BOOL CreateDeviceDependentResource();      //创建设备相关资源.  
void DiscardDeviceIndependentResource();   //销毁设备无关资源.  
void DiscardDeviceDependentResource();     //销毁设备相关资源.  
  
void Render();                             //执行D2D绘制.  
void SizeUpdate();                         //WM_SIZE消息触发.  
         需要注意的是:设备无关资源可以在WinMain入口处就创建。由于涉及到设备丢失的问题,所以设备相关资源必须在创建窗口获得窗口句柄后,窗口显示前完成。Rende()函数可以在程序WM_PAINT消息和空闲时间处理。资源销毁的顺序一般是:先创建的后销毁。所有绘制操作都交由D2D完成,所以我们要处理WM_ERASEBKGND消息。

 

 这样我们需要关注的消息就有:WM_PAINT、WM_ERASEBKGND、WM_SIZE(稍后介绍)。所以程序的一般结构是:

int WINAPI WinMain(…)  
{  
       CreateDeviceIndependentResource();      //创建设备无关资源.  
  
       WNDCLASS wndClass;  
  
       //窗口类填充、注册。  
  
       g_hWnd = CreateWindow(…);               //创建窗口.  
       CreateDeviceDependentResource();        //创建设备相关资源.  
  
       ShowWindow(g_hWnd, SW_NORMAL);          //显示窗口.  
       UpdateWindow(g_hWnd);                   //更新窗口.  
  
       RunMessageLoop();                       //抓取消息.GetMessage  
  
       DiscardDeviceDependentResource();       //销毁设备相关资源.  
       DiscardDeviceIndependentResource();     //销毁设备无关资源.  
  
       return 0;  
}  
  
  
//  
//消息处理回调函数如下:  
//  
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, …)  
{  
       Switch(message)  
       {  
       case WM_PAINT:  
              Render();             //执行D2D绘制.  
              return 0;  
  
       case WM_ERASEBKGND:  
              return 0;             //直接返回,刷新交由D2D处理.  
  
       case WM_SIZE:  
              SizeUpdate();         //处理WM_SIZE消息.  
              return 0;  
  
       // 其余消息处理.  
  
       default:  
              break;  
       }  
  
       return DefWindowProc(hWnd, message, …);  
}  

    一般结构就是这样,如果我们用C++类封装一下,会显得更紧凑。我们要执行D2D绘制,必须定义的接口有ID2D1Factory、ID2D1HwndRenderTarget。如果还要画出一些东西的话,那么就要用到画刷(ID2D1SolidColorBrush等)。

 

            2> 资源变量定义:

//  
// 定义的变量.  
//  
ID2D1Factory*             g_pD2DFactory      = 0;        //D2D根对象.  
ID2D1HwndRenderTarget*    g_pD2DRenderTarget = 0;        //绘制目标.  
ID2D1SolidColorBrush*     g_pSolidBrush      = 0;        //纯色画刷.  
ID2D1LinearGradientBrush* g_pLGBrush         = 0;        //线性渐变画刷.  
HWND                      g_hWnd             = NULL;     //全局窗口句柄.  

    3> 资源的创建:

//  
// D2D设备无关资源的创建.  
//  
BOOL CreateDeviceIndependentResource()  
{  
    HRESULT hr = NULL;  
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2DFactory);  

    return SUCCEEDED(hr);  
}  

    我们用D2D1CreateFactory创建了一个ID2D1Factory对象,第一个参数很明显是使用单线程。

//  
// D2D设备相关资源的创建.  
//  
BOOL CreateDeviceDependentResource()  
{  
    HRESULT hr = NULL;  
    RECT    rc;  
    ::GetClientRect(g_hWnd, &rc);  
  
    D2D1_SIZE_U size = SizeU(rc.right - rc.left, rc.bottom - rc.top);  
  
    //  
    // 创建绘制目标区域.  
    //  
    hr = g_pD2DFactory->CreateHwndRenderTarget(  
        RenderTargetProperties(),  
        HwndRenderTargetProperties(g_hWnd, size),  
        &g_pD2DRenderTarget);  
  
    //  
    // 创建纯色画刷.  
    //  
    if(SUCCEEDED(hr))  
    {  
        hr = g_pD2DRenderTarget->CreateSolidColorBrush(  
            ColorF(ColorF::OrangeRed),          //画刷颜色.  
            &g_pSolidBrush);  
    }  
  
    ID2D1GradientStopCollection* pGStop = 0;    //渐变点集合.  
    D2D1_GRADIENT_STOP stops[2];                //渐变信息描述数组.  
    stops[0].color = ColorF(ColorF::White);     //第一个渐变点颜色.  
    stops[1].color = ColorF(ColorF::Blue);      //第二个渐变点颜色.  
    stops[0].position = 0.0f;                   //第一个渐变点位置百分比.  
    stops[1].position = 1.0f;                   //第二个渐变点位置百分比.  
  
    if(SUCCEEDED(hr))  
    {  
        hr = g_pD2DRenderTarget->CreateGradientStopCollection(  
            stops,  
            sizeof(stops) / sizeof(D2D1_GRADIENT_STOP),  
            D2D1_GAMMA_2_2,                 //启用Gamma 2.2版本校正.  
            D2D1_EXTEND_MODE_CLAMP,         //扩展环绕模式.  
            &pGStop);  
    }  
  
    //  
    // 创建线性渐变画刷.  
    //  
    if(SUCCEEDED(hr))  
    {  
        hr = g_pD2DRenderTarget->CreateLinearGradientBrush(  
            LinearGradientBrushProperties(  
            Point2F(0.0f, 0.0f)             //第一个渐变点坐标.  
            Point2F(1.0f, 1.0f)             //第二个渐变点坐标.  
            ),  
            pGStop,                         //渐变信息集合.  
            &g_pLGBrush                     //所创建的渐变画刷.  
            );  
  
        SafeRelease(pGStop);                //释放局部资源.  
    }  
  
    return SUCCEEDED(hr);  
}  

我们在释放的时候用到了一个模板SafeRelease,我们的定义如下:

//  
// 释放模板.  
//  
template<typename Type>  
void SafeRelease(Type& pObjToRelease)  
{  
    if(pObjToRelease)  
    {  
        pObjToRelease->Release();  
        pObjToRelease = 0;  
    }  
}  

  4> D2D绘制:

   很简单。我们接着看绘制Render():

//  
// D2D绘制函数.  
//  
void Render()  
{  
    //  
    // 如果设备丢失,则创建与设备相关资源.  
    //  
    if(!g_pD2DRenderTarget)  
    {  
        if(!CreateDeviceDependentResource())  
        {  
            return ;  
        }  
    }  
  
    g_pD2DRenderTarget->BeginDraw();                       //开始绘制.  
    g_pD2DRenderTarget->Clear(ColorF(ColorF::White));      //将窗体设置为白色.  
  
    //  
    // 执行D2D绘制.  
    //  
    D2D1_POINT_2F StartPoint = Point2F(10.0f, 10.0f);  
    D2D1_POINT_2F EndPoint   = Point2F(100.0f, 100.0f);  
    g_pSolidBrush->SetColor(ColorF(ColorF::Red));          //设置纯色画刷颜色.  
  
    g_pD2DRenderTarget->DrawLine(       //绘制一条线段.  
        StartPoint,                     //起点.  
        EndPoint,                       //终点.  
        g_pSolidBrush,                  //所使用的画刷.  
        8.0f                            //线宽.  
        );  
  
    D2D1_RECT_F rc = RectF(130.0f, 10.0f, 230.0f, 110.0f);  
    g_pSolidBrush->SetColor(ColorF(ColorF::Pink));  
  
    g_pD2DRenderTarget->DrawRectangle(  //绘制一个矩形.  
        &rc,                            //要绘制的矩形.  
        g_pSolidBrush,                  //所使用画刷.  
        6.0f                            //线宽.  
        );  
  
    //  
    // 用线性渐变画刷绘制一个椭圆.  
    //  
    D2D1_ELLIPSE ellipse = Ellipse(  
        Point2F(350.0f, 70.0f),  
        80.0f,  
        40.0f  
        );  
  
    g_pLGBrush->SetStartPoint(Point2F(270.0f, 30.0f));      //设置渐变起点.  
    g_pLGBrush->SetEndPoint(Point2F(430.0f, 110.0f));       //设置渐变终点.  
    g_pD2DRenderTarget->DrawEllipse(                        //绘制椭圆.  
        ellipse,  
        g_pLGBrush,  
        20.0f  
        );  
  
    //  
    // 用线性渐变画刷填充一个矩形.  
    //  
    g_pLGBrush->SetStartPoint(Point2F(10.0f, 150.0f));     //设置渐变起点.  
    g_pLGBrush->SetEndPoint(Point2F(160.0f, 300.0f));      //设置渐变终点.  
  
    rc = RectF(10.0f, 150.0f, 160.0f, 300.0f);  
    g_pD2DRenderTarget->FillRectangle(  
        rc,  
        g_pLGBrush  
        );  
  
    //  
    // 绘制一个绿色圆角矩形.  
    //  
    rc = RectF(200.0f, 150.0f, 450.0f, 300.0f);  
    D2D1_ROUNDED_RECT RoundRc = RoundedRect(rc, 10.0f, 10.0f);  
  
    g_pSolidBrush->SetColor(ColorF(ColorF::ForestGreen));  
    g_pD2DRenderTarget->DrawRoundedRectangle(  
        &RoundRc,  
        g_pSolidBrush,  
        2.0f  
        );  
  
    HRESULT hr = g_pD2DRenderTarget->EndDraw();  
  
    //  
    // 如果设备丢失.我们丢弃设备相关资源以备下次  
    // 执行绘制时创建.  
    //  
    if(D2DERR_RECREATE_TARGET == hr)  
    {  
        DiscardDeviceDependentResource();  
    }  
}  

    如果你学过GDI+,可以看到,所有的绘制步骤与GDI+极其类似。绘制也很简单,三步走:开始绘制(BeginDraw)、绘制、结束绘制(EndDraw)。期间我们用ID2D1HwndRenderTarget::Clear来将窗体背景设置为我们想要的颜色——此例中我们设置为白色ColorFul::White。可以看出,全部的绘制工作都交由ID2D1HwndRenderTarget对象来完成。

        除了绘制,我们还要处理设备丢失的问题——如果设备丢失,则丢弃与设备相关的资源,因为它们已经不可用了。然后在下一次绘制开始时重新创建设备相关资源。而设备无关资源一旦创建,就在整个应用程序运作过程中存在。

 

   5> 资源释放:

 我们接着看资源的释放。由于我们已经介绍了SafeRelease这个模板,所以释放工作显得非常简单。我们只需遵循“先创建的后释放”这一原则就是了。

//  
// 销毁资源无关资源.  
//  
void DiscardDeviceIndependentResource()  
{  
    SafeRelease(g_pD2DFactory);  
}  
  
  
//  
// 销毁资源相关资源.  
//  
void DiscardDeviceDependentResource()  
{  
    SafeRelease(g_pLGBrush);  
    SafeRelease(g_pSolidBrush);  
    SafeRelease(g_pD2DRenderTarget);  
}  

   6> WM_SIZE消息处理:

   

    我们一直提到了WM_SIZE消息的处理。这有什么用呢?我们先看CreateDeviceDependentResource函数中ID2D1Factory::CreateHwndRenderTarget的第二个参数HwndRenderTargetProperties(g_hWnd, size);其中g_hWnd是这个绘制目标所关联的窗口句柄,而size是一个D2D1_SIZE_U类型的结构。size我们用的是客户区(Client)的宽度和高度来初始化的。所以,ID2D1HwndRenderTarget不仅关联了要绘制窗口的句柄,而且还关联了要绘制区域的大小。当窗口大小发生改变时,我们必须重设这个绘制区域大小。就是下面的SizeUpdate函数:

//  
// 处理WM_SIZE消息.  
//  
void SizeUpdate()  
{  
    RECT rc;  
    GetClientRect(g_hWnd, &rc);  
    g_pD2DRenderTarget->Resize(SizeU(rc.right - rc.left, rc.bottom - rc.top));  
}  
   如果你在SizeUpdate中不做任何事情,那么会出现什么情况呢?如果你注释SizeUpdate中的所有代码,当你运行例程时,会发生有趣的现象:当拖动改变窗体大小时,窗体上你所绘制的图形也会随之变大或缩小!!你可以自己试验一下。

 

7> 运行与分析:

编译链接成功后,我们可以看看运行结果了。如下:

 

Direct2D 编程入门_第2张图片 

 我同样用GDI+实现过这样的程序,但如果不用双缓冲的话,GDI+绘制的程序当拖动时有明显的闪烁现象。而此D2D绘制的例程在拉动、拖动时没有任何闪烁。D2D比GDI+的强大之处可见一斑。


       五.小结

          我们通过初步介绍D2D,并用一个小例程来学习了如何使用D2D来做自己想做的事情——当然,这个程序不是太酷,但这都是最基本的东西。只有将基础的东西弄懂,才能去发掘D2D中最精妙的知识。在附件中,包含了本例程的所有代码和可执行程序。当然,为了使程序显得更紧凑,我另外写了一个CD2DDemoApp类来封装了各个函数,也放在了附件中。你可以用VS2008或者VS2010编译。


http://blog.csdn.net/zhangyafengcpp/article/details/5921774

你可能感兴趣的:(Direct2D 编程入门)