`
tianshibaijia
  • 浏览: 1122891 次
文章分类
社区版块
存档分类
最新评论

Windows GDI+坐标系统详解

 
阅读更多

GDI+中的坐标系

1 什么是坐标系

坐标系就是确定一组数据位置的标尺。按按照维数分为2维平面坐标系和3维空间坐标系。其实2维坐标系也是z=03维坐标系的特例。

坐标系有三要素,一是原点,二是方向,三是单位大小。如果两个坐标系这三点完全一样,那么这两个坐标系就完全相同。关于坐标系和点的关系,我们可以这么理解:点本身是固定的,但在不同坐标系下的表示是不同的。那么为什么要定义那么多的坐标系呢,答案是为了描述方便。比如描述一个圆,如果把坐标系原点放到圆心,那么对圆的描述就是 x2+y2=r2。而如果原点不在圆心,那么圆描述就成了:(x-x0)2+(y-y0)2=r2

2 坐标系变换与矩阵运算

既然可以找到描述形状方便的坐标系,那么问题也来了。比如要同时描述两个形状,如两个圆,而且这两个圆是有相对位置的,比如是自行车的两个轮子。

尽管两个圆各自在自己的坐标系里都能很方便的描述,但是要建立两者之间的关系时,却遇到了麻烦。因为要计算两个圆的位置关系,必须把两个圆放到同一个坐标系下描述才行。所以就引出了坐标系变换的概念。在此例中,可以把第二个圆也放到第一个坐标系下描述,方法就是把第二个坐标系放到第一个坐标系中合适的位置(两个坐标系的关系),然后根据两个坐标系的关系,推算出第二个圆在第一个坐标系中的描述。

这种方式对于CAD中的任务分隔特别重要,比如做汽车设计的公司,可以把不同的部件分配给不同的人来做。设计人员接到任务后,自由选择合适的坐标系来描述负责的部件。等所有部件设计完成以后,再把所有的部件转换的整车坐标系上。坐标系间的转换(2维和3维)是非常有规律的,有数学基础的人可以自己推导公式,没有数学基础的也没有关系,各种图形库都已经把坐标系变换公式做成了函数API供程序调用。比如OpenGL提供了三维坐标系间的各种变换APIGDI+则提供了2维坐标的变换API。需要了解的是,坐标系间的变换,一般是通过矩阵运算完成的,感兴趣的读者可以参考任何讲解OpenGL坐标变换算法的书籍,重要的是矩阵运算可以通过硬件流水线完成,这就是图形显示中的显卡硬件加速的一部分。当然矩阵运算不光应用于坐标系转换,还广泛运用于其他计算领域,因此有人提出了用GPU代替CPU来进行大规模科学计算的方案。

3 GDI+中的三种坐标系

作为Windows中图形显示的关键部件,GDI+代表了Windows2维图形API。三维则是D3D的领域了。图形API要提供的函数大概是两类,一是绘图函数,二是坐标系转换函数。GDI+提供了很多绘图函数,如DrawRectangle,DrawEclipse,DrawString等等。所有这些函数中都需要位置或大小参数,对于这些参数含义的理解是很重要的。

3.1 调用者自定义坐标系(world

一是参数的单位是什么?位置参数的坐标系是什么?答案很有意思:不确定。因为这些东西有调用者自由确定。那么GDI+怎么根据这些不确定的参数绘制图形呢?答案是调用者要提供自己定义的坐标系和PAGE坐标系的关系。

3.2 Page坐标系

Page坐标系附属在某一个窗口或控件上,是一个固定的坐标系,原点位于窗口的左上角,x轴方向向右,y轴方向向下。单位为cminchpixel,根据实际情况设定。GDI+提供了Page坐标系和World坐标系间的转换API。含义是把world坐标系放到Page坐标系合适的位置。回到前面讲过的汽车分部件设计的例子,此处Page坐标系就是最后的整车坐标系,GID+提供的就是把各个部件(GDI+绘制函数绘制的图形)连同其坐标系一起放到整车(Page)坐标系里。

这是很合理的方式。在利用GDI+作图时也要按照这种思路来做。具体说来,先把整个图形分解成各个小的图形,在画某一个小的图形时不要考虑它最终在Page坐标系的位置,只要按照你自己设想的坐标系来调用GDI+的绘图函数就可以了。

当所有的图形都绘制完毕后,在把这些小的图形统统放到Page坐标系里。具体就是,调用绘制小图形的代码之前调用GDI+xxxTransform()系列函数把小图形的建模坐标系放置到Page坐标系里,在绘制小图形的代码之后,调用ResetTransform()。

讲到这里,也许大家会有疑问了,GDI+最后是如何把Page坐标系的图形绘制到屏幕上的呢,这就是显示器的Device坐标系。

3.3Device坐标系

对于Page坐标系和Device坐标系的转换,应用程序员不需要了解了,GDI+已经把这部分隐藏了。

4 GDI+中坐标系的转换实例

4.1题目

利用GDI+绘制如下图形:

4.2 分析

仔细看上面的图形,不难发现,此图形有6部分组成:头,左臂,右臂,身体,左腿,右腿。分别把各个部分分给6个设计师去建模,然后把各个模型连同其建模坐标系一起放到到Page坐标系中。如下图:

4.3代码

private PointF pHead;

private PointF pBody;

private PointF pLeftArm;

private PointF pRightArm;

private PointF pLeftLeg;

private PointF pRightLeg;

private SizeF sHead = new SizeF(30, 30); //头大小30cm

private SizeF sBody = new SizeF(50, 70); //身体大小

private SizeF sArm = new SizeF(10, 60); //胳膊大小

private SizeF sLeg = new SizeF(20, 70); //腿大小

public void DrawHead(PaintEventArgs e)

{

e.Graphics.DrawEllipse(Pens.Red, -sHead.Width / 2.0f, -sHead.Height / 2.0f, sHead.Width, sHead.Height);

}

public void DrawBody(PaintEventArgs e)

{

e.Graphics.DrawRectangle(Pens.Black, 0, 0, sBody.Width, sBody.Height);

}

public void DrawLeftArm(PaintEventArgs e)

{

e.Graphics.DrawRectangle(Pens.Black, 0, 0, sArm.Width, sArm.Height);

}

public void DrawRightArm(PaintEventArgs e)

{

e.Graphics.DrawRectangle(Pens.Black, 0, 0, sArm.Height, sArm.Width);

}

public void DrawLeftLeg(PaintEventArgs e)

{

e.Graphics.DrawRectangle(Pens.Black, 0, 0, sLeg.Width, sLeg.Height);

}

public void DrawRightLeg(PaintEventArgs e)

{

e.Graphics.DrawRectangle(Pens.Black, 0, 0, sLeg.Height, sLeg.Width);

}

private void Form1_Paint(object sender, PaintEventArgs e)

{

pHead = new PointF(this.Width / 2.0f, 100f); //放置头坐标系

e.Graphics.TranslateTransform(pHead.X, pHead.Y);

DrawHead(e); //调用负责头建模的代码

e.Graphics.ResetTransform(); //重置矩阵

pBody = new PointF(pHead.X - sBody.Width/2.0f, pHead.Y+sHead.Height/2.0f);

e.Graphics.TranslateTransform(pBody.X, pBody.Y);

DrawBody(e);

e.Graphics.ResetTransform();

pLeftArm = pBody;

e.Graphics.TranslateTransform(pLeftArm.X, pLeftArm.Y);

e.Graphics.RotateTransform(45);

DrawLeftArm(e);

e.Graphics.ResetTransform();

pRightArm = new PointF(pBody.X + sBody.Width, pBody.Y);

e.Graphics.TranslateTransform(pRightArm.X, pRightArm.Y);

e.Graphics.RotateTransform(45);

DrawRightArm(e);

e.Graphics.ResetTransform();

pLeftLeg = new PointF(pBody.X, pBody.Y + sBody.Height);

e.Graphics.TranslateTransform(pLeftLeg.X, pLeftLeg.Y);

e.Graphics.RotateTransform(45);

DrawLeftLeg(e);

e.Graphics.ResetTransform();

pRightLeg = new PointF(pBody.X + sBody.Width, pBody.Y + sBody.Height);

e.Graphics.TranslateTransform(pRightLeg.X, pRightLeg.Y);

e.Graphics.RotateTransform(45);

DrawRightLeg(e);

e.Graphics.ResetTransform();

}

}

5 MSDN中的几个不妥

由于GDI存在的时间很长,而GDI+诞生后为了兼容,一部分函数采用了与GDI相同的名称,甚至参数,参数的含义在MSDN中也按照原来的解释,导致了一些容易让读者误解的地方。

5.1绘图函数中关于左上角upper-left corner的描述

我们知道,GDI+的绘图函数使用的坐标系是由建模人员随意定义的,以函数
publicvoidDrawRectangle(
Penpen,
floatx,
floaty,
floatwidth,
float height
 )

为例,MSDN中对x,y解释如下:

x

Type: System. Single

The x-coordinate of the upper-left corner of the rectangle to draw.

y

Type: System. Single

The y-coordinate of the upper-left corner of the rectangle to draw.

然而此处的x,y真的是矩形左上角坐标吗?换句话说,左上是从什么角度看的。如下图:

A1坐标系中,DrawRectange()函数中的x,y确实代表了矩形的左上角。A2A3A1经过旋转以后得到的坐标系,此时x,y在建模者看来显然是右下角和左下角。而对于更常见的笛卡尔右手坐标系B来说,(x,y)也不是左上角。左上角的概念仅在Page坐标系中成立。关于(x,y)的正确解释应该是:

矩形四个角中,x,y值都最小的那个角的坐标。

5.2 MatrixOrder问题

5.2.1不带MatrixOrder参数的变换函数顺序执行的理解

前面提到过其实对坐标系的转换就是矩阵的乘法运算。这里面涉及到了变换的顺序和矩阵乘法的顺序的问题。讲解之前一定要确立坐标系变换的视角:把建模坐标系放置到Page坐标系中,从Page坐标系一步步变化成最终坐标系。

可以通过两步完成:

1)平移至虚线位置;

TranslateTransform0, dy);

2)旋转一定角度。

RotateTransform(45);

如果我们把(1)(2)顺序反过来,那么最终的结果如下图:

所以说先平移、旋转的顺序很重要。当前的操作是在前一步完成之后的新坐标系中进行的。对应于矩阵算法相当于左乘

5.2.2 MatrixOrder参数的函数的理解

publicvoidTranslateTransform(
floatdx,
float dy,
 MatrixOrder order
 )

MSDN中对于MatrixOrder解释为:

其实际效果是,Prepend相当于无此参数的版本,也就是说此次调用是在前面操作结果的基础上操作的;相当于矩阵左乘。

Append相当于此次操作先于前面的操作起作用,也就是先进行当前的操作,在此基础上进行此次调用前面的操作。相当于矩阵右乘。在OpenGL中都是采用Append模式的。

如此看来,上述英文解释正好相反了。这个不能说是错,可能是从不同的视角来看问题,我们的视角是:Page坐标系一步步变化成最终坐标系。

5.3 Graphics.Transform属性

这个属性比较特殊。MSDN的解释为:

获取或设置此 Graphics 的几何世界变换的副本。

获取的是Graphics变换矩阵的副本,而不是变换矩阵本身,也就是每次获取时,新建一个和变换矩阵成员值相同的矩阵对象,并返回。

问题是设置。设置的不是副本,而是Graphics变换矩阵本身。

内部实现伪代码

class Graphics

{

private Matrix transform;

public Matrix Transform

{

get { return transform.Clone(); }

set { transform=value; }

}

}

如果要通过矩阵乘法进行坐标转换,那么代码如下:

e.Graphics.Transform = Graphics.Transform.Multiply(new Matrix(....))

6 变换函数不能解决的问题

GDI+提供了平移、旋转、缩放等函数供我们调用,来把Page坐标系一步步改造成最终的建模坐标系在Page中的表现形态。对于绝大多数情况,这些函数足够了,但是看如下:

无论怎么平移和旋转,Page坐标系也无法编程上面红色显示的坐标系。这是因为Page坐标系属于左手坐标系,而红色所示是右手坐标系。在数学上,大多数情况使用的都是右手坐标系,模型也是建立在右手坐标系上,那么如何把Page转化为右手坐标系呢。也就是把y轴反向。答案是通过直接操纵Graphics的变换矩阵。

e.Graphics.Transform = e.Graphics.Transform.Multiply(new Matrix(1, 0, 0, -1, 0, 0)); //y反向

上述代码就实现了y轴反向的目的。

如果你对变换矩阵很了解,直接操作矩阵左右乘法,与调用相关的GDI+变换函数功效完全相同,可以实现任意变换。

(后注:Y轴反方向可以通过g.ScaleTransform(1, -1)完成)

分享到:
评论

相关推荐

    gdi+ 帮助文档

    GDI+开发的帮助文档,讲得清晰浅显,而且是中文的.如果学习GDI+开发的话,特别是初学者,强烈推荐下载. 对于初学者,还有一个问题就是,如果想要进行GDI+开发, 需要下载一个GDI+的开发包(SDK). 网上搜到的一般只包含一个...

    GDI+开发包 GDI+安装详解

    Visual C++6.0使用GDI+的一般方法 1. 载解压GDI+开发包; 2. 正确设置include & lib 目录; 3. stdafx.h 添加: #ifndef ULONG_PTR #define ULONG_PTR unsigned long* #endif #include 或者 将Visual...

    GDI+图像程序设计(PDF & 源码 -电子工业出版社)

     本书从介绍GDI+Windows图形程序设计的基本知识开始,其核心是对一些实际问题的指导,包括如何使用Windows Forms及如何优化GDI+的性能。本书通过一些例子来说明如何开发真实世界的工具,如GDI+Painter、GDI+Editro...

    GDI+游戏GDI+游戏GDI+游戏GDI+游戏GDI+游戏GDI+游戏

    C#GDI+小游戏 飞机大战C#GDI+小游戏 飞机大战C#GDI+小游戏 飞机大战C#GDI+小游戏 飞机大战C#GDI+小游戏 飞机大战

    GDI+的说明 GDI+的说明 GDI+的说明 GDI+的说明

    GDI+的说明 GDI+的说明 GDI+的说明 GDI+的说明

    C# GDI+绘制直角坐标系并自定义绘图

    C# GDI+绘制直角坐标系并自定义绘图并可通过鼠标在坐标系中绘制矩形圆形等形状

    GDI+开发包 GDI+ SDK

    GDI+开发包,解压文件,并将其中的Includes和Lib中的文件复制到VS安装目录中VC98目录下的Includes和Lib文件夹中

    GDI+ SDK参考(翻译版本)

    Microsoft Windows GDI+是为C/C++开发者提供的一个基于类的应用程序编程接口(API)。...运行于Microsoft Windows NT 4.0 SP6、Windows 2000、Windows 98 和 Windows Me操作系统的应用程序在分发的时候需要包含GDI+。

    C#GDI+图形程序设计源码

    第1章 GDI+ ——下一代图形接口 1.1 理解GDI+ 1.2 探索GDI+ 的功能 1.3 从GDI的角度学习GDI+ 1.4 .NET中的GDI+ 名称空间和类 总结 第2章 第一个GDI+ 应用程序 2.1 绘制表面 2.2 坐标系统 2.3 指南——第一...

    GDI+SDK参考 GDI+SDK参考

    GDI+SDK参考 GDI+SDK参考 GDI+SDK参考 GDI+SDK参考 GDI+SDK参考

    GDI+技术画面添加水印

    GDI+技术实现在画面上,图片上添加水印功能。

    GDI+中发生一般性错误

    GDI+中发生一般性错误GDI+中发生一般性错误GDI+中发生一般性错误GDI+中发生一般性错误

    C#用GDI+做的坐标图

    最近做项目需要用到坐标画曲线,在网上搜了半天,大部分都是只...现在提供一个能直接运行的源代码给各位初学者参考,基于C#2005做的,只有几行代码,只画一个固定的二维坐标轴,希望对各位初涉GDI+的菜鸟们有帮助。^_^

    GDI+双缓冲技术

    进入.NET时代,Windows的绘图技术也从GDI升级到了GDI+,从名字就能知道GDI+是对以前传统GDI绘图技术的一次升级,不过在微软几乎把所有的新技术都冠之.NET的情况下,GDI+竟然不叫做GDI.NET,还真让我感到有点意外了。...

    GDI+ LIB ,GDI+ LIB

    GDI+LIB GDI+LIB GDI+LIB GDI+LIB

Global site tag (gtag.js) - Google Analytics