1. 程式人生 > >C#的圖形繪製基礎知識

C#的圖形繪製基礎知識



圖形繪製基礎

Windows的使用者介面中,當建立一個視窗,並在該視窗進行繪圖時,一般要宣告一個派生於System.Windows.Forms.Form的類。如果要編寫一個定製控制元件,就要宣告一個派生於System.Windows.Forms.UserControl的類。在這兩種情況下,都重寫了虛擬函式OnPaint()只要視窗的任何一部分需要重新繪製,Windows都會呼叫這個函式

在這個事件中,PaintEventArgs類是一個引數。在PaintEventArgs中有兩個重要的資訊:Graphics物件和ClipRectangle物件。

Graphics類:

這個類封裝了一個GDI+

繪圖介面。有3種基本型別的繪圖介面:

lWindows和螢幕上的控制元件

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的類,例如SolidBrushTextureBrushLinearGradientBrush

Brush類在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(100,60,50,50));

        // 實現漸變色

        Brush linearGradientBrush = new LinearGradientBrush(new Rectangle(10,60,50,50), Color.Red, Color.White, 45);//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);//<span style="font-family:KaiTi_GB2312;font-size:12px;">繪製一個黑色矩形</span>

        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格式。

載入影象

使用紋理畫筆進行繪圖

使用鋼筆繪製圖像

使用影象繪製文字

未使用雙倍緩衝

使用雙倍緩衝
   
例程下載