gdi与gdi+绘图效率
With this one you're reading, I'm starting a series of articles focused on creating and manipulating graphics in .Net using GDI+ library starting at the most basic level of knowledge in this area. I'll post all the source code in Visual Basic.net, as it is my preferred language, although the code would look much similar and is really easy to translate from VB.net to C# using one of the many excellent online translators (for example,
通过阅读您正在阅读的这篇文章,我将开始一系列文章,着重于使用GDI +库在.Net中创建和处理图形的文章,从该领域的最基本知识开始。 我会将所有源代码都发布在Visual Basic.net中,因为它是我的首选语言,尽管该代码看起来非常相似,并且使用许多出色的在线翻译器之一(实际上是从VB.net到C#的翻译)非常容易(对于例,
this one). 这个 )。介绍 (Introduction)
Microsoft在Windows XP中引入了GDI +,作为GDI的改进和替代。 Windows的所有早期版本中都存在GDI(图形设备接口),它是OS的核心组件,负责表示图形对象并将其传输到输出设备(例如监视器和打印机)。 请注意,GDI不负责绘制窗口,菜单和其他Windows OS核心图形对象(这些功能在用户子系统中)。As said, Windows XP replaced the old GDI libraries with GDI+, which improved GDI by adding new features and optimizing existing features. MSDN defines GDI+ as "the portion of the Windows XP operating system or Windows Server 2003 operating system that provides two-dimensional vector graphics, imaging, and typography". The .Net Framework graphics system uses GDI+ through the System.Drawing namespace.
如上所述,Windows XP用GDI +代替了旧的GDI库,它通过添加新功能和优化现有功能来改进GDI。 MSDN将GDI +定义为“ Windows XP操作系统或Windows Server 2003操作系统中提供二维矢量图形,图像和排版的部分”。 .Net Framework图形系统通过System.Drawing命名空间使用GDI +。
旧版图形? (Legacy graphics?)
MSDN将GDI +描述为旧版图形系统。 是真的吗 好吧,用严格的术语来说,Microsoft已经引入了几个新的图形库和渲染引擎,例如DirectX(可能是Windows现有的最强大的图形引擎)。 但是事实是DirectX的对象模型非常复杂,在实际情况下,使用GDI +足以绘制涉及笔,画笔,背景,渐变,路径,文本渲染等的2D图形。当然,GDI +自Windows XP以来,所有版本的Windows都支持Windows XP,甚至在Windows 8刚刚发布的今天,它仍然是Redmond OS的重要组成部分。与设备无关 (Device-independient)
GDI和GDI +的主要特征是它们都是与设备无关的。 这意味着,如果要绘制某些东西(例如,带有蓝色边框和白色文本的红色圆圈),则将使用相同的指令集在监视器上进行绘制并在打印机上进行打印。 图形系统负责与适当的系统驱动程序进行通信,以将您的指令“翻译”为驱动程序可以理解的内容。 显然,这对程序员来说是一个很大的优势。设备上下文和图形对象 (The Device Context and the Graphics object)
过去,当我们使用GDI编程时,我们想绘制一些东西,我们要做的第一件事是将设备上下文获取到我们要绘制的设备上。 设备上下文(通常称为DC)是一种结构,它定义了一组图形对象及其关联的属性以及影响输出的图形模式。 因此,例如,如果我们想将某些东西绘制到Windows窗体中,则需要通过其Handle(或hWnd)将DC传递给该窗体。 出于绘画目的,我们可以为视频显示,打印机和内存打开DC(以绘制到内存位图中)。All of these operations involved the use of several Windows API functions, and many times the work was painful. Now, the .Net Framework has simplified it all and gives to us the Graphics object that is very close to a DC, but lets us deal with it as a managed class with simple methods and properties.
所有这些操作都涉及多个Windows API函数的使用,并且很多时候工作很痛苦。 现在,.Net Framework简化了所有操作,并为我们提供了非常接近DC的Graphics对象,但让我们将其作为具有简单方法和属性的托管类来处理。
GDI +的一部分 (Parts of GDI+)
正如MSDN所说,GDI +服务分为以下3大类:2-D vector graphics. These are primitive graphics such as lines, curves and figures, that can be specified by sets of points in a coordinate system. For example, a straight line is specified by its two endpoints, while a rectangle is specified by a point defining its upper-left corner and 2 numbers defining its width and height. 2D矢量图形。 这些是原始图形,例如直线,曲线和图形,可以通过坐标系中的点集来指定。 例如,一条直线由其两个端点指定,而矩形由一个定义其左上角的点和两个数字定义其宽度和高度。
Imaging. There are certain kinds of pictures which are difficult (or impossible) to display using only 2-D vector graphics. For example, think of a high resolution picture of a leaping lion to hunt antelope in full African savannah. This is not possible to represent (or at least it is extremely difficult) with vector graphics. So these images are treated as bitmaps, which are arrays of numbers representing individual colored dots. 成像。 有某些类型的图片仅使用2维矢量图形很难(或不可能)显示。 例如,想像一下一幅高分辨率的图片,上面有一只跳跃的狮子在非洲大草原上猎杀羚羊。 这不可能用矢量图形表示(或至少非常困难)。 因此,这些图像被视为位图,它们是代表单个彩色点的数字数组。
Typography. Typography is the display of text in a variety of fonts, sizes, and styles. GDI+ provides extensive support for this complex task. One of the new features in GDI+ (not present in GDI) is subpixel antialiasing, which gives text rendered on an LCD screen a smoother appearance. 版式。 印刷术是显示各种字体,大小和样式的文本。 GDI +为这项复杂的任务提供了广泛的支持。 亚像素抗锯齿功能是GDI +中的一项新功能(GDI中未提供),它可以使在LCD屏幕上呈现的文本看起来更平滑。
图形类 (The Graphics class)
As this article is being eminently theoretical, and because I would like the following one to be much more practical, it is essential to enter deeper into the heart of the whole system of graphical manipulation in GDI+ with .Net: the System.Drawing.Graphics class.
由于本文的理论性很强,并且由于我希望以下文章更加实用,因此必须使用.Net深入研究GDI +中的图形处理整个系统的核心: System.Drawing.Graphics类。
You can think of the Graphics object as a canvas on which you are about to paint a picture. But in this picture, besides painting straight lines, curves, colors, dots, shapes, etc, you will be able to perform complex transformations anytime. You can rotate, scale, shake, paint again and transform again the times you want. Finally, you can show your picture to the world (usually on a screen, but it could be on a printer) or save it to a bitmap for posterity.
您可以将Graphics对象视为要在其上绘制图片的画布。 但是在这张图片中,除了绘制直线,曲线,颜色,点,形状等外,您还可以随时执行复杂的变换。 您可以再次旋转,缩放,摇动,绘制并再次变换所需的时间。 最后,您可以向世界展示您的图片(通常在屏幕上,但也可以在打印机上),也可以将其保存到位图中以供后代使用。
But, how can you obtain a Graphics object to start drawing? The Graphics class has no public constructors, so you cannot create a new Graphics object from scratch. This is for a good reason: a Graphics object is closely related to a drawing surface, so you need to access first the drawing surface (for example, a form, a control or a printer) and then obtain a Graphics object that the drawing surface will provide to you.
但是,如何获取Graphics对象开始绘制? Graphics类没有公共构造函数,因此您不能从头开始创建新的Graphics对象。 这是有充分理由的:Graphics对象与图形表面密切相关,因此您需要首先访问图形表面(例如,窗体,控件或打印机),然后获取图形对象作为图形表面将为您提供。
Let's see some examples.
让我们看一些例子。
Getting a Graphics object to a Form or a Control
获取图形对象到窗体或控件
The class System.Windows.Forms.Contr
类System.Windows.Forms.Contr ol实现了CreateGraphics函数,这正是获取Graphics对象以在控件上绘制所需要的。 类System.Windows.Forms.Form, 因为它继承自System.Windows.Forms.Contr ol,也有此功能。 因此,如果要在窗体或控件上进行绘制,则只需调用此函数,该函数将返回一个Graphics对象,该对象准备开始绘制。
But using this method can have a disadvantage: your drawings on the Graphics object will not be persistent, which means that they will disappear as soon as your form or control is beign repainted.
但是使用此方法可能有一个缺点:Graphics对象上的图形将不会持久保存,这意味着一旦重新绘制窗体或控件,它们就会消失。
To test it, let's make a very simple example of what I'm saying. In Visual Studio, create a new VB.net Windows Forms application. Form1 is created by default. Now put a standard Button into the form and move the button to the bottom-right corner. You should have something like this:
为了测试它,让我们举一个非常简单的例子来说明我的意思。 在Visual Studio中,创建一个新的VB.net Windows窗体应用程序。 默认情况下创建Form1。 现在,将一个标准的Button放入表单中,并将该按钮移到右下角。 您应该具有以下内容:
Now, let's put some code into the Button1.Click event handler. Double-click on the button to open the Code Window. Button1_Click is created by default. Put this code inside the Button1_Click method:
现在,让我们将一些代码放入Button1.Click事件处理程序中。 双击按钮以打开“代码窗口”。 默认情况下创建Button1_Click。 将此代码放入Button1_Click方法中:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Using g As Graphics = Me.CreateGraphics
g.DrawEllipse(Pens.Black, New Rectangle(10, 10, 100, 100))
End Using
End Sub
Even if you've never worked with System.Drawing classes, this code is very easy to understand. First, we called the Form's CreateGraphics function to get a ready-to-use Graphics object. Then, we called the DrawEllipse method of the Graphics object to draw a black circle on the Form starting at (10,10) coordinates and with a width and height of 100 pixels.
即使您从未使用过System.Drawing类,此代码也非常易于理解。 首先,我们调用了Form的CreateGraphics函数来获取一个现成的Graphics对象。 然后,我们调用Graphics对象的DrawEllipse方法,以从(10,10)坐标开始并以100像素的宽度和高度在Form上绘制一个黑色圆圈。
If you run your project and click on the button, you should have something like this:
如果您运行项目并单击按钮,则应该具有以下内容:
Now, minimize the Form and then restore it. What happened? The circle has disappeared. But, why? Well, each time that your form (or any part of it) needs to be repainted, it is responding to the WM_PAINT system message by calling BeginPaint to obtain a device context, wrapping this device context with a managed Graphics class and passing this class to your drawing code in the PaintEventArgs which are provided first to the OnPaint protected method and then, in turn, to the Paint event. So the graphics on your form are overriden by the graphics of the Graphics object provided in the Paint event.
现在,最小化窗体,然后将其还原。 发生了什么? 圈子不见了。 但为什么? 嗯,每次需要重新绘制表单(或其任何部分)时,它都会通过调用BeginPaint获取设备上下文,使用托管Graphics类包装该设备上下文并将此类传递给WM_PAINT系统消息进行响应。 PaintEventArgs中的绘图代码,该代码首先提供给OnPaint受保护的方法,然后提供给Paint事件。 因此,窗体上的图形会被Paint事件中提供的Graphics对象的图形覆盖。
Fortunately, the solution here is very simple. If you want to draw persistent graphics on a form or control, then use the Graphics object provided in the Paint event. Let's try! Edit your form's code and write this code in the form's Paint event:
幸运的是,这里的解决方案非常简单。 如果要在窗体或控件上绘制持久图形,请使用Paint事件中提供的Graphics对象。 我们试试吧! 编辑表单的代码,并将此代码写入表单的Paint事件中:
Private Sub Form1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
e.Graphics.DrawEllipse(Pens.Black, New Rectangle(10, 10, 100, 100))
End Sub
Now, run the project. The circle is there from the beginning, because it is drawn every time that the form paints itself. Minimize the form and restore it: the circle is still there.
现在,运行项目。 圆圈从一开始就存在,因为每次表格绘制时都会画一个圆圈。 最小化表格并恢复它:圆圈仍然存在。
Well, now you know how to get a valid Graphics object to draw on a form or control and you have the ability to make your graphics persistent.
好了,现在您知道了如何获取有效的Graphics对象以在窗体或控件上进行绘制,并且可以使图形持久化。
Getting a Graphics object to a printer
将图形对象获取到打印机
Although is not a very common task to draw directly on a printer using a Graphics object, in fact it can be done and is as easy as doing it on a Form. You can do it, for example, with a PrintDocument component, which provides a Graphics object in the parameters of its PrintPage event.
尽管使用Graphics对象直接在打印机上绘制不是一项很常见的任务,但实际上它可以完成,并且与在Form上一样容易。 例如,您可以使用PrintDocument组件执行此操作,该组件在其PrintPage事件的参数中提供Graphics对象。
If you want to test it, continue with the previous Visual Studio project and simply put a PrintDocument component from the Toolbox into your Form. Switch to code view and edit the PrintPage event of the PrintDocument object. Write this code:
如果要测试它,请继续上一个Visual Studio项目,只需将工具箱中的PrintDocument组件放入窗体中即可。 切换到代码视图并编辑PrintDocument对象的PrintPage事件。 编写这段代码:
Private Sub PrintDocument1_PrintPage(sender As System.Object, e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
With e.Graphics
.PageUnit = GraphicsUnit.Millimeter
.DrawEllipse(Pens.Black, New Rectangle(30, 30, 50, 50))
End With
e.HasMorePages = False
End Sub
Now, modify your Button1_Click event to print the document:
现在,修改Button1_Click事件以打印文档:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Me.PrintDocument1.Print()
End Sub
As you can see, the "e" parameter has a .Graphics property that is a valid Graphics object to the printer. What we are doing here is change the page unit of the Graphics object to millimeters, and then draw a circle at 3 cm from the left and top borders of the page, and with a width and height of 5 cm. If you run your project and click the button, then you should obtain that circle in your default printer.
如您所见,“ e”参数具有.Graphics属性,该属性是打印机的有效Graphics对象。 我们在这里所做的是将Graphics对象的页面单位更改为毫米,然后在距页面的左边界和顶边界3 cm处绘制一个圆圈,宽度和高度为5 cm。 如果运行项目并单击按钮,则应该在默认打印机中获得该圆圈。
Getting a Graphics object to an in-memory bitmap
将Graphics对象获取到内存中的位图
Earlier I said that is not a common task to draw directly on the Graphics object of a printer. This is because is much common to edit a in-memory bitmap and then print the bitmap. Create images from scratch (or edit existing images) is a very easy task with System.Drawing. Let's see it modifying your Button1_Click event:
之前我曾说过,直接在打印机的Graphics对象上绘制并不是常见的任务。 这是因为编辑内存位图然后打印位图是很常见的。 使用System.Drawing从头开始创建图像(或编辑现有图像)是一项非常简单的任务。 让我们看看它如何修改Button1_Click事件:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Using bmp As Bitmap = New Bitmap(50, 50)
Using g As Graphics = Graphics.FromImage(bmp)
g.Clear(Color.White)
g.DrawEllipse(Pens.Black, New Rectangle(10, 10, 30, 30))
End Using
bmp.Save("c:\testbitmap.png", Imaging.ImageFormat.Png)
End Using
End Sub
Now let's explain this code. First we create a new in-memory bitmap with a width and height of 50 pixels. Then we get a valid Graphics object to the bitmap using the FromImage static function of the System.Drawing.Graphics class. After, we set the background of the bitmap to a solid white color, and finally we draw a circle starting at (10,10) pixels and with a width and height of 30 pixels. Finally, we save the bitmap to disk in .png format.
现在让我们解释一下这段代码。 首先,我们创建一个新的内存中位图,其宽度和高度为50像素。 然后,使用System.Drawing.Graphics类的FromImage静态函数将有效的Graphics对象获得到位图。 之后,我们将位图的背景设置为纯白色,最后绘制一个以(10,10)像素为起点,宽度和高度为30像素的圆圈。 最后,我们将位图以.png格式保存到磁盘。
If you run your project and click the button, you should obtain a "c:\testbitmap.png" that should look exactly like this:
如果您运行项目并单击按钮,则应获得一个“ c:\ testbitmap.png”,其外观应完全如下所示:
As you can see, in-memory creation and manipulation of bitmaps is very easy.
如您所见,在内存中创建和操作位图非常容易。
结论 (Conclusion)
在本文中,我已经解释了GDI和GDI +的起源,并讨论了其一些特征。 我还介绍了Graphics类,它是.Net中所有绘图操作的基础,而System.Drawing是使用GDI +的名称空间。 在下一篇文章中,我将详细解释坐标系。 一旦开始绘制,这是必不可少的步骤,以准确地知道我们绘制的方式和位置。参考资料 (References)
GDI on Wikipedia 维基百科上的GDI GDI+ on msdn msdn上的GDI + GDI on msdn msdn上的GDI DirectX Development Site DirectX开发站点 The System.Drawing.Graphics class on msdn msdn上的System.Drawing.Graphics类 Bob Powell's GDI+ FAQ Bob Powell的GDI +常见问题解答翻译自: https://www.experts-exchange.com/articles/11371/Drawing-with-NET-and-GDI-Part-1-The-Basics.html
gdi与gdi+绘图效率