引子

任何一门语言的第一个教程几乎都是Hello,world。我们也不例外,但是这里不是教大家打印Hello,world,而是编写一个简单的D2D绘制程序,让大家对Direct2D的程序结构及编程方法有一个基本的认识。下面我们来看如何一步一步绘制一个矩形。

基本概念

在开始之前,还是先介绍一些基本的概念,有助于大家理解程序,这些概念包括,Brush(画刷),Render target(渲染目标),Geometry(几何图形),它们会贯穿整个教程,所以越早介绍越好,对于有Windows GDI基础的人来说,理解这些概念很容易。没有基础的也没关系,我们可以先了解一下,随着学习的深入,会有更加深刻的认识。

Brush

Brush-画刷,画刷是绘图的工具,它管理图形的颜色,虚实,画刷可以绘制几何图形,也可以绘制位图。

Render target

Render target-渲染目标(姑且这么翻译吧)是绘制的场所,其实这就是一个surface,一个表面,再具体点就是一块内存,可以是显存,也可能是系统内存。所有的绘图操作都在这里完成。

Geometry, Bitmap, Text

Geometry-几何图形,Bitmap-位图, Text-文本。这三者是要绘制的内容,几何图形包括矩形,圆角矩形,椭圆等,当然Direct2D除了可以绘制几何图形之外,还可绘制位图和文本,D2D没有提供加载位图的接口,所以对位图的加载都是使用WIC来实现的。而对文本的绘制则是通过DirectWrite来实现的,DirectWrite在分类上属于D2D,但是目前已经独立成一个组件了。为了便于理解以上三者之间的关系,大家可以想象一个画家,他在作画的时候,都需要那些东西呢?第一,他需要一支画笔用来绘制,这相当于上面的画刷,第二,他需要一张纸或者一张画布用来承载绘制的东西,这就是上面的Render target,最后,他想画什么呢?山水?花鸟?亦或是人物?这就是绘制的内容,相当于上面的几何图形,位图或者文本。

Resource

在Direct2D中主要有两种资源,一是设备无关的资源,另一个是设备相关的资源。所谓设备无关,是指该资源不与特定的硬件渲染设备相关联,所以设备无关的资源都分配在CPU中,而设备相关是指该资源与特定的渲染硬件相关联,比如当硬件加速可用时,使用GPU渲染,否则使用CPU渲染。

设备无关的资源

  • ID2D1DrawingStateBlock
  • ID2D1Factory
  • ID2D1Geometry及继承自它的接口
  • ID2D1GeometrySink和ID2D1SimplifiedGeometrySink
  • ID2D1StrokeStyle

除了ID2D1RenderTarget之外,所有使用ID2D1Factory创建的资源都是设备无关的。

设备相关的资源

  • ID2D1Brush及继承自它的接口
  • ID2D1Layer
  • ID2D1RenderTarget及继承自它的接口

一般来说,使用ID2D1RenderTarget创建的资源都是设备相关的。

程序框架

一个简单的D2D程序大致包含下面三个核心函数。

1 创建资源(CreateResources) - 设备无关的资源可以一次性创建,永久使用,而设备相关的资源则需要随着设备改变而相应的改变。

2 渲染(Render) - 响应WM_PAINT消息进行绘制。

3 清理资源(Cleanup) - DX是基于COM的,所有COM对象在使用完毕时,都要释放。

为了便于大家理解,我画了一张图,这个图简单描述了D2D程序的基本渲染流程。

需要说明的是,Direct2D的渲染时机与D3D有些不同,D3D是在没有消息处理时进行渲染,而D2D则是响应WM_PAINT消息进行渲染。下面的代码中会有详细的解释。

代码

添加头文件

除了Win32编程需要的头文件(比如Windows.h)之外,任何D2D程序都需要头文件d2d1.h。

#include <windows.h>
#include <D2D1.h>

声明全局变量

首先我们需要一个ID2D1Factory*类型的对象,也就是D2D工厂接口,这个接口是所有D2D程序的起始点,几乎所有的D2D资源都是由这个接口创建的,其次我们需要一个渲染的场所,也就是Render Target,在D2D中有多种类型的Render Target,这里我们选择ID2D1HwndRenderTarget类型,用来在窗口中进行渲染。最后我们定义一个画刷,用来绘制图形,这里选择固定颜色的画刷,即ID2D1SolidColorBrush。

ID2D1Factory* pD2DFactory = NULL ; // Direct2D factory
ID2D1HwndRenderTarget* pRenderTarget = NULL;   // Render target
ID2D1SolidColorBrush* pBlackBrush = NULL ; // A black brush, reflect the line color RECT rc ; // Render area
HWND g_Hwnd ; // Window handle

创建D2D工厂

接下来创建D2D工厂对象,有了这个对象才能创建后续的资源,这个函数有两个参数,第一个参数是工厂的类型,这里只有单线程和多线程两类,如果是单线程的话,意味着D2D不会为所创建的工厂的对象以及由这个对象创建的子对象提供同步机制,也就是说,如果有多个线程访问了这个资源,那么需要自己提供同步机制。如果是多线程类型,那么D2D会为你提供同步机制。第二个参数用来接收创建的工厂。


HRESULT hr ;

hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory) ;
if (FAILED(hr))
{
  MessageBox(hWnd, "Create D2D factory failed!", "Error", 0) ;
  return ;
}

创建Render target

有了工厂对象以后,开始创建RenderTarget,CreateHwndRenderTarget函数有三个参数,第一个参数是Render target属性,包括渲染模式,象素格式,DPI等,D2D提供了一个函数D2D1::RenderTargetProperties(),可以用来生成默认的属性,我们这里直接使用这个函数。第二个参数是Hwnd类型的Render target属性,它包含三个参数,第一个是窗口句柄,第二个是Render target的大小,第三个参数是Present选项,这个参数有个默认值,这里我们使用它的默认值。CreateHwndRenderTarget函数的最后一个参数用来接收创建的Render target。


// Create a Direct2D render target
hr = pD2DFactory->CreateHwndRenderTarget(
  D2D1::RenderTargetProperties(),
  D2D1::HwndRenderTargetProperties(
    hWnd, 
    D2D1::SizeU(rc.right - rc.left,rc.bottom - rc.top)
  ), 
  &pRenderTarget
) ;
if (FAILED(hr))
{
  MessageBox(hWnd, "Create render target failed!", "Error", 0) ;
  return ;
}

创建画刷

有了Render target,再使用函数CreateSolidColorBrush创建画刷,这里创建一个固定颜色的画刷,第一个参数是画刷的颜色,第二个参数接收创建的画刷,画刷的颜色就是绘制线条所用的颜色,比如这里创建一个红色的画刷,那么后面绘制的矩形就是红色的。


// Create a brush
hr = pRenderTarget->CreateSolidColorBrush(
  D2D1::ColorF(D2D1::ColorF::Red),
  &pBlackBrush
) ;
if (FAILED(hr))
{
  MessageBox(hWnd, "Create brush failed!", "Error", 0) ;
  return ;
}

绘制矩形

万事俱备,只欠渲染!渲染的代码很简单,首先调用CreateD2DResource函数来创建资源,这是个自定义函数,用来创建资源,比如Render target,画刷之类的,该函数包含了上面提到的代码。绘制的代码要放在BeginDraw和EndDraw函数之间,调用Clear函数可以将Render target清除为指定的背景色。DrawRectangle函数用来绘制矩形,它有两个参数,第一个是被绘制的矩形,第二个是绘制所用的画刷。函数EndDraw的返回值标识了渲染是否成功。


VOID DrawRectangle()
{
  CreateD2DResource(g_Hwnd) ;   pRenderTarget->BeginDraw() ;   // Clear background color white
  pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));   // Draw Rectangle
  pRenderTarget->DrawRectangle(
  D2D1::RectF(100.f, 100.f, 500.f, 500.f),
  pBlackBrush
); HRESULT hr = pRenderTarget->EndDraw() ; if (FAILED(hr))
{
  MessageBox(NULL, "Draw failed!", "Error", 0) ;
  return ;
}

清理资源

最后,在程序退出时,清理程序资源,每个COM对象都有一个Release方法,用来释放自己,这里我们定义一个宏来释放COM对象。

VOID Cleanup()
{
  SAFE_RELEASE(pRenderTarget) ;
  SAFE_RELEASE(pBlackBrush) ;
  SAFE_RELEASE(pD2DFactory) ;
}

释放COM对象的宏。

#define SAFE_RELEASE(P) if(P){P->Release() ; P = NULL ;}

效果图如下

代码下载

Happy Coding

== THE END ==

Direct2D教程(二)来看D2D世界中的Hello,World的更多相关文章

  1. MVC教程二:从控制器中获取URL的值

    一.从控制器中获取URL的值有三种方式: 1.使用Request.QueryString[] 例如: string value = Request.QueryString["BookId&q ...

  2. Direct2D教程(外篇)环境配置

    2014年世界杯首场淘汰赛马上开始了,闲着没事,整理以前的博客草稿打发时间,意外的发现这篇文章,本来是打算加入到Direct2D那个系列的,不知道为什么把它给遗漏了.环境配置,对于熟手来说,不是什么重 ...

  3. Laravel教程 二:路由,视图,控制器工作流程

    Laravel教程 二:路由,视图,控制器工作流程 此文章为原创文章,未经同意,禁止转载. View Controller 上一篇教程我们走了那么长的路,终于把Laravel安装好了,这一篇教程我们就 ...

  4. SharpDX之Direct2D教程I——简单示例和Color(颜色)

    研究Direct2D已经有一段时间了,也写了一个系列的文章 Direct2D ,是基于Windows API Code Pack 1.1.在前文 Direct2D教程VIII——几何(Geometry ...

  5. Direct2D教程IV——笔刷(Brush)对象

    目前博客园中成系列的Direct2D的教程有 1.万一的 Direct2D 系列,用的是Delphi 2009 2.zdd的 Direct2D 系列,用的是VS中的C++ 3.本文所在的 Direct ...

  6. Direct2D教程II——绘制基本图形和线型(StrokeStyle)的设置详解

    目前,在博客园上,相对写得比较好的两个关于Direct2D的教程系列,分别是万一的Direct2D系列和zdd的Direct2D系列.有兴趣的网友可以去看看.本系列也是介绍Direct2D的教程,是基 ...

  7. 无废话SharePoint入门教程二[SharePoint发展、工具及术语]

    一.前言 1.由于上一篇文章的标题命名失误,此篇标题写给百度搜索”什么是SharePoint”. 2.关于什么是SharePoint,请参见本人的第一篇文章:http://www.cnblogs.co ...

  8. SharpDX之Direct2D教程II——加载位图文件和保存位图文件

    本系列文章目录: SharpDX之Direct2D教程I——简单示例和Color(颜色) 绘制位图是绘制操作的不可缺少的一部分.在Direct2D中绘制位图,必须先利用WIC组件将位图加载到内存中,再 ...

  9. 【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/16384009 作者:毛星云 ...

随机推荐

  1. JAVA 基础--开发环境Sublime Text 3 搭建

    方法一  打开Sublime Text 3,依次点击Preference, Browse Packages,在打开的窗口中双击User文件夹,新建文件JavaC.sublime-build,用记事本打 ...

  2. 光学字符识别OCR-5 文本切割

    经过前面文字定位得到单行的文本区域之后,我们就可以想办法将单行的文本切割为单个的字符了.因为第三步的模型是针对单个的字符建立的,因此这一步也是必须的. 均匀切割 基于方块汉字的假设,事实上最简单的切割 ...

  3. 转 关于oracle 分区表 表空间以及索引的总结

    关于oracle的表空间,分区表,以及索引的总结关键字: oracle, 表空间, 分区表, 索引 上周第一次做数据库测试碰到了很多问题特此总结: 表空间: Oracle的UNDOTBS01.DBF文 ...

  4. selenium - 常用等待操作

    # 4. 等待操作 # 强制等待from time import sleepsleep(10) # 隐性等待# 设置最长等待时间,在这个时间在只要有个时间点加载完成,则执行下一步代码,比sleep智能 ...

  5. SVM 与 LR的异同

    LR & SVM 的区别 相同点 LR和SVM都是分类算法. 如果不考虑核函数,LR和SVM都是线性分类算法,也就是说他们的分类决策面都是线性的. LR和SVM都是监督学习算法. LR和SVM ...

  6. Leetcode 419.甲板上的战舰

    甲板上的战舰 给定一个二维的甲板, 请计算其中有多少艘战舰. 战舰用 'X'表示,空位用 '.'表示. 你需要遵守以下规则: 给你一个有效的甲板,仅由战舰或者空位组成. 战舰只能水平或者垂直放置.换句 ...

  7. JS进行人民币大小写转换

    //数字金额大写转换(可以处理整数,小数,负数) function upDigit() { digit=$("#digit").html(); n=digit.replace(/\ ...

  8. 性能学习之六---socket接口测试

    socket协议较底层,所以是一个万能协议.socket发的是数据包,所以较难看懂. 下面我们来讲解socket接口测试. 大致思路为:新建sever端和client端---建立连接---发送数据 一 ...

  9. 【Luogu】P2536病毒检测(Trie上DP)

    题目链接 这道题我写了个01DP,f[i][j]表示跑到Trie上第i个节点,匹配到字符串第j位行不行 然后重点在*号无限匹配怎么处理 经过一番脑洞我们可以发现*号无限匹配可以拆成两种情况: 1:匹配 ...

  10. Multiset ------ 多重集合

    Multiset的中文名是多重集合,其实就是集合的扩展版.唯一的不同是集合中一个值只能出现一次,而多重集合中一个值可以出现多次. 粗略看了看MSDN,在STL中,multiset和set的成员函数声明 ...