原作者:孙涛 整理
http://blog.csdn.net/suntaoznz
Microsoft® Windows® GDI+ 是Windows XP 或者Windows Server 2003 操作系统的子系统。它负责在屏幕和打印机上显示信息。GDI+ 是一个应用程序编程接口,它是一个C++ 类。
如同它的名字暗示一样,GDI+ 是Windows 图形设备接口(GDI)的继承者, 这个图形设备接口包括早期的Windows版本。 Windows XP 或者 Windows Server 2003 为了支持以前的应用程序还支持GDI,但是新的应用程序开发人员应该去使用GDI+ 去作他们的图像处理工作。因为GDI+对GDI的很多性能进行了优化,同时还提供了其他的一些特性。
一个图形设备接口,例如GDI+, 允许应用程序开发人员在打印机或屏幕上显示信息,而不用去考虑显示设备的具体细节。应用程序开发人员调用GDI+ classes 提供的方法(methods),这些方法将依次传到指定的设备驱动程序上。 GDI+ 让应用程序与图形硬件分离开,并且它可以开发人员创建独立于设备的应用程序。
--------------------------------------------------------------------------------
2D矢量图 2-D vector graphics
矢量图形包括坐标系统中的系列点指定的绘图基元(例如,直线、曲线和图形)。例如,直线可通过它的两个端点来指定,而矩形可通过确定其左上角位置的点并给出其宽度和高度的一对数字来指定。简单路径可由通过直线连接的点的数组来指定。贝塞尔样条是由四个控制点指定的复杂曲线。
GDI+ 提供了存储基元自身相关信息的类(和结构)、存储基元绘制方式相关信息的类,以及实际进行绘制的类。例如,Rectangle 结构存储矩形的位置和尺寸;Pen 类存储有关线条颜色、线条粗细和线型的信息;而 Graphics 类具有用于绘制直线、矩形、路径和其他图形的方法。还有几种 Brush 类,它们存储有关如何使用颜色或图案来填充封闭图形和路径的信息。
您可以在图元文件中记录矢量图像(图形命令的序列)。GDI+ 提供了 Metafile 类,可用于记录、显示和保存图元文件。MetafileHeader 和 MetaHeader 类允许您检查图元文件头中存储的数据。
图像处理(Imaging)
某些种类的图片很难或者根本无法用矢量图形技术来显示。例如,工具栏按钮上的图片和显示为图标的图片就难以指定为直线和曲线的集合。拥挤的棒球运动场的高分辨率数字照片会更难以使用矢量技术来制作。这种类型的图像可存储为位图,即代表屏幕上单个点颜色的数字数组。GDI+ 提供了 Bitmap 类,可用于显示、操作和保存位图。
版式
版式关系到使用各种字体、字号和样式来显示文本。GDI+ 为这种复杂任务提供了大量的支持。GDI+ 中的新功能之一是子像素消除锯齿,它可以使文本在 LCD 屏幕上呈现时显得比较平滑。
--------------------------------------------------------------------------------
GDI+ 的托管类接口包含大约 60 个类、50 个枚举和 8 个结构。Graphics 类是 GDI+ 的核心功能,它是实际绘制直线、曲线、图形、图像和文本的类。
许多类与 Graphics 类一起使用。例如,Graphics.DrawLine 方法接收 Pen 对象,该对象中存有所要绘制的线条的属性(颜色、宽度、虚线线型和外观)。Graphics.FillRectangle 方法可以接收指向 LinearGradientBrush 对象(它使用Graphics 对象以渐变色填充矩形)的指针。Font 和 StringFormat 对象影响 Graphics 对象绘制文本的方式。Matrix 对象存储并操作 Graphics 对象的全局变形,该对象用于旋转、缩放和翻转图像。
GDI+ 为组织图形数据提供了几种结构(例如,Rectangle、Point 和 Size)。而且,某些类的主要作用是结构化数据类型。例如,BitmapData 类是 Bitmap 类的助手,而 PathData 类是 GraphicsPath 类的助手。
GDI+ 定义了几种枚举,它们是相关常量的集合。例如,LineJoin 枚举包含元素 Bevel、Miter 和 Round,它们指定可用于连接两个线条的样式。
GDI+ 提供了少量不依附于任何类的函数。比如GdiplusStartup 和 GdiplusShutdown函数。 在你要调用GDI+前,必须先调用GdiplusStartup 。当你使用完GDI+后,你必须调用GdiplusShutdown 。
--------------------------------------------------------------------------------
Microsoft® Windows® GDI+ 是不同于GDI的。首先, GDI+ 通过提供新的一些性能,在GDI的上进行扩充。比如gradient brushes 和alpha blending. 其次,编程的模式已经重新修改了,并且图形编程更容易更灵活。
--------------------------------------------------------------------------------
下面将介绍Microsoft® Windows® GDI+的几个新的特性。
渐变画笔(Gradient Brushes)
通过提供用于填充图形、路径和区域的线性渐变画笔和路径渐变画笔,GDI+ 扩展了 GDI 的功能。渐变画笔还可用于绘制直线、曲线和路径。线性渐变画笔可用于使用颜色来填充图形,画笔在图形中移动时,颜色会逐渐改变。例如,假定您通过指定图形左边为蓝色、右边为绿色创建了一个水平渐变画笔。当您用水平渐变画笔填充该图形时,随着画笔从图形的左边移至右边,颜色就会由蓝色逐渐变为绿色。用类似方法定义的垂直渐变画笔填充的图形,颜色从上到下变化。下面的插图显示了用水平渐变画笔填充的椭圆和用斜式渐变画笔填充的区域。
用路径渐变画笔填充图形时,可选择不同的方法来指定当您从图形的一部分至另一部分移动画笔时颜色的变化方式。一种选择是指定中心颜色和边缘颜色,以使在您从图形中间向外边缘移动画笔时,像素逐渐从一种颜色变化到另一种颜色。下面的插图显示了用路径渐变画笔填充的路径(该路径是用一对贝塞尔样条创建的)。
基数样条(Cardinal Splines)
GDI+ 支持在 GDI 中不支持的基数样条。基数样条是一连串单独的曲线,这些曲线连接起来形成一条较大的曲线。样条由点的数组指定,并通过该数组中的每一个点。基数样条平滑地(没有锐角)通过数组中的每一个点,因此,比通过连接直线创建的路径更精准。下面的插图显示了两个路径,一个以基数样条的形式创建,另一个通过连接直线创建。
独立的路径对象
在 GDI 中,路径属于设备上下文,并且会在绘制时被毁坏。利用 GDI+,绘图由 Graphics 对象执行,而您可以创建并维护几个与Graphics 对象分开的 GraphicsPath 对象。绘图操作不会破坏 GraphicsPath 对象,因此您可以多次使用同一个 GraphicsPath 对象来绘制路径。
变形和矩阵对象
GDI+ 提供了 Matrix 对象,它是一种可以使变形(旋转、平移,等等)简易灵活的强大工具。矩阵对象与所变形对象联合使用。例如,GraphicsPath 对象具有 Transform 方法,此方法接收 Matrix 对象作为参数。单一的 3×3 矩阵可存储一种变形或一个变形序列。下面的插图显示了一个路径在执行两种变形前后的情况。
缩放区域
GDI+ 通过对区域的支持极大地扩展了 GDI。在 GDI 中,区域存储在设备坐标中,而且,可应用于区域的唯一变形是平移。GDI+在全局坐标中存储区域,且允许区域发生任何可存储在变形矩阵中的变形(例如缩放)。下面的插图显示一个区域在执行三种变形(缩放、旋转和平移)前后的情况。
Alpha 混合
请注意,在上图中,您可以在变形区域(用蓝色阴影画笔填充)中看到未变形区域(用红色填充)。这是由 GDI+ 支持的 alpha混合实现的。使用 alpha 混合,您可以指定填充颜色的透明度。透明色与背景色相混合 —填充色越透明,背景色的透出程度就越高。下面的插图显示四个用相同颜色(红色)填充、但透明层次不同的椭圆。
对多图像格式的支持
BMP 、Graphics Interchange Format (GIF) 、JPEG 、Exif 、PNG 、TIFF 、ICON 、WMF
EMF。
--------------------------------------------------------------------------------
以下章节描述了使用 GDI+ 编程与使用 GDI 编程的几点不同之处。
设备上下文、句柄和图形对象
如果您使用过 GDI(Windows 的以前版本中包括的图形设备接口)编写程序,就会熟悉设备上下文的知识。设备上下文是Windows 使用的一种结构,用于存储与特殊显示设备的功能和指定如何在该设备上绘制项目的属性相关的信息。用于视频显示的设备上下文还与显示的特定窗口关联。首先,您获得一个设备上下文的句柄 (HDC),然后将该句柄作为参数传递至实际进行绘制的GDI 函数。您还可将此句柄作为参数传递给获取或设置设备上下文属性的 GDI 函数。
使用 GDI+,您不需要再使用句柄或设备上下文,而是只需创建一个 Graphics 对象,然后以您熟悉的面向对象样式myGraphicsObject.DrawLine(parameters) 调用其方法。正如设备上下文位于 GDI 的核心,Graphics 对象也位于GDI+ 的核心。设备上下文和 Graphics 对象的作用相似,但在使用设备上下文 (GDI) 的、基于句柄的编程模式和使用Graphics 对象 (GDI+) 的、面向对象的编程模型之间存在一些基本的差异。
Graphics 对象(像设备上下文一样)与屏幕上的特定窗口关联,并具有指定如何绘制项目的属性(例如,SmoothingMode 和TextRenderingHint)。但是,Graphics 对象不受钢笔、画笔、路径、图像或字体的约束,这与设备上下文不同。例如,使用设备上下文绘制线条之前,必须先调用 SelectObject 以使钢笔对象和设备上下文关联。这是指将钢笔选入设备上下文中。在设备上下文中绘制的所有线条均使用该钢笔,直到您选择另一支不同的钢笔为止。在 GDI+ 中,将 Pen 对象作为参数传递给Graphics 类的 DrawLine 方法。您可以在一系列 DrawLine 调用的每个调用中使用不同的 Pen 对象,而不必使给定的 Pen对象与 Graphics 对象关联。
绘制线条的两种方法
下面每个示例都在位置 (20, 10) 和位置 (200, 100) 之间绘制了一条宽为 3 的红色线条。第一个示例调用 GDI,第二个示例通过托管类接口调用 GDI+。
使用 GDI 绘制线条
要使用 GDI 绘制线条,需要两个对象:设备上下文和钢笔。通过调用 BeginPaint,可以获得设备上下文句柄;通过调用CreatePen,则可以获得钢笔句柄。下一步,调用 SelectObject 以将钢笔选入设备上下文。调用 MoveToEx,将钢笔位置设在 (20, 10),然后调用 LineTo,在钢笔位置与位置 (200, 100) 之间绘制一条线条。请注意,MoveToEx 和 LineTo 均将hdc(设备上下文的句柄)作为参数接收。
HDC hdc;
PAINTSTRUCT ps;
HPEN hPen;
...
hdc = BeginPaint(hWnd, &ps);
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hPen);
MoveToEx(hdc, 20, 10, NULL);
LineTo(hdc, 200, 100);
EndPaint(hWnd, &ps);
使用 GDI+ 和托管类接口绘制线条
用GDI+的C++类接口来画线,你需要一个Graphics 对象和一个Pen 对象。你不要去获得这些对象的句柄。你去创建一个Graphics对象和一个Pen 对象。调用Graphics 对象的Graphics::DrawLine 方法。Graphics::DrawLine 方法的第一个参数,就是指向你的Pen 对象的指针。
HDC hdc;
PAINTSTRUCT ps;
Pen* myPen;
Graphics* myGraphics;
hdc = BeginPaint(hWnd, &ps);
myPen = new Pen(Color(255, 255, 0, 0), 3);
myGraphics = new Graphics(hdc);
myGraphics->DrawLine(myPen, 20, 10, 200, 100);
delete myGraphics;
delete myPen;
EndPaint(hWnd, &ps);
作为参数的钢笔、画笔、路径、图像和字体
前面的示例显示:创建和维护 Pen 对象可以与提供绘制方法的 Graphics 对象分开。创建和维护Brush、GraphicsPath、Image 和 Font 对象也可以与 Graphics 对象分开。Graphics 类提供的许多绘制方法都将Brush、GraphicsPath、Image 或 Font 对象作为参数接收。例如,Brush 对象作为参数传递至 FillRectangle 方法,GraphicsPath 对象作为参数传递至 DrawPath 方法。同样,Image 和 Font 对象传递至 DrawImage 和DrawString 方法。这与 GDI 不同,在 GDI 中,需要将画笔、路径、图像或字体选入设备上下文,然后将设备上下文的句柄作为参数传递至绘制函数。
方法重载
许多 GDI+ 方法都是重载的,即,若干方法共享同一名称,却有不同的参数列表。例如,Graphics 类的 DrawLine 方法来自下列表单:
Status DrawLine(IN const Pen* pen,
IN REAL x1,
IN REAL y1,
IN REAL x2,
IN REAL y2);
Status DrawLine(IN const Pen* pen,
IN const PointF& pt1,
IN const PointF& pt2);
Status DrawLine(IN const Pen* pen,
IN INT x1,
IN INT y1,
IN INT x2,
IN INT y2);
Status DrawLine(IN const Pen* pen,
IN const Point& pt1,
IN const Point& pt2);
以上四种 DrawLine 变体均接收 Pen 对象、起点坐标和终点坐标。前两种变体将坐标作为浮点数接收,而后两种变体将坐标作为整数接收。第一种和第三种变体将坐标作为四个单个数字的列表接收,而第二种和第四种变体则将坐标作为一对 Point(或PointF)对象接收。
无当前位置
请注意,前面所述的 DrawLine 方法中显示:线条的起点和终点均被作为参数接收。这与 GDI 方案不同,在 GDI 中,调用MoveToEx(hdc, x1, y1, NULL) 来设置当前钢笔位置之后,再调用 LineTo(hdc, x2, y2) 以绘制一条从 (x1, y1) 到(x2, y2) 的线条。GDI+ 从总体上已经放弃了当前位置的概念。
绘制和填充的不同方法
论及绘制轮廓和填充图形内部时,GDI+ 要比 GDI 更灵活。GDI 有一个 Rectangle 函数,可一步完成绘制轮廓和填充矩形内部。轮廓由当前选定的钢笔绘制,而内部则由当前选定的画笔来填充。
hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 100, 50, 200, 80);
GDI+ 使用不同的方法来绘制轮廓和填充矩形内部。Graphics 类的 DrawRectangle 方法将 Pen 对象作为其参数之一,而FillRectangle 方法将 Brush 对象作为其参数之一。
HatchBrush* myHatchBrush = new HatchBrush(
HatchStyleCross,
Color(255, 0, 255, 0),
Color(255, 0, 0, 255));
Pen* myPen = new Pen(Color(255, 255, 0, 0), 3);
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30);
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30);
请注意,GDI+ 中的 FillRectangle 和 DrawRectangle 方法接收指定矩形左边缘、上边缘、宽和高的参数。这与 GDI 的Rectangle 函数不同,Rectangle 函数接收指定矩形左边缘、右边缘、上边缘和下边缘的参数。另请注意,GDI+ 中 Color 类的 FromArgb 方法有四个参数。后三个参数是常见的红色、绿色和蓝色值。第一个参数是 alpha 值,它指定所绘制颜色与背景颜色的混合程度。
构造区域
GDI 提供几种用于创建区域的函数:CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn 和CreatePolyPolygonRgn。您或许希望 GDI+ 中的 Region 类也有类似的构造函数,将矩形、椭圆、圆角矩形和多边形作为参数接收,但事实并非如此。GDI+ 中的 Region 类提供一个接收 Rectangle 对象的构造函数和另一个接收 GraphicsPath 对象的构造函数。如果您想基于椭圆、圆角矩形或多边形构造区域,可以通过创建一个 GraphicsPath 对象(例如包含椭圆的对象),然后将其传递至 Region 构造函数来轻松实现。
GDI+ 通过组合图形和路径,使得构成复杂区域十分简单。Region 类具有 Union 和 Intersect 方法,可用于扩展具有路径的现有区域或其他区域。GDI+ 方案一个很好的功能就是 GraphicsPath 对象在作为参数传递至 Region 构造函数时不会被破坏(在GDI 中,可以使用 PathToRegion 函数将路径转换为区域,但在此过程中,路径将被破坏)。另外,GraphicsPath 对象在作为参数传递给 Union 或 Intersect 方法时也不会被破坏,因此,在一些单独的区域中,您可以将给定的路径作为构造块使用。此过程如下面的示例所示。假定 onePath 是一个已初始化的 GraphicsPath 对象(简单或复杂)。
Region region1(rect1);
Region region2(rect2);
region1.Union(onePath);
region2.Intersect(onePath);
--------------------------------------------------------------------------------
GDI+ 的矢量图形部分用于绘制直线、曲线,并用于绘制和填充图形。
--------------------------------------------------------------------------------
GDI+ 在坐标系统中绘制直线、矩形和其他图形。您可以从各种各样的坐标系统中选择,但默认坐标系统的原点是在左上角,并且 x 轴指向右边,y 轴指向下边。默认坐标系统的度量单位是像素。
计算机监视器是在一个点的矩形数组上创建其显示,这些点被称为图片元素或像素。各台监视器屏幕上显示的像素数量都是不同的,并且用户通常在一定程度上可以配置单独一台监视器上显示的像素数量。
在使用 GDI+ 绘制直线、矩形或曲线时,要提供有关要绘制的项目的某些关键信息。例如,您可以通过提供两个点来指定一条直线,而且您可以通过提供一个点、高度和宽度来指定一个矩形。GDI+ 与显示驱动程序软件一同工作来确定要显示直线、矩形或曲线必须打开哪些像素。下面的插图显示了已打开的用于显示从点 (4, 2) 到点 (12, 8) 的直线的像素。
在实践中,人们发现某些基本构造块对于创建二维图片尤其有用。下面列出了全部由 GDI+ 支持的这些构造块:
GDI+ 中的 Graphics 类提供了以下用于绘制前面列表中项目的方法:DrawLine、DrawRectangle、DrawEllipse、DrawPolygon、DrawArc、DrawCurve(用于基数样条)和DrawBezier。这些方法中的每一种都是重载的,即每种方法都支持几个不同的参数列表。例如,DrawLine 方法的一个变体接收一个 Pen 对象和四个整数,而 DrawLine 方法的另一个变体接收一个 Pen 对象和两个 Point 对象。
绘制线条、矩形和贝塞尔样条的方法具有可在单个调用中绘制多个项目的复数同伴方法:DrawLines、DrawRectangles 和DrawBeziers。DrawCurve 方法也有一个同伴方法 DrawClosedCurve,它能够通过连接曲线的终点和起点来关闭该曲线。
Graphics 类的所有绘制方法与 Pen 对象共同工作。要进行绘制,必须至少创建两个对象:一个 Graphics 对象和一个 Pen 对象。Pen 对象存储要绘制项目的属性,例如,线宽和颜色。Pen 对象作为参数之一传递到绘制方法。例如,如下面的示例所示,DrawRectangle 方法的一个变体接收一个 Pen 对象和四个整数,该示例绘制一个宽 100、高 50 且左上角位于 (20, 10)的矩形:
myGraphics.DrawRectangle(&myPen, 20, 10, 100, 50);
--------------------------------------------------------------------------------
要使用 GDI+ 绘制直线,需要创建 Graphics 对象和 Pen 对象。Graphics 对象提供能实际进行绘制的方法,Pen 对象存储属性,例如,线条颜色、宽度和线型。要绘制直线,可调用 Graphics 对象的 DrawLine 方法。Pen 对象作为参数之一传递到DrawLine 方法。下面的示例绘制了一条从点 (4, 2) 到点 (12, 6) 的直线:
myGraphics.DrawLine(&myPen, 4, 2, 12, 6);
DrawLine 是 Graphics 类的重载方法,因此您有几种方法可以给它提供参数。例如,您可以构造两个 Point 对象并把它们作为参数传递给 DrawLine 方法:
Point myStartPoint(4, 2);
Point myEndPoint(12, 6);
myGraphics.DrawLine(&myPen, myStartPoint, myEndPoint);
您在构造 Pen 对象时可以指定某些属性。例如,一种 Pen 构造函数允许您指定颜色和宽度。下面的示例绘制了一条从 (0, 0) 到 (60, 30) 宽度为 2 的蓝线:
Pen myPen(Color(255, 0, 0, 255), 2);
myGraphics.DrawLine(&myPen, 0, 0, 60, 30);
Pen 对象也公开属性,例如 DashStyle 可用于指定直线的特性。下面的示例绘制了一条从 (100, 50) 到 (300, 80) 的点划线:
myPen.SetDashStyle(DashStyleDash);
myGraphics.DrawLine(&myPen, 100, 50, 300, 80);
您可以利用 Pen 对象的属性来为直线设置更多属性。StartCap 和 EndCap 属性指定线条两端的外观;两端可以是平的、正方形的、圆形的、三角形的或自定义形状。LineJoin 属性可用于指定连接的线相互间是斜接的(连接时形成锐角)、斜切的、圆形的还是截断的。下面的插图显示了具有不同的线帽和连接类型的直线。
利用 GDI+ 绘制矩形与绘制直线类似。要绘制矩形,需要有 Graphics 对象和 Pen 对象。Graphics 对象提供了DrawRectangle 方法,Pen 对象存储属性,例如,线宽和颜色。Pen 对象作为参数之一传递到 DrawRectangle 方法。下面的示例绘制了一个矩形,其左上角位于 (100, 50),宽度为 80,高度为 40:
myGraphics.DrawRectangle(&myPen, 100, 50, 80, 40);
DrawRectangle 是 Graphics 类的重载方法,因此您有几种方法为它提供参数。例如,您可以构造一个 Rectangle 对象并将该 Rectangle 对象作为参数传递到 DrawRectangle 方法:
Rect myRect(100, 50, 80, 40);
myGraphics.DrawRectangle(&myPen, myRect);
Rectangle 对象具有用于处理和收集矩形相关信息的方法和属性。例如,Inflate 和 Offset 方法可改变该矩形的大小和位置。IntersectsWith 方法可显示矩形是否与另一给定的矩形相交,Contains 方法可显示一个给定的点是否在该矩形内。
--------------------------------------------------------------------------------
椭圆由其边框指定。下面的插图显示了一个椭圆,以及它的边框。
要绘制椭圆,需要有 Graphics 对象和 Pen 对象。Graphics 对象提供 DrawEllipse 方法,而 Pen 对象存储用于呈现椭圆的线条属性,例如,宽度和颜色。Pen 对象作为参数之一传递到 DrawEllipse 方法。传递到 DrawEllipse 方法的其余参数指定椭圆的边框。下面的示例绘制了一个椭圆;边框的宽度为 80,高度为 40,左上角位于 (100, 50):
myGraphics.DrawEllipse(&myPen, 100, 50, 160, 80);
DrawEllipse 是 Graphics 类的重载方法,因此您有几种方法可为它提供参数。例如,您可以构造 Rectangle 对象并将Rectangle 对象作为参数传递到 DrawEllipse 方法:
Rect myRect(100, 50, 160, 80);
myGraphics.DrawEllipse(&myPen, myRect);
弧线是椭圆的一部分。要绘制弧线,可调用 Graphics 类的 DrawArc 方法。除了 DrawArc 需要有起始角度和仰角以外,DrawArc 方法的参数与 DrawEllipse 方法的参数相同。下面的示例绘制了一个起始角为 30 度、仰角为 180 度的弧线:
myGraphics.DrawArc(&myPen, 100, 50, 160, 80, 30, 180);
下面的插图显示了弧线、椭圆和边框。
--------------------------------------------------------------------------------
多边形是有三条或更多直边的闭合图形。例如,三角形是有三条边的多边形,矩形是有四条边的多边形,五边形是有五条边的多边形。下面的插图显示了几个多边形。
要绘制多边形,需要有 Graphics 对象、Pen 对象和 Point(或 PointF)对象的数组。Graphics 对象提供了 DrawPolygon方法。Pen 对象存储用于呈现多边形的线条属性,例如,宽度和颜色,Point 对象的数组存储将由直线连接的点。Pen 对象和Point 对象的数组作为参数传递到 DrawPolygon 方法。下面的示例绘制了一个三条边的多边形。请注意,myPointArray 中只有三个点:(0, 0)、(50, 30) 和 (30, 60)。DrawPolygon 方法通过绘制一条从 (30, 60) 回到起始点 (0, 0) 的线来自动闭合该多边形。
Point myPointArray[] =
{Point(0, 0), Point(50, 30), Point(30, 60)};
myGraphics.DrawPolygon(&myPen, myPointArray, 3);
下面的插图显示了该多边形。
--------------------------------------------------------------------------------
基数样条是一连串单独的曲线,这些曲线连接起来形成一条较大的曲线。样条由点的数组和张力参数指定。基数样条平滑地经过数组中的每个点;曲线的陡度上没有尖角和突然的变化。下面的插图显示了一组点和经过这一组点中每一点的基数样条。
物理样条是一块薄木片或其他有弹性的物质。在数学样条出现之前,设计者利用物理样条绘制曲线。设计者把样条放置在一张纸上并锚定到一组给定的点上。然后设计者就可以用铅笔沿着样条绘制出一条曲线。一组给定的点可以产生各种各样的曲线,这取决于物理样条的属性。例如,极不易弯曲的样条与非常有弹性的样条产生的曲线是不同的。
数学样条的公式基于弹性棒条的属性,因此数学样条产生的曲线与物理样条曾产生的曲线是相同的。正如不同张力的物理样条通过一组给定的点将产生不同的曲线一样,张力参数值不同的数学样条在一组给定的点上将产生不同的曲线。下面的插图显示了经过同一组点的四个基数样条。每个样条都显示了张力。请注意,0 张力对应于无限的物理张力,强制曲线在点与点之间采用最短的路线(直线)。张力为 1 对应于没有物理张力,使样条采用最小完全弯曲的路径。张力值大于 1 的曲线就像压缩的弹簧,被挤压着采用较长的路径。
请注意,上面插图中的四个样条共享同一条起始点处的切线。该切线是从起始点到沿着曲线的下一点绘制的一条线。同样,在结束点处共享的切线是从结束点到沿着曲线的上一点绘制的一条线。
要绘制基数样条,需要有 Graphics 对象、Pen 对象和 Point 对象的数组。Graphics 提供绘制样条的 DrawCurve 方法,Pen 对象存储样条的属性,例如,线宽和颜色。Point 对象数组存储曲线将要经过的点。下面的示例绘制了一个经过myPointArray 中的点的基数样条。第三个参数是张力。
myGraphics.DrawCurve(&myPen, myPointArray, 3, 1.5f);
--------------------------------------------------------------------------------
贝塞尔样条是由四个点指定的曲线:两个终点(p1 和 p2)和两个控制点(c1 和 c2)。曲线开始于 p1,结束于 p2。该曲线不经过控制点,但是控制点的作用像磁铁一样,在某些方向上拉拽曲线并影响曲线弯曲的方式。下面的插图显示了一个贝塞尔曲线及其终点和控制点。
请注意,该曲线开始于 p1 并向控制点 c1 移动。该曲线 p1 处的切线是从 p1 到 c1 绘制的线。另外请注意,终结点 p2 处的切线是从 c2 到 p2 绘制的线。
若要绘制贝塞尔样条,需要 Graphics 对象和 Pen 对象。Graphics 对象提供 DrawBezier 方法,Pen 对象存储用于呈现曲线的线条属性,例如,宽度和颜色。Pen 对象作为参数之一传递给 DrawBezier 方法。传递到 DrawBezier 方法的其余参数是终结点和控制点。下面的示例绘制了一个贝塞尔样条,起始点为 (0, 0),控制点为 (40, 20) 和 (80, 150),结束点为 (100, 10):
myGraphics.DrawBezier(&myPen, 0, 0, 40, 20, 80, 150, 100, 10);
下面的插图显示了曲线、控制点和两条切线。
贝塞尔样条最初是由皮埃尔·贝塞尔开发的,用于汽车工业设计中。许多类型的计算机辅助设计都证明了它们十分有用,它们也用于定义字体的轮廓。贝塞尔样条可生成各种各样的形状,下面的插图显示了其中的一些。
--------------------------------------------------------------------------------
路径是通过组合直线、矩形和简单的曲线而形成的。请回忆一下矢量图形概述,以下基本构造块已被证明对于绘制图片是非常有用的:
在 GDI+ 中,GraphicsPath 对象允许您将这些构造块序列收集到一个单独单元中。一个对 Graphics 类的 DrawPath 方法的调用,可以绘制出整个序列的直线、矩形、多边形和曲线。下面的插图显示了通过结合一条直线、一个弧形、一个贝塞尔样条和一个基数样条而创建的路径。
GraphicsPath 类提供了以下用于创建待绘制项目序列的方法:AddLine、AddRectangle、AddEllipse、AddArc、AddPolygon、AddCurve(用于基数样条)和 AddBezier。这些方法中的每一种都是重载的,即每种方法都支持几个不同的参数列表。例如,AddLine 方法的一个变体接收四个整数,而AddLine 方法的另一个变体接收两个 Point 对象。
向一个路径添加直线、矩形和贝塞尔样条的方法有复数同伴方法(在单个调用中向路径添加若干个项目):AddLines、AddRectangles 和 AddBeziers。AddCurve 和 AddArc 方法也有同伴方法 AddClosedCurve 和AddPie,它们可向路径添加闭合的曲线或扇形。
要绘制路径,需要有 Graphics 对象、Pen 对象和 GraphicsPath 对象。Graphics 对象提供 DrawPath 方法,Pen 对象存储用于呈现路径的线条属性,例如,宽度和颜色。GraphicsPath 对象存储构成路径的直线和曲线序列。Pen 对象和GraphicsPath 对象作为参数传递到 DrawPath 方法。下面的示例绘制了由直线、椭圆和贝塞尔样条组成的路径:
myGraphicsPath.AddLine(0, 0, 30, 20);
myGraphicsPath.AddEllipse(20, 20, 20, 40);
myGraphicsPath.AddBezier(30, 60, 70, 60, 50, 30, 100, 10);
myGraphics.DrawPath(&myPen, &myGraphicsPath);
下面的插图显示了该路径。
除了向路径添加直线、矩形和曲线外,还可以向路径添加路径。这就允许您合并现有的路径来形成大型复杂路径。
myGraphicsPath.AddPath(&graphicsPath1, FALSE);
myGraphicsPath.AddPath(&graphicsPath2, TRUE);
您还可以把其他两个项目加入路径:字符串和扇形。扇形是椭圆内的一部分。下面的示例用弧形、基数样条、字符串和扇形创建了路径:
myGraphicsPath.AddArc(0, 0, 30, 20, -90, 180);
myGraphicsPath.AddCurve(myPointArray, 3);
myGraphicsPath.AddString(L"a string in a path", 18, &myFontFamily,
0, 24, myPointF, &myStringFormat);
myGraphicsPath.AddPie(230, 10, 40, 40, 40, 110);
myGraphics.DrawPath(&myPen, &myGraphicsPath);
下面的插图显示了该路径。请注意,不必连接路径;弧形、基数样条、字符串和扇形都是分开的。
--------------------------------------------------------------------------------
闭合的形状(例如,矩形或椭圆)由轮廓和内部组成。轮廓用钢笔绘制,内部用画笔填充。GDI+ 提供了几种用于填充闭合形状内部的画笔类:SolidBrush、HatchBrush、TextureBrush 和 GradientBrush。所有这些类都是从 Brush 类继承的。下面的插图显示了用实心画笔填充的矩形和用阴影画笔填充的椭圆。
实心画笔
要填充闭合图形,需要有 Graphics 对象和 Brush 对象。Graphics 对象提供 FillRectangle 和 FillEllipse 这样的方法,Brush 对象存储填充的属性,例如,颜色和图案。Brush 对象作为参数之一传递到填充方法。下面的示例用纯红色填充椭圆:
SolidBrush mySolidBrush(Color(255, 255, 0, 0));
myGraphics.FillEllipse(&mySolidBrush, 0, 0, 60, 40);
请注意,在前面的示例中,画笔是从 Brush 继承的 SolidBrush 类型。
阴影画笔
用阴影画笔填充图形时,要指定前景色、背景色和阴影样式。前景色是阴影的颜色。
HatchBrush myHatchBrush(
HatchStyleVertical,
Color(255, 0, 0, 255),
Color(255, 0, 255, 0));
GDI+ 提供了 50 多种阴影样式;下面插图中显示的三种样式分别是水平的、前置对角的和交叉的。
纹理画笔
有了纹理画笔,您就可以用位图中存储的图案来填充图形。例如,假定下面的图片存储在名为MyTexture.bmp 的磁盘文件中。
下面的示例通过重复存储在 MyTexture.bmp 中的图片来填充椭圆:
Image myImage(L"MyTexture.bmp");
TextureBrush myTextureBrush(&myImage);
myGraphics.FillEllipse(&myTextureBrush, 0, 0, 100, 50);
下面的插图显示已填充的椭圆。
渐变画笔
GDI+ 提供两种渐变画笔:线性和路径。您可以使用线性渐变画笔来用颜色(在您横向、纵向或斜向移过图形时会逐渐变化的颜色)填充图形。下面的示例用水平渐变画笔填充一个椭圆,当您从椭圆的左边缘向右边缘移动时画笔颜色会由蓝变绿:
LinearGradientBrush myLinearGradientBrush(
myRect,
Color(255, 0, 0, 255),
Color(255, 0, 255, 0),
LinearGradientModeHorizontal);
myGraphics.FillEllipse(&myLinearGradientBrush, myRect);
下面的插图显示已填充的椭圆。
路径渐变画笔可配置为当您从图形中心向边缘移动时颜色随之改变。
路径渐变画笔非常灵活。用于填充下面插图中三角形的渐变画笔,颜色从中心由红色开始到顶点逐渐变为三种不同的颜色。
--------------------------------------------------------------------------------
下面的插图显示了两条曲线:一条打开的和一条闭合的。
闭合的曲线具有内部,因此可以用画笔填充。GDI+ 中的 Graphics 类提供了以下用于填充闭合图形和曲线的方法:FillRectangle、FillEllipse、FillPie、FillPolygon、FillClosedCurve、FillPath 和 FillRegion。每当调用其中某种方法时,都必须将一种特定的画笔类型(SolidBrush、HatchBrush、TextureBrush、LinearGradientBrush 或PathGradientBrush)作为参数传递。
FillPie 方法是 DrawArc 方法的伴侣。正如 DrawArc 方法绘制椭圆轮廓的一部分,FillPie 方法填充椭圆内部的一部分。下面的示例绘制了一个弧形并填充椭圆内部的相应部分:
SolidBrush mySolidBrush(Color(255, 255, 0, 0));
myGraphics.FillEllipse(&mySolidBrush, 0, 0, 60, 40);
下面的插图显示了该弧形和填充后的扇形。
FillClosedCurve 方法是 DrawClosedCurve 方法的伴侣。这两种方法都通过连接结束点和起始点来自动闭合曲线。下面的示例绘制了一条经过 (0, 0)、(60, 20) 和 (40, 50) 的曲线。然后,该曲线通过连接 (40, 50) 至起始点 (0, 0) 自动闭合,并且用一种纯色填充内部。
Point myPointArray[] =
{Point(10, 10), Point(60, 20),Point(40, 50)};
myGraphics.DrawClosedCurve(&myPen, myPointArray, 3);
myGraphics.FillClosedCurve(&mySolidBrush, myPointArray, 3, FillModeAlternate)
FillPath 方法填充了路径的不同部分的内部。如果路径的某一部分不构成封闭的曲线或图形,FillPath 方法会在填充该部分之前先自动将其闭合。下面的示例绘制并填充一个路径,该路径由弧形、基数样条、字符串和扇形组成:
myGraphics.FillPath(&mySolidBrush, &myGraphicsPath);
myGraphics.DrawPath(&myPen, &myGraphicsPath);
下面的插图显示了有纯色填充和没有纯色填充的路径。请注意,通过利用 DrawPath 方法,字符串内的文本是空心的,而不是实心的。FillPath 方法可用于绘制字符串中字符的内部。
---------------------------------------------------------------------
区域是输出设备显示区域的一部分。区域可以是简单的(单个矩形)或复杂的(多边形和闭合曲线的组合)。下面的插图显示了两个区域:一个利用矩形构造,另一个利用路径构造。
区域常用于剪辑和命中检测。剪辑需要将绘制限制到显示区域的一个特定区域,通常是需要更新的部分。命中检测需要通过检查来确定按下鼠标按钮时光标是否在屏幕的特定区域中。
您可以从矩形或路径中构造区域。您也可以通过合并现有的区域来创建复杂区域。Region 类提供了以下合并区域的方法:Intersect、Union、Xor、Exclude 和 Complement。
两个区域的交集是同时属于两个区域的所有点的集合。并集是属于一个或另一个或两个区域的所有点的集合。区域的补集是不在该区域的所有点的集合。下面的插图显示了前面插图中两个区域的交集和并集。
适用于一对区域的 Xor 方法可生成一个区域,其中包含属于一个区域或另一个区域但不同时属于两个区域的所有点。适用于一对区域的 Exclude 方法可生成了一个区域,其中包含在第一个区域中而不在第二个区域中的所有点。下面的插图显示了通过将 Xor 和Exclude 方法应用于该主题开始处的两个区域而产生的区域。
要填充区域,需要有 Graphics 对象、Brush 对象和 Region 对象。Graphics 对象提供 FillRegion 方法,Brush 对象存储填充的属性,例如,颜色或图案。下面的示例用纯色填充区域:
myGraphics.FillRegion(&mySolidBrush, &myRegion);
---------------------------------------------------------------------
剪辑需要把绘制限制到一个特定的矩形或区域。下面的插图显示了剪辑到心形区域的字符串"Hello"。
区域可从路径构造,路径可包含字符串的轮廓,因此您可以剪辑空心效果的文字。下面的插图显示了剪辑到文本字符串内部的一组同心椭圆。
要利用剪辑绘制,可创建 Graphics 对象,设置其 Clip 属性,然后调用同一个 Graphics 对象的绘制方法:
Region myRegion(Rect(20, 30, 100, 50));
myGraphics.DrawRectangle(&myPen, 20, 30, 100, 50);
myGraphics.SetClip(&myRegion, CombineModeReplace);
myGraphics.DrawLine(&myPen, 0, 0, 200, 200);
---------------------------------------------------------------------
GraphicsPath 对象存储一系列直线和贝塞尔样条。您可以将若干种类型的曲线(椭圆、弧形和基数样条)添至路径,但在存储到路径中之前,各种曲线都会转化为贝塞尔样条。拉平路径包含将路径中的各条贝塞尔样条转化为一连串直线。
要拉平路径,可调用 GraphicsPath 对象的 Flatten 方法。Flatten 方法接收拉平参数,该参数指定拉平的路径和原始路径之间的最大距离。下面的插图显示了路径(拉平之前和拉平之后)。
---------------------------------------------------------------------
在使用 GDI+ 绘制直线时,要提供直线的起始点和结束点,但不需要提供有关线上单个像素的任何信息。GDI+ 与显示驱动程序软件共同工作,可确定在特定显示设备上显示直线要打开哪些像素。
请注意从点 (4,2) 到点 (16,10) 的红色直线。假定坐标系统的原点位于左上角且度量单位是像素。另外假定 x 坐标轴指向右边、y 坐标轴指向下边。下面的插图显示了在多颜色背景下绘制的红线的放大视图。
请注意,用来呈现该线的红色像素是不透明的。直线中没有部分透明的像素。这种类型的直线看上去带有锯齿,有点像楼梯。这种用楼梯状来表示直线的技术被称为锯齿化;楼梯是理论直线的一个别名。
一项更为复杂的呈现直线的技术需要使用部分透明的像素和不透明的像素。像素被设为纯红色或红色和背景色的混合色(取决于它们和直线的接近程度)。这种呈现方式被称为消除锯齿,它可以生成视觉上更感平滑的直线。下面的插图显示了如何混合特定的像素和背景来生成消除锯齿的直线。
消除锯齿(平滑化)也可应用于曲线。下面的插图显示了平滑椭圆的放大视图。
下面的插图显示了实际大小的同一个椭圆,一次没有使用消除锯齿,另一次使用了消除锯齿。
要绘制使用消除锯齿的直线和曲线,可创建 Graphics 对象,并将其 SmoothingMode 属性设为SmoothingMode.AntiAlias 或 SmoothingMode.HighQuality。然后调用同一个 Graphics 对象的某种绘制方法。
myGraphics.SetSmoothingMode(SmoothingModeAntiAlias);
myGraphics.DrawLine(&myPen, 0, 0, 12, 8);
--------------------------------------------------------------------------------
Image 类是抽象基类,它提供了处理光栅图像(位图)和矢量图像(图元文件)的方法。Bitmap 类和 Metafile 类都从Image 类继承。Bitmap 类提供了用于加载、保存和处理光栅图像的其他方法,因而扩展了 Image 类的功能。Metafile 类提供了用于记录和检查矢量图像的其他方法,因而扩展了 Image 类的功能。
位图是位的数组,它指定了像素矩阵中各像素的颜色。专用于单个像素的位数决定了可分配到该像素的颜色数。例如,如果用 4 位来呈现每个像素,那么一个给定的像素就可以分配到 16 (2^4 = 16) 种颜色中的一种。下表中的几个示例显示了可分配到由给定位数代表的像素的颜色数量。
每像素的位数 |
一个像素可分配到的颜色数量 |
1 |
2^1 = 2 |
2 |
2^2 = 4 |
4 |
2^4 = 16 |
8 |
2^8 = 256 |
16 |
2^16 = 65536 |
24 |
2^24 = 16, 777, 216 |
存储位图的磁盘文件通常包含一个或多个信息块,信息块中存储了如每像素位数、每行的像素数以及数组中的行数等信息。这样一个文件也可能包含颜色表(有时称为调色板)。颜色表将位图中的数值映射到特定的颜色。下面的插图显示了一幅放大的图像以及它的位图和颜色表。每个像素用一个 4 位数表示,那么颜色表中有 2^4 = 16 种颜色。表中的每种颜色用一个 24 位数表示:8位用于红色,8 位用于绿色,8 位用于蓝色。数字以十六进制(基 16)形式显示:A = 10,B = 11,C = 12,D = 13,E = 14,F = 15。
请看位于该图像第 3 行、第 5 列的像素。位图中相应的数值为 1。颜色表告诉我们 1 表示红色,所以该像素是红色的。该位图最上面一行中所有的项都是 3。颜色表告知我们 3 表示蓝色,所以该图像最上面一行中的所有像素都是蓝色。
注意 一些位图以上下颠倒的格式存储;位图首行中的数值对应于图像最下面一行的像素。
在颜色表中存储索引的位图被称为"调色板索引"位图。有些位图不需要颜色表。例如,如果位图使用每像素 24 位的格式,那么该位图就可以将颜色本身(而不是索引)存储到颜色表中。下面的插图显示了一个直接存储颜色(24 位/像素)而不使用颜色表的位图。该插图也显示了相应图像的放大视图。在位图中,FFFFFF 表示白色,FF0000 表示红色,00FF00 表示绿色,0000FF 表示蓝色。
图形文件格式
用于在磁盘文件中存储位图的标准格式有许多种。GDI+ 支持下面段落中描述的图形文件格式。
BMP
BMP 是 Windows 使用的一种标准格式,用于存储设备无关和应用程序无关的图像。一个给定 BMP 文件的每像素位数值(1、4、8、15、24、32 或 64)在文件头中指定。每像素 24 位的 BMP 文件是通用的。BMP 文件通常不压缩,因此不太适合通过 Internet 传输。
可交换图像文件格式 (GIF)
GIF 是一种用于在 Web 页中显示图像的通用格式。GIF 文件适用于画线、有纯色块的图片和在颜色之间有清晰边界的图片。GIF文件是压缩的,但是在压缩过程中没有信息丢失;解压缩的图像与原始图像完全一样。GIF 文件中的一种颜色可以被指定为透明,这样,图像将具有显示它的任何 Web 页的背景色。在单个文件中存储一系列 GIF 图像可以形成一个动画 GIF。GIF 文件每像素最多能存储 8 位,所以它们只限于使用 256 种颜色。
联合摄影专家组 (JPEG)
JPEG 是一种适应于自然景观(如扫描的照片)的压缩方案。一些信息会在压缩过程中丢失,但是这些丢失人眼是察觉不到的。JPEG 文件每像素存储 24 位,因此它们能够显示超过 16,000,000 种颜色。JPEG 文件不支持透明或动画。
JPEG 图像中的压缩级别是可以控制的,但是较高的压缩级别(较小的文件)会导致丢失更多的信息。对于一幅以 20:1 压缩比生成的图像,人眼难以把它和原始图像区别开来。下面的插图显示了一幅 BMP 图像和用这幅 BMP 图像压缩而得的两幅 JPEG 图像。第一幅 JPEG 的压缩比是 4:1,第二幅 JPEG 的压缩比是 8:1。
JPEG 压缩不适用于线条图形、纯色块和清晰边界。下面的插图显示了一幅 BMP 图像,以及两幅 JPEG 图像和一幅 GIF 图像。JPEG 和 GIF 图像从 BMP 图像压缩而得。GIF 的压缩比是 4:1,较小 JPEG 的压缩比是 4:1,较大 JPEG 的压缩比是 8:3。请注意,GIF 图像沿线条保持着清晰的边界,但 JPEG 图像的边界处则有些模糊。
JPEG 是一种压缩方案,不是一种文件格式。"JPEG 文件交换格式 (JFIF)"是一种文件格式,常用于存储和传输已根据 JPEG 方案压缩的图像。Web 浏览器显示的 JFIF 文件使用 .jpg 扩展名。
可交换图像文件 (EXIF)
EXIF 是一种适用于数码相机拍摄的照片的文件格式。EXIF 文件中含有根据 JPEG 规格压缩的图像。EXIF 文件中还包含了有关照片的信息(拍摄日期、快门速度、曝光时间,等等)和相机信息(制造商、型号,等等)。
可移植网络图形 (PNG)
PNG 格式不但保留了许多 GIF 格式的优点,还提供了超出 GIF 的功能。像 GIF 文件一样,PNG 文件在压缩时也不损失信息。PNG 文件能以每像素 8、24 或 48 位来存储颜色,并以每像素 1、2、4、8 或 16 位来存储灰度。相比之下,GIF 文件只能使用每像素 1、2、4 或 8 位。PNG 文件还可为每个像素存储一个 alpha 值,该值指定了该像素颜色与背景颜色混合的程度。
PNG 优于 GIF 之处在于它能够逐渐显示一幅图像,也就是说,当图像通过网络连接到达时显示将越来越近似。PNG 文件可包含伽玛校正和颜色校正信息,以便图像可在各种各样的显示设备上精确地呈现。
标签图像文件格式 (TIFF)
TIFF 是一种灵活的和可扩展的格式,各种各样的平台和图像处理应用程序都支持这种格式。TIFF 文件能以每像素任意位来存储图像,并可以使用各种各样的压缩算法。单个的多页 TIFF 文件可以存储数幅图像。可以把与图像相关的信息(扫描仪制造商、主机、压缩类型、打印方向和每像素采样,等等)存储在文件中并使用标签来排列这些信息。可以根据需要通过批准和添加新标签来扩展TIFF 格式。
GDI+ 提供 Metafile 类,以便您能够记录和显示图元文件。图元文件,也称为矢量图像,是一种存储为一系列绘图命令和设置的图像。Metafile 对象记录的命令和设置可以存储在内存中或保存到文件或流。
GDI+ 能够显示用以下格式存储的图元文件:
GDI+ 能够用 EMF 和 EMF+ 格式记录图元文件,但不能使用 WMF 格式。
EMF+ 是 EMF 的扩展,可存储 GDI+ 记录。EMF+ 格式有两种变体:"EMF+ 唯一"和"EMF+ 双重"。"EMF+ 唯一"图元文件只包含 GDI+ 记录。这样的图元文件可由 GDI+ 显示,但不可由 GDI 显示。"EMF+ 双重"图元文件包含 GDI+ 和 GDI 记录。"EMF+ 双重"图元文件的每个 GDI+ 记录与一个备用 GDI 记录成对出现。这样的图元文件可由 GDI+ 或 GDI 显示。
下面的示例显示了一个以前另存为文件的图元文件。该图元文件在显示时,左上角的位置是 (100,100)。
myMetafile = new Metafile(L"MyDiskFile.emf", hdc);
myGraphics = new Graphics(myMetafile);
myPen = new Pen(Color(255, 0, 0, 200));
myGraphics->SetSmoothingMode(SmoothingModeAntiAlias);
myGraphics->DrawLine(myPen, 0, 0, 60, 40);
delete myGraphics;
delete myPen;
delete myMetafile;
您可以使用 Bitmap 类来加载和显示光栅图像,还可以利用 Metafile 类来加载和显示矢量图像。Bitmap 和 Metafile 类从Image 类中继承。要显示矢量图像,需要有 Graphics 对象和 Metafile 对象。要显示光栅图像,需要有 Graphics 对象和Bitmap 对象。Graphics 对象提供了 DrawImage 方法,该方法接收 Metafile 或 Bitmap 对象作为参数。
下面的示例从文件 Climber.jpg 构造 Bitmap 对象并显示位图。图像左上角的目标点 (10,10) 在第二个和第三个参数中指定。
Image myImage(L"Climber.jpg");
myGraphics.DrawImage(&myImage, 10, 10);
下面的插图显示了该图像。
您可以从各种各样的图形文件格式(BMP、GIF、JPEG、EXIF、PNG、TIFF 和 ICON)中构造 Bitmap 对象。
下面的示例从各种各样的文件类型中构造 Bitmap 对象,然后显示位图:
Image myBMP(L"SpaceCadet.bmp");
Image myEMF(L"Metafile1.emf");
Image myGIF(L"Soda.gif");
Image myJPEG(L"Mango.jpg");
Image myPNG(L"Flowers.png");
Image myTIFF(L"MS.tif");
myGraphics.DrawImage(&myBMP, 10, 10);
myGraphics.DrawImage(&myEMF, 220, 10);
myGraphics.DrawImage(&myGIF, 320, 10);
myGraphics.DrawImage(&myJPEG, 380, 10);
myGraphics.DrawImage(&myPNG, 150, 200);
myGraphics.DrawImage(&myTIFF, 300, 200);
Bitmap 类提供了 Clone 方法,可用于制作现有 Bitmap 对象的副本。Clone 方法带有源矩形参数,可用于指定要复制的原始位图的部分。下面的示例通过克隆现有 Bitmap 对象的上半部分来创建 Bitmap 对象。然后绘制两幅图像。
Bitmap* originalBitmap = new Bitmap(L"Spiral.png");
RectF sourceRect(
0.0f,
0.0f,
(REAL)(originalBitmap->GetWidth()),
(REAL)(originalBitmap->GetHeight())/2.0f);
Bitmap* secondBitmap = originalBitmap->Clone(sourceRect, PixelFormatDontCare);
myGraphics.DrawImage(originalBitmap, 10, 10);
myGraphics.DrawImage(secondBitmap, 100, 10);
下面的插图显示这两幅图像。
您可以使用 Graphics 类的 DrawImage 方法来绘制并定位矢量图像和光栅图像。DrawImage 是一种重载方法,因此您有数种方式为它提供参数。DrawImage 方法的一种变体接收 Bitmap 对象和 Rectangle 对象。该矩形指定了绘图操作的目标,即它指定了将要在其内绘图的矩形。如果目标矩形的大小与原始图像的大小不同,原始图像将进行缩放,以适应目标矩形。下面的示例将同一图像绘制了三次:一次没有缩放,一次使用扩展,一次使用压缩:
Bitmap myBitmap(L"Spiral.png");
Rect expansionRect(80, 10, 2 * myBitmap.GetWidth(), myBitmap.GetHeight());
Rect compressionRect(210, 10, myBitmap.GetWidth() / 2,
myBitmap.GetHeight() / 2);
myGraphics.DrawImage(&myBitmap, 10, 10);
myGraphics.DrawImage(&myBitmap, expansionRect);
myGraphics.DrawImage(&myBitmap, compressionRect);
下面的插图显示了这三张图片。
DrawImage 方法的一些变体带有源矩形参数和目标矩形参数。源矩形参数指定原始图像要绘制的部分。目标矩形参数指定将要在其内绘制该图像指定部分的矩形。如果目标矩形的大小与源矩形的大小不同,图片将会缩放,以适应目标矩形。
下面的示例从文件 Runner.jpg 中构造 Bitmap 对象。整个图像绘制时在 (0,0) 处没有缩放。然后将该图像的一小部分绘制两次:一次使用压缩,一次使用扩展。
Bitmap myBitmap(L"Runner.jpg");
// The rectangle (in myBitmap) with upper-left corner (80, 70),
// width 80, and height 45, encloses one of the runner's hands.
// Small destination rectangle for compressed hand.
Rect destRect1(200, 10, 20, 16);
// Large destination rectangle for expanded hand.
Rect destRect2(200, 40, 200, 160);
// Draw the original image at (0, 0).
myGraphics.DrawImage(&myBitmap, 0, 0);
// Draw the compressed hand.
myGraphics.DrawImage(
&myBitmap, destRect1, 80, 70, 80, 45, UnitPixel);
// Draw the expanded hand.
myGraphics.DrawImage(
&myBitmap, destRect2, 80, 70, 80, 45, UnitPixel);
myBitmap, destRectangle2, sourceRectangle, GraphicsUnit.Pixel);
下面的插图显示了未缩放的图像,以及压缩的和扩展的图像部分。
--------------------------------------------------------------------------------
GDI+ 提供了全局变形和页面变形,以便您可以使绘制的项目变形(旋转、缩放、平移,等等)。两种变形还允许您使用多种坐标系统。
GDI+ 使用三个坐标空间:全局、页面和设备。
进行 myGraphics.DrawLine(myPen, 0, 0, 160, 80) 调用时,传递到 DrawLine 方法的点((0, 0) 和 (160, 80))位于全局坐标空间中。坐标先要通过变形序列,然后 GDI+ 才能在屏幕上绘制线条。一种变形将全局坐标转换为页面坐标,另一种变形将页面坐标转换为设备坐标。
假定您想使用原点位于工作区的主体而非左上角的坐标系统。例如,您需要让原点位于距工作区左边缘 100 像素、距顶部 50 像素的位置。下图显示了这样的坐标系统。
进行 myGraphics.DrawLine(myPen, 0, 0, 160, 80) 调用时,可获得下面插图中显示的线条。
下表显示了三种坐标空间中线条终点的坐标:
全局 |
(0, 0) 到 (160, 80) |
页 |
(100, 50) 到 (260, 130) |
设备 |
(100, 50) 到 (260, 130) |
请注意,页面坐标空间的原点在工作区的左上角,情况将总是如此。另外请注意,由于度量单位是像素,所以设备坐标与页面坐标是相同的。如果将度量单位设置为像素以外的其他单位(例如英寸),设备坐标将不同于页面坐标。
将全局坐标映射到页面坐标的变形称为"全局变形",保存在 Graphics 类的 Transform 属性中。在前面的示例中,全局变形是在 x 方向平移 100 个单位、在 y 方向平移 50 个单位。下面的示例设置了 Graphics 对象的全局变形,并随后使用该Graphics 对象来绘制前图中显示的线条:
myGraphics.TranslateTransform(100.0f, 50.0f);
myGraphics.DrawLine(&myPen, 0, 0, 160, 80);
将页面坐标映射到设备坐标的变形称为"页面变形"。Graphics 类提供了用于操作页面变形的 PageUnit 和 PageScale属性。Graphics 类还提供了两个只读属性:DpiX 和 DpiY,可用于检查显示设备每英寸的水平点和垂直点。
Graphics 类的 PageUnit 属性可用于指定像素以外的其他度量单位。下面的示例从 (0, 0) 至 (2, 1) 绘制线条,其中点 (2, 1)位于点 (0, 0) 的右边 2 英寸和下边 1 英寸处:
myGraphics.SetPageUnit(UnitInch);
myGraphics.DrawLine(&myPen, 0, 0, 2, 1);
注意 如果您在构造钢笔时不指定钢笔宽度,前面的示例将绘制一条一英寸宽的线条。您可以在 Pen 构造函数的第二个参数中指定钢笔宽度:
Pen myPen(Color(255, 0, 0, 0), 1/myGraphics.GetDpiX()).
如果我们假定显示设备在水平方向和垂直方向每英寸都有 96 个点,则上例中直线的终结点在三个坐标空间中分别具有以下坐标:
全局 |
(0, 0) 到 (2, 1) |
页 |
(0, 0) 到 (2, 1) |
设备 |
(0, 0) 到 (192, 96) |
请注意,由于全局坐标空间的原点在工作区的左上角,因此页面坐标与全局坐标相同。
您可以合并全局变形和页面变形,以实现多种效果。例如,假定您想使用英寸作为度量单位,并且想让坐标系统的原点距工作区左边缘 2 英寸、距工作区顶部 1/2 英寸。下面的示例设置 Graphics 对象的全局变形和页面变形,然后从 (0, 0) 到 (2, 1) 绘制线条:
myGraphics.TranslateTransform(2.0f, 0.5f);
myGraphics.SetPageUnit(UnitInch);
myGraphics.DrawLine(&myPen, 0, 0, 2, 1);
下图显示了线条和坐标系统。
如果我们假定显示设备在水平方向和垂直方向每英寸都有 96 个点,则上例中直线的终结点在三个坐标空间中分别具有以下坐标:
全局 |
(0, 0) 到 (2, 1) |
页 |
(2, 0.5) 到 (4, 1.5) |
设备 |
(192, 48) 到 (384, 144) |
m×n 矩阵是以 m 行和 n 列排列的一组数字。下图显示几个矩阵。
您可以通过将单个元素相加来加合两个尺寸相同的矩阵。下图显示了两个矩阵相加的示例。
m×n 矩阵可以与 n×p 矩阵相乘,得到的结果是 m×p 矩阵。第一个矩阵的列数必须与第二个矩阵的行数相同。例如,4×2 矩阵可与 2×3 矩阵相乘,产生 4×3 矩阵。
矩阵的行列的平面点可视为矢量。例如,(2, 5) 是具有两个组件的矢量,(3, 7, 1) 是具有三个组件的矢量。两个矢量的点产品定义如下:
(a, b) • (c, d) = ac + bd
(a, b, c) • (d, e, f) = ad + be + cf
例如,(2, 3) 和 (5, 4) 的点产品是 (2)(5) + (3)(4) = 22。(2, 5, 1) 和 (4, 3, 1) 的点产品是 (2)(4) + (5)(3) + (1)(1) = 24。请注意,两个矢量的点产品是数字,而不是另一个矢量。另外请注意,只有当两个矢量的组件数相同时,才能计算点产品。
将 A(i, j) 作为矩阵 A 中第 i 行和第 j 列的项。例如,A(3, 2)是矩阵 A 中第 3 行和第 2 列的项。假定 A、B 和 C 是矩阵,且AB = C,则 C 的项计算如下:
C(i, j) =(A 的第 i 行)•(B 的第 j 列)
下图显示了矩阵相乘的几个示例。
如果您将平面中的点视为 1×2 矩阵,则可通过将该点乘以 2×2 矩阵来变形该点。下图显示了应用于点 (2, 1) 的几个变形。
前图中显示的所有变形都是线性变形。某些其他变形(例如平移)不是线性的,也不能表示为与 2×2 矩阵相乘的形式。假定您要从点 (2, 1) 开始,将其旋转 90 度,在 x 方向将其平移 3 个单位,在 y 方向将其平移 4 个单位。可通过先使用矩阵乘法再使用矩阵加法来完成此操作。
后面是平移(与 1×2 矩阵相加)的线性变形(与 2×2 矩阵相乘)被称为仿射变形。将仿射变形存储于一对矩阵(一个用于线性部分,一个用于平移)的替换方案是将整个变形存储于 3×3 矩阵。要使其起作用,平面上的点必须存储于有虚拟第 3 坐标的1×3 矩阵。通常的技术是使所有的第 3 坐标等于 1。例如,矩阵 [2 1 1] 代表点 (2, 1)。下图显示了表示为与单个 3×3 矩阵相乘的仿射变形(旋转 90 度;在 x 方向上平移 3 个单位,在 y 方向上平移 4 个单位)。
在前面的示例中,点 (2, 1) 映射到了点 (2, 6)。请注意,3×3 矩阵的第三列包含数字 0, 0, 1。对于仿射变形的 3×3 矩阵而言,情况将总是如此。重要的数字是列 1 和列 2 中的 6 个数字。矩阵左上角的 2×2 部分表示变形的线性部分,第 3 行中的前两项表示平移。
在 GDI+ 中,您可以在 Matrix 对象中存储仿射变形。由于表示仿射变形的矩阵第三列总是(0, 0, 1),因此构建 Matrix 对象时,只需指定前两列中的 6 个数字。语句 Matrix myMatrix = new Matrix(0, 1, -1, 0, 3, 4) 构建了前图中显示的矩阵。
复合变形
复合变形是一个接一个的变形序列。请考虑下面列表中的矩阵和变形:
矩阵 A |
旋转 90 度 |
矩阵 B |
在 x 方向上缩放 2 倍 |
矩阵 C |
在 y 方向上平移 3 个单位 |
如果我们从矩阵 [2 1 1] 表示的点 (2, 1) 开始,并先后乘以 A、B、C,则点 (2, 1) 将按列出的顺序经历三种变形。
[2 1 1]ABC = [-2 5 1]
您可以不将复合变形的三部分存储于三个单独的矩阵,而是一起乘以 A、B 和 C,得到一个存储整个复合变形的单独的 3×3 矩阵。假定 ABC = D。则一个点乘以 D 得出的结果与一个点先后乘以 A、B、C 的结果相同。
[2 1 1]D = [-2 5 1]
下图显示了矩阵 A、B、C 和 D。
复合变形的矩阵可通过单个变形矩阵相乘来形成,这就意味着任何仿射变形的序列均可存储于一个单独的 Matrix 对象中。
警告 复合变形的顺序非常重要。一般说来,先旋转、再缩放、然后平移,与先缩放、再旋转、然后平移是不同的。同样,矩阵相乘的顺序也是重要的。一般说来,ABC 与 BAC 不同。
Matrix 类提供了建立复合变形的几种方法:Multiply、Rotate、RotateAt、Scale、Shear 和Translate。下面的示例创建了复合变形(先旋转 30 度,再在 y 方向上缩放 2 倍,然后在 x 方向平移 5 个单位)的矩阵。
Matrix myMatrix;
myMatrix.Rotate(30.0f);
myMatrix.Scale(1.0f, 2.0f, MatrixOrderAppend);
myMatrix.Translate(5.0f, 0.0f, MatrixOrderAppend);
下图显示该矩阵。
全局变形是应用于由给定的 Graphics 对象绘制的每个项目的变形。要创建全局变形,可构建 Graphics 对象,然后操作其Transform 属性。Transform 属性是 Matrix 对象,因此它能够保存仿射变形的任何序列。存储在 Transform 属性中的变形被称为全局变形。Graphics 类提供了建立复合全局变形的几种方法:MultiplyTransform、RotateTransform、ScaleTransform 和 TranslateTransform。下面的示例绘制了两次椭圆:一次在创建全局变形之前,一次在创建全局变形之后。变形首先在 y 方向上缩放 0.5 倍,再在 x 方向平移 50 个单位,然后旋转 30 度。
myGraphics.DrawEllipse(&myPen, 0, 0, 100, 50);
myGraphics.ScaleTransform(1.0f, 0.5f);
myGraphics.TranslateTransform(50.0f, 0.0f, MatrixOrderAppend);
myGraphics.RotateTransform(30.0f, MatrixOrderAppend);
myGraphics.DrawEllipse(&myPen, 0, 0, 100, 50);
下图显示了变形中涉及的矩阵。
注意 在前面的示例中,椭圆围绕坐标系统的原点旋转,该原点在工作区的左上角。与椭圆围绕其自身中心旋转相比,这样会产生不同的结果。
局部变形是应用于要绘制的特定项目的变形。例如,GraphicsPath 对象具有 Transform 方法,此方法可用于将该路径的数据点变形。下面的示例绘制了一个没有变形的矩形以及一个有旋转变形的路径。(假定没有全局变形)。
Matrix myMatrix;
myMatrix.Rotate(45.0f);
myGraphicsPath.Transform(&myMatrix);
myGraphics.DrawRectangle(&myPen, 10, 10, 100, 50);
myGraphics.DrawPath(&myPen, &myGraphicsPath);
全局变形可与局部变形合并,以得到多种结果。例如,全局变形可用于修正坐标系统,而局部变形可用于旋转和缩放在新坐标系统上绘制的对象。
假定您需要原点距工作区左边缘 200 像素、距工作区顶部 150 像素的坐标系统。此外,假定您需要的度量单位是像素,且 x 轴指向右方,y 轴指向上方。默认的坐标系统是 y 轴指向下方,因此您需要执行绕水平坐标轴的反射。下图显示了这样的矩阵反射。
下一步,假定您需要执行一个向右 200 个单位、向下 150 个单位的平移。
下面的示例建立通过设置 Graphics 对象的全局变形来描述的坐标系统。
Matrix myMatrix(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
myGraphics.SetTransform(&myMatrix);
myGraphics.TranslateTransform(200.0f, 150.0f, MatrixOrderAppend);
下面的代码(置于前面示例的结尾处)创建了由一个单独矩形组成的路径,该矩形的左下角在新坐标系统的原点。矩形经过两次填充:一次不使用局部变形,一次使用局部变形。局部变形包括在水平方向上缩放 2 倍,然后再旋转 30 度。
// Create the path.
GraphicsPath myGraphicsPath;
Rect myRect(0, 0, 60, 60);
myGraphicsPath.AddRectangle(myRect);
// Fill the path on the new coordinate system.
// No local transformation
myGraphics.FillPath(&mySolidBrush1, &myGraphicsPath);
// Transform the path.
Matrix myPathMatrix;
myPathMatrix.Scale(2, 1);
myPathMatrix.Rotate(30, MatrixOrderAppend);
myGraphicsPath.Transform(&myPathMatrix);
// Fill the transformed path on the new coordinate system.
myGraphics.FillPath(&mySolidBrush2, &myGraphicsPath);
下图显示了新的坐标系统和两个矩形。
--------------------------------------------------------------------------------
图形状态(剪辑区域、变形和质量设置)存储在 Graphics 对象中。GDI+ 允许您通过使用容器来临时替换或增加 Graphics 对象中状态的组成部分。您可以通过调用 Graphics 对象的 BeginContainer 方法来启动容器,通过调用 EndContainer 方法来结束容器。在 BeginContainer 和 EndContainer 之间,您对 Graphics 对象做出的任何状态更改都属于容器,且不改写Graphics 对象的现有状态。
下面的示例在 Graphics 对象中创建容器。Graphics 对象的全局变形是向右平移 200 个单位,而容器的全局变形是向下平移100 个单位。
myGraphics.TranslateTransform(200.0f, 0.0f);
myGraphicsContainer = myGraphics.BeginContainer();
myGraphics.TranslateTransform(0.0f, 100.0f);
myGraphics.DrawRectangle(&myPen, 0, 0, 50, 50);
myGraphics.EndContainer(myGraphicsContainer);
myGraphics.DrawRectangle(&myPen, 0, 0, 50, 50);
请注意,在前面的示例中,在调用 BeginContainer 和 EndContainer 之间执行语句 myGraphics.DrawRectangle(0, 0, 50, 50) 与在调用 EndContainer 之后执行同一语句,会产生不同的矩形。只有水平变形应用于在容器外做出的DrawRectangle 调用。两种变形(横向平移 200 个单位及纵向平移 100 个单位)都应用于在容器内进行的 DrawRectangle调用。下面的插图显示这两个矩形。
容器可以嵌套在另一容器中。下面的示例在 Graphics 对象中创建容器,并在第一个容器中创建另一容器。Graphics 对象的全局变换是在 x 方向平移 100 个单位,在 y 方向平移 80 个单位。第一个容器的全局变形是旋转 30 度。第二个容器的全局变形是在x 方向缩放 2 倍。对 DrawEllipse 方法的调用是在第二个容器内做出的。
myGraphics.TranslateTransform(100.0f, 80.0f, MatrixOrderAppend);
container1 = myGraphics.BeginContainer();
myGraphics.RotateTransform(30.0f, MatrixOrderAppend);
container2 = myGraphics.BeginContainer();
myGraphics.ScaleTransform(2.0f, 1.0f);
myGraphics.DrawEllipse(&myPen, -30, -20, 60, 40);
myGraphics.EndContainer(container2);
myGraphics.EndContainer(container1);
下图显示了该椭圆。
请注意,所有三种变形都应用于在第二个容器内做出的 DrawEllipse 调用。另外请注意变形的顺序:先缩放,再旋转,然后平移。最先应用最里面的变形,最后应用最外面的变形。
Graphics 对象的任何属性均可在容器内(对 BeginContainer 和 EndContainer 的调用之间)设置。例如,可在容器内设置剪辑区域。在容器内完成的任何绘图都将受制于该容器的剪辑区域,并将受制于任何外部容器的剪辑区域和 Graphics 对象自身的剪辑区域。
迄今为止所讨论的属性(全局变形和剪辑区域)由嵌套的容器联合在一起。嵌套的容器可临时替换其他属性。例如,如果在容器内将 SmoothingMode 属性设为 SmoothingMode.AntiAlias,那么在该容器内调用的任何绘图方法就都将使用 AntiAlias 平滑模式,但在 EndContainer 之后调用的绘图方法将使用在调用 BeginContainer 之前就位的平滑模式。
对于将 Graphics 对象的全局变形与容器合并的另一示例:假定您要绘制一只眼睛并将其置于面孔序列上的各种位置。下面的示例以坐标系统原点为中心绘制了一只眼睛:
void DrawEye(Graphics* pGraphics)
{
GraphicsContainer eyeContainer;
eyeContainer = pGraphics->BeginContainer();
Pen myBlackPen(Color(255, 0, 0, 0));
SolidBrush myGreenBrush(Color(255, 0, 128, 0));
SolidBrush myBlackBrush(Color(255, 0, 0, 0));
GraphicsPath myTopPath;
myTopPath.AddEllipse(-30, -50, 60, 60);
GraphicsPath myBottomPath;
myBottomPath.AddEllipse(-30, -10, 60, 60);
Region myTopRegion(&myTopPath);
Region myBottomRegion(&myBottomPath);
// Draw the outline of the eye.
// The outline of the eye consists of two ellipses.
// The top ellipse is clipped by the bottom ellipse, and
// the bottom ellipse is clipped by the top ellipse.
pGraphics->SetClip(&myTopRegion);
pGraphics->DrawPath(&myBlackPen, &myBottomPath);
pGraphics->SetClip(&myBottomRegion);
pGraphics->DrawPath(&myBlackPen, &myTopPath);
// Fill the iris.
// The iris is clipped by the bottom ellipse.
pGraphics->FillEllipse(&myGreenBrush, -10, -15, 20, 22);
// Fill the pupil.
pGraphics->FillEllipse(&myBlackBrush, -3, -7, 6, 9);
pGraphics->EndContainer(eyeContainer);
}
下图显示了眼睛和坐标轴。
前面示例中的 DrawEye 函数可接收 Graphics 对象,并立即在该 Graphics 对象中创建容器。此容器将任何调用 DrawEye 函数的代码与在执行 DrawEye 函数期间做出的属性设置隔离开来。例如,DrawEye 函数中的代码设置 Graphics 对象的剪辑区域,但当 DrawEye 将控件返回到调用例程时,剪辑区域将和它在调用 DrawEye 之前的状态一样。
下面的示例绘制三个椭圆(面孔),每个里面都有一只眼睛:
// Draw an ellipse with center at (100, 100).
myGraphics.TranslateTransform(100.0f, 100.0f);
myGraphics.DrawEllipse(&myBlackPen, -40, -60, 80, 120);
// Draw the eye at the center of the ellipse.
DrawEye(&myGraphics);
// Draw an ellipse with center at 200, 100.
myGraphics.TranslateTransform(100.0f, 0.0f, MatrixOrderAppend);
myGraphics.DrawEllipse(&myBlackPen, -40, -60, 80, 120);
// Rotate the eye 40 degrees, and draw it 30 units above
// the center of the ellipse.
myGraphicsContainer = myGraphics.BeginContainer();
myGraphics.RotateTransform(-40.0f);
myGraphics.TranslateTransform(0.0f, -30.0f, MatrixOrderAppend);
DrawEye(&myGraphics);
myGraphics.EndContainer(myGraphicsContainer);
// Draw a ellipse with center at (300.0f, 100.0f).
myGraphics.TranslateTransform(100.0f, 0.0f, MatrixOrderAppend);
myGraphics.DrawEllipse(&myBlackPen, -40, -60, 80, 120);
// Stretch and rotate the eye, and draw it at the
// center of the ellipse.
myGraphicsContainer = myGraphics.BeginContainer();
myGraphics.ScaleTransform(2.0f, 1.5f);
myGraphics.RotateTransform(45.0f, MatrixOrderAppend);
DrawEye(&myGraphics);
myGraphics.EndContainer(myGraphicsContainer); myGraphics.EndContainer(myGraphicsContainer);
下图显示了这三个椭圆。
在前面的示例中,所有的椭圆都是调用 DrawEllipse(myBlackPen, -40, -60, 80, 120) 来绘制的,此调用以坐标系统的原点为中心绘制椭圆。通过设置 Graphics 对象的全局变形,将椭圆从工作区左上角移开。语句myGraphics.TranslateTransform(100, 100) 使第一个椭圆以 (100, 100) 为中心。语句myGraphics.TranslateTransform(100, 0) 使第二个椭圆的中心在第一个椭圆的中心右边 100 个单位处。同样,第三个椭圆的中心在第二个椭圆的中心右边 100 个单位处。
前面示例中的容器用于相对给定椭圆的中心来变形眼睛。第一只眼睛在椭圆中心处绘制,没有变形,因此不在容器中调用DrawEye。第二只眼睛旋转 40 度并在椭圆中心上方 30 个单位处绘制,因此在容器内调用 DrawEye 函数和设置变形的方法。第三只眼睛经过拉伸和旋转,并在椭圆中心处绘制。像处理第二只眼睛一样,在容器内调用 DrawEye 函数和设置变形的方法。