C#的图形绘制基础知识

2010-08-28 10:49:44来源:西部e网作者:

图形绘制基础

Windows的用户界面中,当创建一个窗口,并在该窗口进行绘图时,一般要声明一个派生于System.Windows.Forms.Form的类。如果要编写一个定制控件,就要声明一个派生于System.Windows.Forms.UserControl的类。在这两种情况下,都重写了虚拟函数OnPaint()。只要窗口的任何一部分需要重新绘制,Windows都会调用这个函数。

在这个事件中,PaintEventArgs类是一个参数。在PaintEventArgs中有两个重要的信息:Graphics对象和ClipRectangle对象。

 

Graphics类:

这个类封装了一个GDI+绘图界面。有3种基本类型的绘图界面:

l         Windows和屏幕上的控件

l         要发送给打印机的页面

l         内存中的位图和图像

Graphics类提供了可以在这些绘图界面上绘图的功能。在其他功能中,我们可以使用它绘制圆弧、曲线、Bezier曲线、椭圆、图像、线条、矩形和文本。

给窗口获得Graphics对象有两种不同的方式。首先是重写OnPaint()事件,该事件是一个Form类继承Control类的虚拟方法。下面利用从该事件的PaintEventArgs中获取Graphics对象:

            protected override void OnPaint(PaintEventArgs e)

            {

                Graphics g = e.Graphics;

                // do our drawing here

            }

有时,需要直接在窗口中绘图,而无需等待OnPaint()事件。例如要编写代码,选择窗口中的某些图像(类似于在Windows Explorer中选择图标),或者用鼠标拖动一些对象,就是这种情况。在窗体上调用CreateGraphics()方法就可以获得一个Graphics对象,这是Form类继承Control类的另一个方法:

            protected void Form1_Click(object sender, System.EventArgs e)

            {

                Graphics g = this.CreateGraphics();

                // do our drawing here

                g.Dispose();    // this is important

            }

只有在无用存储单元收集器(GC)调用了析构函数时,我们才不必调用Dispose(),但不能确保GC何时运行。所以很可能在释放这些资源前系统资源就被用尽了,所以需要手动释放这些资源。还有一种方法就是使用using关键字,在对象超出作用域时using结构会自动调用Dispose()

            using(Graphics g = this.CreateGraphics())

            {

                g.DrawLine(Pens.Black,new Point(0,0),new Point(3,5));

            }

 

坐标系统:

GDI+的坐标系统建立在通过像素中心的假想数学直线上,这些直线从0开始,其左上角的交点是X=0,Y=0(简短记号是(0,0)/Ponit(0,0)。

在绘制线条时,GDI+ 会把绘制出来的像素在指定的数学直线上对中。在绘制整数坐标的水平线时,可以认为每个像素的一半落在假想数学直线的上半部分,而另一半落在假想数学直线的下半部分。

这里有一点需要注意的地方,如果指定了宽度为5(例如画一个从(1,0)(6,4)的矩形),就会在水平方向上绘制6个像素。但如果考虑到数学直线通过像素中心,则该矩形只有5个像素宽,绘制的线条有一半像素落在假想数学直线的外面,一半像素则落在假想数学直线的里面。

不仅如此,如果使用图形保真技术进行绘图,其他像素就会上一半的颜色,创建出光滑的线条,部分避免了对角线的“台阶”外观。

在绘图时,常常用3种结构指定坐标:PointSizeRectangle

Point

GDI+使用Point表示一个点。这是一个二维平面上的点——一个像素的表示方式。许多GDI+函数例如DrawLine(),以Point作为其参数。声明和构造Point的代码如下所示:

Point p = new Point(1,1);

通过其公用属性可以获得和设置PointXY坐标。

Size

GDI+使用Size表示一个尺寸(像素)。Size结构包含宽度和高度。声明和构造Size的代码如下所示:

Size s = new Size(5,5);

通过其公用属性可以获得和设置Size的宽度和高速。

Rectangle

有两个构造函数。一个构造函数的参数是X坐标、Y坐标、宽度和高度。另一个构造函数的参数是PointSize结构(Point定义矩形的左上角,Size定义其大小)。声明方式如下:

            Rectangle r1 = new Rectangle(1,2,5,6);

 

            Point p = new Point(1,2);

            Size s = new Size(5,6);

            Rectangle r2 = new Rectangle(p,s);

Rectangle的成员:

公共字段:

Empty:表示其属性未被初始化的 Rectangle 结构。

公共属性:

Bottom:获取此 Rectangle 结构下边缘的 y 坐标。

Height:获取或设置此 Rectangle 结构的高度。

IsEmpty:测试此 Rectangle 的所有数值属性是否都具有零值。

Left:获取此 Rectangle 结构左边缘的 x 坐标。

Location:获取或设置此 Rectangle 结构左上角的坐标。

Right:获取此 Rectangle 结构右边缘的 x 坐标。

Size:获取或设置此 Rectangle 的大小。

Top:获取此 Rectangle 结构上边缘的 y 坐标。

Width:获取或设置此 Rectangle 结构的宽度。

X:获取或设置此 Rectangle 结构左上角的 x 坐标。

Y:获取或设置此 Rectangle 结构左上角的 y 坐标。

公共方法:

Ceiling:通过将 RectangleF 值舍入到比它大的相邻整数值,将指定的 RectangleF 结构转换为 Rectangle 结构。

Contains:已重载。确定指定的点是否包含在此 Rectangle 定义的矩形区域范围内。

Equals:已重写。测试 obj 是否为与此 Rectangle 结构具有相同位置和大小的 Rectangle 结构。

FromLTRB:创建一个具有指定边缘位置的 Rectangle 结构。

GetHashCode:已重写。返回此 Rectangle 结构的哈希代码。有关如何使用哈希代码的信息,请参见 Object.GetHashCode

Inflate:已重载。创建并返回指定 Rectangle 结构的放大副本。该副本被放大指定的量。

Intersect:已重载。将此 Rectangle 结构替换为其自身与指定 Rectangle 结构的交集。

IntersectsWith:确定此矩形是否与 rect 相交。

Offset:已重载。将此矩形的位置调整指定的量。

Round:通过将 RectangleF 舍入到最近的整数值,将指定的 RectangleF 转换为 Rectangle

ToString:已重写。将此 Rectangle 的属性转换为可读字符串。

Truncate:通过截断 RectangleF 值,将指定的 RectangleF 转换为 Rectangle

Union:获取包含两个 Rectangle 结构的交集的 Rectangle 结构。

公共运算符:

相等运算符:测试两个 Rectangle 结构的位置和大小是否相同。

不等运算符:测试两个 Rectangle 结构的位置或大小是否不同。

GraphicsPaths

这个类表示一系列连接的线条和曲线。在构造一条路径时,可以添加线条、Bezier曲线、圆弧、饼形图、多边形和矩形等。在构造一条复杂的路径后,可以用一个操作绘制路径:调用DrawPath()。可以调用FillPath()填充路径。

使用一个点数组和PathTypes构造GraphicsPathPathTypes是一个byte数组,其中的每个元素对应于点数组中的每一个元素,并给出了路径如何通过这些点来构造的其他信息。例如,如果点是路径的起始点,那么这个点的路径类型就是PathPointType.Start。如果点是两个线条的连接点,那么这个点的路径类型就是PathPointType.Line。如果点用于构造一条从前一点到后一点之间的Bezier曲线,路经类型就是PathPointType.Bezier

注意要使用GraphicsPaths需要引入如下命名空间:

using System.Drawing.Drawing2D;

示例,用四条线段创建一个图形路径:

            GraphicsPath path;

            path = new GraphicsPath(new Point[]{

                new Point(10,10),

                new Point(150,10),

                new Point(200,150),

                new Point(10,150),

                new Point(200,160)

                },new byte[]{

                    (byte)PathPointType.Start,

                    (byte)PathPointType.Line,

                    (byte)PathPointType.Line,

                    (byte)PathPointType.Line,

                    (byte)PathPointType.Line

                }

            );

            using(Graphics g = this.CreateGraphics())

            {

                g.DrawPath(Pens.Black,path);

            }

Regions

这个类是一个复杂的图形,由矩形和路径组成。在构造了一个Region后,就可以使用FillRegion()方法绘制该区域。

下面的代码创建了一个区域,给它添加一个Rectangle和一个GraphicsPath,再用蓝色填充该区域:

            Rectangle r1 = new Rectangle(10,10,50,50);

            Rectangle r2 = new Rectangle(40,40,50,50);

            Region r = new Region(r1);

            r.Union(r2);

 

            GraphicsPath path;

            path = new GraphicsPath(new Point[]{

                new Point(45,45),

                new Point(145,55),

                new Point(200,150),

                new Point(75,150),

                new Point(45,45)

                },new byte[]{

                    (byte)PathPointType.Start,

                    (byte)PathPointType.Bezier,

                    (byte)PathPointType.Bezier,

                    (byte)PathPointType.Bezier,

                    (byte)PathPointType.Line

                }

            );

            r.Union(path);

            using(Graphics g = this.CreateGraphics())

            {

                g.FillRegion(Brushes.Blue,r);

            }

颜色:

可以用两种不同的方式来表示,一种是RGB(将红、绿、蓝色值传送给Color结构的一个函数),另一种是把颜色分解为3种组件(色调、饱和度和亮度)。

GetBrightness:获取此 Color 结构的色调-饱和度-亮度(HSB) 的亮度值。

GetHue:获取此 Color 结构的色调-饱和度-亮度(HSB) 的色调值,以度为单位。

GetSaturation:获取此 Color 结构的色调-饱和度-亮度(HSB) 的饱和度值。

GDI+中的颜色还有第4个组件:Alpha组件。使用这个组件可以设置颜色的不透明度,以便创建淡入淡出效果。

图形绘制进阶-线条、字体

使用Pen类绘制线条

Pen类在System.Drawing名称空间中。

例如,如下代码即可在Form窗体加载调用绘图方法时绘制一些直线

    protected override void OnPaint(PaintEventArgs e)

    {

        Graphics g = e.Graphics;

            using (Pen blackPen = new Pen(Color.Black,1))

        {

            for (int y = 0;y < ClientRectangle.Height;y += ClientRectangle.Height / 10)

            {

                g.DrawLine(blackPen, new Point(0,0), new Point(ClientRectangle.Width,y));

            }

        }

    }

获得Pen有更简单的方式,Pens包含的属性可以包含创建大约150种钢笔,每个钢笔都有前面介绍的约定义颜色。下面我们使用这种钢笔做一个例子:

    protected override void OnPaint(PaintEventArgs e)

    {

        for (int y = 0;y < ClientRectangle.Height;y += ClientRectangle.Height / 10)

        {

            e.Graphics.DrawLine(Pens.Black, new Point(0,0), new Point(ClientRectangle.Width,y));

        }

    }

这样就可以不用创建一个Pen的对象,最后也不用担心忘记释放对象而调用Dispose()方法。

 

使用Brush类绘制图形

Brush类是一个抽象的基类,要实例化一个Brush对象,应适用派生于Brush的类,例如SolidBrushTextureBrushLinearGradientBrushBrush类在System.Drawing名称空间中,但TextureBrushLinearGradientBrushSystem.Drawing.Drawing2D名称空间中。

1SolidBrush用一种单色填充图形。

2TextureBrush用一个位图填充图形。在构造这个画笔时,还指定了一个边框矩形和一个填充模式。边框矩形指定画笔适用位图的哪一部分——可以不使用整个位图。填充模式有许多选项,包括平铺纹理的TileTileFlipXTileFlipYTileFlipXY,它们指定连续平铺时翻转对象。使用TextureBrush可以创建非常有趣和富有相像力的效果。

3LinearGradientBrush封装了一个画笔,该画笔可以绘制两种颜色渐变的图形,其中第一种颜色以指定的角度逐渐过渡到第二种颜色。角度则可以根据程度来指定。0º表示颜色从左向右过渡。90º表示颜色从上到下过渡。

还有一种画笔PathGradientBrush,它可以创建精细的阴影效果,其中阴影从路径的中心趋向路径的边界。这种画笔可以让人想起用彩笔绘制的阴影地图,可以实现类似在不同的省或国家爱之间的边界上涂上较暗的颜色。

使用的时候需要对窗体的构造函数进行相应的修改:

    public Form3()

    {

        //

        // Windows 窗体设计器支持所必需的

        //

        InitializeComponent();

        // 控件被绘制为不透明的,不绘制背景

        SetStyle(ControlStyles.Opaque,true);

 

        //

        // TODO: InitializeComponent 调用后添加任何构造函数代码

        //

    }

会出现如下效果,默认运行时是个透明白色的背景。但是将任何一个窗口覆盖上后就绘制成了被覆盖部分的位图。

覆盖前:


    当将选中的窗口覆盖他后就会出现如下效果:


    就是被重新绘制了。

(由于图片上传出现问题,不知如何上传,以前可以上传的名字都不能用了,还有好多以前可用的图片现在都不可用了,好像不接受中文命名)

 

这并不是我们需要的。我们引入System.Drawing.Drawing2D命名空间,为了此名称空间下的使用LinearGradientBrush画笔。

    protected override void OnPaint(PaintEventArgs e)

    {

        Graphics g = e.Graphics;

        g.FillRectangle(Brushes.White, ClientRectangle);

        g.FillRectangle(Brushes.Red, new Rectangle(10,60,50,50));

            // 实现渐变色

       Brush linearGradientBrush = new LinearGradientBrush(new Rectangle(10,60,50,50), Color.Red, Color.White, 45);

        g.FillRectangle(linearGradientBrush, new Rectangle(10,60,50,50));

 

        linearGradientBrush.Dispose();

 

        g.FillEllipse(Brushes.Aquamarine, new Rectangle(60,20,50,30));

        g.FillPie(Brushes.Chartreuse, new Rectangle(60,60,50,50),90,210);

        g.FillPolygon(Brushes.BlueViolet, new Point[]{new Point(110,10),new Point(150,10),new Point(160,40),new Point(120,20),new Point(120,60)});

    }

 

使用Font绘制文本

Font类封装了字体的3个主要特征:字体系列、字体大小和字体样式。Font类在System.Drawing明称空间中。

.NET Framework中,Size并不仅仅是点的大小,通过Unit属性可以改变GraphicsUnit属性,Unit定义了字体的测量单位。一个点等于1/72英寸,所以10点的字体有10/72英寸高。在GraphicsUnit枚举中,可以把字体的大小指定为:

l         点的大小

l         显示大小(1/75英寸)

l         文档(1/300英寸)

l         英寸

l         毫米

l         像素

一般情况下,屏幕上每英寸有72个像素。打印机上每英寸右300个像素、600个像素,甚至更多。使用Graphics对象的MeasureString()方法可以计算出给定字体的字符串宽度。

    protected override void OnPaint(PaintEventArgs e)

    {

        Graphics g = e.Graphics;

        string str = "This is a string";

        SizeF size = g.MeasureString(str,Font);

        g.DrawRectangle(Pens.Black,0,0,size.Width,size.Height);

        g.DrawString(str,Font,Brushes.Blue,new RectangleF(0,0,size.Width,size.Height));

    }

这个例子将在获取字符串的宽度和高度后绘制出一个黑色矩形,然后将字符串以蓝色绘制在矩形中。

StringFormat类封装了文本局部信息,包括对齐和行间距信息。

        public Form3()

        {

            //

            // Windows 窗体设计器支持所必需的

            //

            InitializeComponent();

            // 控件被绘制为不透明的,不绘制背景

            SetStyle(ControlStyles.Opaque,true);

            Bounds = new Rectangle(0,0,500,300);

 

            //

            // TODO: InitializeComponent 调用后添加任何构造函数代码

            //

        }

        protected override void OnPaint(PaintEventArgs e)

        {

            Graphics g = e.Graphics;

            int y = 0;

            g.FillRectangle(Brushes.White,ClientRectangle);

 

            // Draw left justifed text

            Rectangle rect = new Rectangle(0,y,400,Font.Height);

            g.DrawRectangle(Pens.Blue,rect);

            g.DrawString("This text is left justified.",Font,Brushes.Black,rect);

            y += Font.Height + 20;

 

            // Draw right justifed text

            Font aFont = new Font("Arial",16,FontStyle.Bold);

            rect = new Rectangle(0,y,400,aFont.Height);

            g.DrawRectangle(Pens.Blue,rect);

            StringFormat sf = new StringFormat();

            sf.Alignment = StringAlignment.Far;

            g.DrawString("This text is right justified.",aFont,Brushes.Blue,rect,sf);

            y += Font.Height + 20;

            aFont.Dispose();

 

            // Draw centered text

            Font cFont = new Font("Courier New",12,FontStyle.Underline);

            rect = new Rectangle(0,y,400,cFont.Height);

            g.DrawRectangle(Pens.Blue,rect);

            sf = new StringFormat();

            sf.Alignment = StringAlignment.Center;

            g.DrawString("This text is centered and underlined.",cFont,Brushes.Red,rect,sf);

            y += Font.Height + 20;

            cFont.Dispose();

 

            // Draw multiline text

            Font trFont = new Font("Times New Roman",12);

            rect = new Rectangle(0,y,400,trFont.Height * 3);

            g.DrawRectangle(Pens.Blue,rect);

            String longString = "This text is much longer, and drawn ";

            longString += "into a rectangle that is higher than ";

            longString += "one line, so that it will wrap. It is ";

            longString += "very easy to wrap text using GDI+.";

            g.DrawString(longString,trFont,Brushes.Black,rect);

            trFont.Dispose();

        }

实现画不同类型的字体。

 

图形绘制进阶-图像(双倍缓冲)

 

图像在GDI+中有很多用途。当然,可以在窗口中绘制图像,也可以用图像创建画笔(TextureBrush),再绘制用该图像填充的图形。

Image类在System.Drawing命名空间中。

图像另一个非常重要的用途是双倍缓冲的图形编程技巧。有时要创建的图形非常精细复杂,即使使用目前运行速度最快的机器,也需要很长时间才能绘制出来。观察图像在屏幕中一点一点地绘制出来,并不是一件令人愉快的事。这类应用程序有映射应用程序和复杂的CAD/CAM应用程序。在这个技巧中,并不在把图形绘制在窗口中,而是绘制到一个图像中。在完成了图像的绘制后,再把该图像绘制到窗口中。这个技巧就成为双倍缓冲。一些其他的绘制技巧还涉及到在多个图层上绘制,即首先绘制背景,再在背景的上面绘制对象,最后在对象的上面绘制文本。如果这个图形直接在屏幕上绘制,用户就会看到一个闪烁的效果。双倍缓冲可以消除这种闪烁效果。

Image本身是一个抽象类,它有两个子类:BitmapMetafile

Bitmap类用于一般的图像,有高度和宽度属性。下面的一个小例子就是从文件中加载一个Bitmap图像,并绘制它。也可以从该类中创建画笔,再使用该画笔创建一个钢笔,以绘制线条,也可以使用该画笔绘制文本。

位图有几个可能的来源。可以从文件中加载位图,位图也可以来自打开的流,还可以从另一个现有的图像中创建位图。位图可以创建为空白的图像,以便在其上绘制。在从文件中读取图像时,该图像可以是JPEGGIFBMP格式。

加载图像

使用纹理画笔进行绘图

使用钢笔绘制图像

使用图像绘制文本

未使用双倍缓冲

使用双倍缓冲
    例程下载

 

原文地址:http://www.cnblogs.com/Bear-Study-Hard/archive/2006/03/13/349100.html

关键词:C#

赞助商链接: