C#的圖形繪制基礎(chǔ)知識(shí)

2010-08-28 10:49:44來源:西部e網(wǎng)作者:

圖形繪制基礎(chǔ)

Windows的用戶界面中,當(dāng)創(chuàng)建一個(gè)窗口,并在該窗口進(jìn)行繪圖時(shí),一般要聲明一個(gè)派生于System.Windows.Forms.Form的類。如果要編寫一個(gè)定制控件,就要聲明一個(gè)派生于System.Windows.Forms.UserControl的類。在這兩種情況下,都重寫了虛擬函數(shù)OnPaint()。只要窗口的任何一部分需要重新繪制,Windows都會(huì)調(diào)用這個(gè)函數(shù)。

在這個(gè)事件中,PaintEventArgs類是一個(gè)參數(shù)。在PaintEventArgs中有兩個(gè)重要的信息:Graphics對(duì)象和ClipRectangle對(duì)象。

 

Graphics類:

這個(gè)類封裝了一個(gè)GDI+繪圖界面。有3種基本類型的繪圖界面:

l         Windows和屏幕上的控件

l         要發(fā)送給打印機(jī)的頁面

l         內(nèi)存中的位圖和圖像

Graphics類提供了可以在這些繪圖界面上繪圖的功能。在其他功能中,我們可以使用它繪制圓弧、曲線、Bezier曲線、橢圓、圖像、線條、矩形和文本。

給窗口獲得Graphics對(duì)象有兩種不同的方式。首先是重寫OnPaint()事件,該事件是一個(gè)Form類繼承Control類的虛擬方法。下面利用從該事件的PaintEventArgs中獲取Graphics對(duì)象:

            protected override void OnPaint(PaintEventArgs e)

            {

                Graphics g = e.Graphics;

                // do our drawing here

            }

有時(shí),需要直接在窗口中繪圖,而無需等待OnPaint()事件。例如要編寫代碼,選擇窗口中的某些圖像(類似于在Windows Explorer中選擇圖標(biāo)),或者用鼠標(biāo)拖動(dòng)一些對(duì)象,就是這種情況。在窗體上調(diào)用CreateGraphics()方法就可以獲得一個(gè)Graphics對(duì)象,這是Form類繼承Control類的另一個(gè)方法:

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

            {

                Graphics g = this.CreateGraphics();

                // do our drawing here

                g.Dispose();    // this is important

            }

只有在無用存儲(chǔ)單元收集器(GC)調(diào)用了析構(gòu)函數(shù)時(shí),我們才不必調(diào)用Dispose(),但不能確保GC何時(shí)運(yùn)行。所以很可能在釋放這些資源前系統(tǒng)資源就被用盡了,所以需要手動(dòng)釋放這些資源。還有一種方法就是使用using關(guān)鍵字,在對(duì)象超出作用域時(shí)using結(jié)構(gòu)會(huì)自動(dòng)調(diào)用Dispose()。

            using(Graphics g = this.CreateGraphics())

            {

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

            }

 

坐標(biāo)系統(tǒng):

GDI+的坐標(biāo)系統(tǒng)建立在通過像素中心的假想數(shù)學(xué)直線上,這些直線從0開始,其左上角的交點(diǎn)是X=0,Y=0(簡(jiǎn)短記號(hào)是(0,0)/Ponit(0,0)。

在繪制線條時(shí),GDI+ 會(huì)把繪制出來的像素在指定的數(shù)學(xué)直線上對(duì)中。在繪制整數(shù)坐標(biāo)的水平線時(shí),可以認(rèn)為每個(gè)像素的一半落在假想數(shù)學(xué)直線的上半部分,而另一半落在假想數(shù)學(xué)直線的下半部分。

這里有一點(diǎn)需要注意的地方,如果指定了寬度為5(例如畫一個(gè)從(1,0)(6,4)的矩形),就會(huì)在水平方向上繪制6個(gè)像素。但如果考慮到數(shù)學(xué)直線通過像素中心,則該矩形只有5個(gè)像素寬,繪制的線條有一半像素落在假想數(shù)學(xué)直線的外面,一半像素則落在假想數(shù)學(xué)直線的里面。

不僅如此,如果使用圖形保真技術(shù)進(jìn)行繪圖,其他像素就會(huì)上一半的顏色,創(chuàng)建出光滑的線條,部分避免了對(duì)角線的“臺(tái)階”外觀。

在繪圖時(shí),常常用3種結(jié)構(gòu)指定坐標(biāo):Point,SizeRectangle。

Point

GDI+使用Point表示一個(gè)點(diǎn)。這是一個(gè)二維平面上的點(diǎn)——一個(gè)像素的表示方式。許多GDI+函數(shù)例如DrawLine(),以Point作為其參數(shù)。聲明和構(gòu)造Point的代碼如下所示:

Point p = new Point(1,1);

通過其公用屬性可以獲得和設(shè)置PointXY坐標(biāo)。

Size

GDI+使用Size表示一個(gè)尺寸(像素)。Size結(jié)構(gòu)包含寬度和高度。聲明和構(gòu)造Size的代碼如下所示:

Size s = new Size(5,5);

通過其公用屬性可以獲得和設(shè)置Size的寬度和高速。

Rectangle

有兩個(gè)構(gòu)造函數(shù)。一個(gè)構(gòu)造函數(shù)的參數(shù)是X坐標(biāo)、Y坐標(biāo)、寬度和高度。另一個(gè)構(gòu)造函數(shù)的參數(shù)是PointSize結(jié)構(gòu)(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 結(jié)構(gòu)。

公共屬性:

Bottom:獲取此 Rectangle 結(jié)構(gòu)下邊緣的 y 坐標(biāo)。

Height:獲取或設(shè)置此 Rectangle 結(jié)構(gòu)的高度。

IsEmpty:測(cè)試此 Rectangle 的所有數(shù)值屬性是否都具有零值。

Left:獲取此 Rectangle 結(jié)構(gòu)左邊緣的 x 坐標(biāo)。

Location:獲取或設(shè)置此 Rectangle 結(jié)構(gòu)左上角的坐標(biāo)。

Right:獲取此 Rectangle 結(jié)構(gòu)右邊緣的 x 坐標(biāo)。

Size:獲取或設(shè)置此 Rectangle 的大小。

Top:獲取此 Rectangle 結(jié)構(gòu)上邊緣的 y 坐標(biāo)。

Width:獲取或設(shè)置此 Rectangle 結(jié)構(gòu)的寬度。

X:獲取或設(shè)置此 Rectangle 結(jié)構(gòu)左上角的 x 坐標(biāo)。

Y:獲取或設(shè)置此 Rectangle 結(jié)構(gòu)左上角的 y 坐標(biāo)。

公共方法:

Ceiling:通過將 RectangleF 值舍入到比它大的相鄰整數(shù)值,將指定的 RectangleF 結(jié)構(gòu)轉(zhuǎn)換為 Rectangle 結(jié)構(gòu)。

Contains:已重載。確定指定的點(diǎn)是否包含在此 Rectangle 定義的矩形區(qū)域范圍內(nèi)。

Equals:已重寫。測(cè)試 obj 是否為與此 Rectangle 結(jié)構(gòu)具有相同位置和大小的 Rectangle 結(jié)構(gòu)。

FromLTRB:創(chuàng)建一個(gè)具有指定邊緣位置的 Rectangle 結(jié)構(gòu)。

GetHashCode:已重寫。返回此 Rectangle 結(jié)構(gòu)的哈希代碼。有關(guān)如何使用哈希代碼的信息,請(qǐng)參見 Object.GetHashCode。

Inflate:已重載。創(chuàng)建并返回指定 Rectangle 結(jié)構(gòu)的放大副本。該副本被放大指定的量。

Intersect:已重載。將此 Rectangle 結(jié)構(gòu)替換為其自身與指定 Rectangle 結(jié)構(gòu)的交集。

IntersectsWith:確定此矩形是否與 rect 相交。

Offset:已重載。將此矩形的位置調(diào)整指定的量。

Round:通過將 RectangleF 舍入到最近的整數(shù)值,將指定的 RectangleF 轉(zhuǎn)換為 Rectangle。

ToString:已重寫。將此 Rectangle 的屬性轉(zhuǎn)換為可讀字符串。

Truncate:通過截?cái)?SPAN lang=EN-US> RectangleF 值,將指定的 RectangleF 轉(zhuǎn)換為 Rectangle。

Union:獲取包含兩個(gè) Rectangle 結(jié)構(gòu)的交集的 Rectangle 結(jié)構(gòu)。

公共運(yùn)算符:

相等運(yùn)算符:測(cè)試兩個(gè) Rectangle 結(jié)構(gòu)的位置和大小是否相同。

不等運(yùn)算符:測(cè)試兩個(gè) Rectangle 結(jié)構(gòu)的位置或大小是否不同。

GraphicsPaths

這個(gè)類表示一系列連接的線條和曲線。在構(gòu)造一條路徑時(shí),可以添加線條、Bezier曲線、圓弧、餅形圖、多邊形和矩形等。在構(gòu)造一條復(fù)雜的路徑后,可以用一個(gè)操作繪制路徑:調(diào)用DrawPath()?梢哉{(diào)用FillPath()填充路徑。

使用一個(gè)點(diǎn)數(shù)組和PathTypes構(gòu)造GraphicsPathPathTypes是一個(gè)byte數(shù)組,其中的每個(gè)元素對(duì)應(yīng)于點(diǎn)數(shù)組中的每一個(gè)元素,并給出了路徑如何通過這些點(diǎn)來構(gòu)造的其他信息。例如,如果點(diǎn)是路徑的起始點(diǎn),那么這個(gè)點(diǎn)的路徑類型就是PathPointType.Start。如果點(diǎn)是兩個(gè)線條的連接點(diǎn),那么這個(gè)點(diǎn)的路徑類型就是PathPointType.Line。如果點(diǎn)用于構(gòu)造一條從前一點(diǎn)到后一點(diǎn)之間的Bezier曲線,路經(jīng)類型就是PathPointType.Bezier。

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

using System.Drawing.Drawing2D;

示例,用四條線段創(chuàng)建一個(gè)圖形路徑:

            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

這個(gè)類是一個(gè)復(fù)雜的圖形,由矩形和路徑組成。在構(gòu)造了一個(gè)Region后,就可以使用FillRegion()方法繪制該區(qū)域。

下面的代碼創(chuàng)建了一個(gè)區(qū)域,給它添加一個(gè)Rectangle和一個(gè)GraphicsPath,再用藍(lán)色填充該區(qū)域:

            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(將紅、綠、藍(lán)色值傳送給Color結(jié)構(gòu)的一個(gè)函數(shù)),另一種是把顏色分解為3種組件(色調(diào)、飽和度和亮度)。

GetBrightness:獲取此 Color 結(jié)構(gòu)的色調(diào)-飽和度-亮度(HSB) 的亮度值。

GetHue:獲取此 Color 結(jié)構(gòu)的色調(diào)-飽和度-亮度(HSB) 的色調(diào)值,以度為單位。

GetSaturation:獲取此 Color 結(jié)構(gòu)的色調(diào)-飽和度-亮度(HSB) 的飽和度值。

GDI+中的顏色還有第4個(gè)組件:Alpha組件。使用這個(gè)組件可以設(shè)置顏色的不透明度,以便創(chuàng)建淡入淡出效果。

圖形繪制進(jìn)階-線條、字體

使用Pen類繪制線條

Pen類在System.Drawing名稱空間中。

例如,如下代碼即可在Form窗體加載調(diào)用繪圖方法時(shí)繪制一些直線

    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有更簡(jiǎn)單的方式,Pens包含的屬性可以包含創(chuàng)建大約150種鋼筆,每個(gè)鋼筆都有前面介紹的約定義顏色。下面我們使用這種鋼筆做一個(gè)例子:

    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));

        }

    }

這樣就可以不用創(chuàng)建一個(gè)Pen的對(duì)象,最后也不用擔(dān)心忘記釋放對(duì)象而調(diào)用Dispose()方法。

 

使用Brush類繪制圖形

Brush類是一個(gè)抽象的基類,要實(shí)例化一個(gè)Brush對(duì)象,應(yīng)適用派生于Brush的類,例如SolidBrushTextureBrushLinearGradientBrush。Brush類在System.Drawing名稱空間中,但TextureBrushLinearGradientBrushSystem.Drawing.Drawing2D名稱空間中。

1SolidBrush用一種單色填充圖形。

2TextureBrush用一個(gè)位圖填充圖形。在構(gòu)造這個(gè)畫筆時(shí),還指定了一個(gè)邊框矩形和一個(gè)填充模式。邊框矩形指定畫筆適用位圖的哪一部分——可以不使用整個(gè)位圖。填充模式有許多選項(xiàng),包括平鋪紋理的Tile、TileFlipXTileFlipYTileFlipXY,它們指定連續(xù)平鋪時(shí)翻轉(zhuǎn)對(duì)象。使用TextureBrush可以創(chuàng)建非常有趣和富有相像力的效果。

3LinearGradientBrush封裝了一個(gè)畫筆,該畫筆可以繪制兩種顏色漸變的圖形,其中第一種顏色以指定的角度逐漸過渡到第二種顏色。角度則可以根據(jù)程度來指定。0º表示顏色從左向右過渡。90º表示顏色從上到下過渡。

還有一種畫筆PathGradientBrush,它可以創(chuàng)建精細(xì)的陰影效果,其中陰影從路徑的中心趨向路徑的邊界。這種畫筆可以讓人想起用彩筆繪制的陰影地圖,可以實(shí)現(xiàn)類似在不同的省或國(guó)家愛之間的邊界上涂上較暗的顏色。

使用的時(shí)候需要對(duì)窗體的構(gòu)造函數(shù)進(jìn)行相應(yīng)的修改:

    public Form3()

    {

        //

        // Windows 窗體設(shè)計(jì)器支持所必需的

        //

        InitializeComponent();

        // 控件被繪制為不透明的,不繪制背景

        SetStyle(ControlStyles.Opaque,true);

 

        //

        // TODO: InitializeComponent 調(diào)用后添加任何構(gòu)造函數(shù)代碼

        //

    }

會(huì)出現(xiàn)如下效果,默認(rèn)運(yùn)行時(shí)是個(gè)透明白色的背景。但是將任何一個(gè)窗口覆蓋上后就繪制成了被覆蓋部分的位圖。

覆蓋前:


    當(dāng)將選中的窗口覆蓋他后就會(huì)出現(xiàn)如下效果:


    就是被重新繪制了。

(由于圖片上傳出現(xiàn)問題,不知如何上傳,以前可以上傳的名字都不能用了,還有好多以前可用的圖片現(xiàn)在都不可用了,好像不接受中文命名)

 

這并不是我們需要的。我們引入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));

            // 實(shí)現(xiàn)漸變色

       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個(gè)主要特征:字體系列、字體大小和字體樣式。Font類在System.Drawing明稱空間中。

.NET Framework中,Size并不僅僅是點(diǎn)的大小,通過Unit屬性可以改變GraphicsUnit屬性,Unit定義了字體的測(cè)量單位。一個(gè)點(diǎn)等于1/72英寸,所以10點(diǎn)的字體有10/72英寸高。在GraphicsUnit枚舉中,可以把字體的大小指定為:

l         點(diǎn)的大小

l         顯示大。1/75英寸)

l         文檔(1/300英寸)

l         英寸

l         毫米

l         像素

一般情況下,屏幕上每英寸有72個(gè)像素。打印機(jī)上每英寸右300個(gè)像素、600個(gè)像素,甚至更多。使用Graphics對(duì)象的MeasureString()方法可以計(jì)算出給定字體的字符串寬度。

    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));

    }

這個(gè)例子將在獲取字符串的寬度和高度后繪制出一個(gè)黑色矩形,然后將字符串以藍(lán)色繪制在矩形中。

StringFormat類封裝了文本局部信息,包括對(duì)齊和行間距信息。

        public Form3()

        {

            //

            // Windows 窗體設(shè)計(jì)器支持所必需的

            //

            InitializeComponent();

            // 控件被繪制為不透明的,不繪制背景

            SetStyle(ControlStyles.Opaque,true);

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

 

            //

            // TODO: InitializeComponent 調(diào)用后添加任何構(gòu)造函數(shù)代碼

            //

        }

        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();

        }

實(shí)現(xiàn)畫不同類型的字體。

 

圖形繪制進(jìn)階-圖像(雙倍緩沖)

 

圖像在GDI+中有很多用途。當(dāng)然,可以在窗口中繪制圖像,也可以用圖像創(chuàng)建畫筆(TextureBrush),再繪制用該圖像填充的圖形。

Image類在System.Drawing命名空間中。

圖像另一個(gè)非常重要的用途是雙倍緩沖的圖形編程技巧。有時(shí)要?jiǎng)?chuàng)建的圖形非常精細(xì)復(fù)雜,即使使用目前運(yùn)行速度最快的機(jī)器,也需要很長(zhǎng)時(shí)間才能繪制出來。觀察圖像在屏幕中一點(diǎn)一點(diǎn)地繪制出來,并不是一件令人愉快的事。這類應(yīng)用程序有映射應(yīng)用程序和復(fù)雜的CAD/CAM應(yīng)用程序。在這個(gè)技巧中,并不在把圖形繪制在窗口中,而是繪制到一個(gè)圖像中。在完成了圖像的繪制后,再把該圖像繪制到窗口中。這個(gè)技巧就成為雙倍緩沖。一些其他的繪制技巧還涉及到在多個(gè)圖層上繪制,即首先繪制背景,再在背景的上面繪制對(duì)象,最后在對(duì)象的上面繪制文本。如果這個(gè)圖形直接在屏幕上繪制,用戶就會(huì)看到一個(gè)閃爍的效果。雙倍緩沖可以消除這種閃爍效果。

Image本身是一個(gè)抽象類,它有兩個(gè)子類:BitmapMetafile。

Bitmap類用于一般的圖像,有高度和寬度屬性。下面的一個(gè)小例子就是從文件中加載一個(gè)Bitmap圖像,并繪制它。也可以從該類中創(chuàng)建畫筆,再使用該畫筆創(chuàng)建一個(gè)鋼筆,以繪制線條,也可以使用該畫筆繪制文本。

位圖有幾個(gè)可能的來源?梢詮奈募屑虞d位圖,位圖也可以來自打開的流,還可以從另一個(gè)現(xiàn)有的圖像中創(chuàng)建位圖。位圖可以創(chuàng)建為空白的圖像,以便在其上繪制。在從文件中讀取圖像時(shí),該圖像可以是JPEG,GIFBMP格式。

加載圖像

使用紋理畫筆進(jìn)行繪圖

使用鋼筆繪制圖像

使用圖像繪制文本

未使用雙倍緩沖

使用雙倍緩沖
    例程下載

 

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

關(guān)鍵詞:C#

贊助商鏈接: