GDI+ 是一種通用的面向對象的 .NET 應用程序繪圖模型。GDI+ 在 .NET 里有很多用途,包括向打印機輸出文檔、在一個 Windows 應用程序里顯示圖形、以及在網頁里呈現圖形。
你可以創建采用了用戶指定信息的富圖形,也可以基於數據庫記錄動態呈現圖表或圖形。
GDI+ 編程的核心是 System.Drawing.Graphics 類。它封裝了一個 GDI+ 繪圖表面,它可能是一個窗口、一個打印文檔或者一個內存里的位圖。ASP.NET 人員很少需要繪制窗口或者打印文檔,因此只有最后一個選項是最實際的。
要在 ASP.NET 里使用 GDI+,需要遵循如下 4 個步驟:
- 創建一個內存里的位圖,在其上你將完成所有繪制工作。
- 為圖像創建一個 GDI+ 圖形上下文。這令你獲得你需要的 System.Drawing.Graphics 實例。
- 使用 Graphics 的實例方法完成繪制。你可以繪制和填充線條和形狀,你甚至可以從現有文件中復制位圖內容。
- 使用 Response.OutputStream 屬性輸出圖像到瀏覽器。
之后,會介紹一些例子,確保以下命名空間被引入:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
簡單繪制
下例展示了最簡單的 GDI+ 頁面,所有的工作都在 Load 事件里執行。
先創建 System.Drawing.Bitmap 類的一個實例來創建一個內存中的位圖,需要指定圖像的高度和寬度,單位為像素。應盡可能的小,較大的位圖會消耗更多服務器內存,會減慢傳輸時間。
// Create the in-memory bitmap where you will draw the image.
// This bitmap is 300 pixel wide and 50 pixel high.
Bitmap image = new Bitmap(300, 50);
接着為圖像創建 GDI+ 繪圖的上下文,由 System.Drawing.Graphics 對象表示。該對象提供方法讓你在內存里的位圖上繪制內容。只需使用靜態方法 Graphics.FromImage()即可。
Graphics g = Graphics.FromImage(image);
下面是很有意思的的部分。使用 Graphics 類的方法,可以繪制文本,形狀和位圖上的圖像。本例中用實的白色背景填充一個矩形(新位圖中的每個像素最初被設置為黑色)。
// Draw a solid white rectangle.
// Start from point(1,1).
// Make it 298 pixels wide and 48 pixels high.
g.FillRectangle(Brushes.White, 1, 1, 298, 48);
下一部分使用 System.Drawing.Font 對象來呈現一個靜態的標簽信息。不要將它與 ASP.NET 控件里用來指定網頁中所需字體的 FontInfo 對象混淆。因為圖像是在服務器上生成的,所有創建圖形時可以使用服務器上安裝好的任何字體,而客戶端並不需要這些字體,客戶端收到的是文本呈現后的圖像。
Font font = new Font("Impact", 20, FontStyle.Regular);
將文本繪制到位圖上,(10,5)指定文本位於位圖上左上角的坐標:
g.DrawString("This is a test", font, Brushes.Blue, 10, 5);
一旦圖像完成,就可以使用 Image.Save()將其發送到瀏覽器(瀏覽器的響應流)。然后,它就被發送到客戶端,並且在客戶端的瀏覽器里顯示。當類似這樣的響應流直接寫入數據時,你的圖像替代了任何其他網頁數據並且跳過了 Web 控件模型!
// Render the image to the output stream.
image.Save(Response.OutputStream, ImageFormat.Gif);
注意,可以保存圖像到任意一個有效的流,包括一個 FileStream。這使你能保存動態生成的圖像到磁盤。
完成后,應該立即調用圖像和繪圖上下文的 Dispose() 方法,因為兩者都占用了一些不會立即被釋放的非托管資源:
g.Dispose();
image.Dispose();
圖像格式和質量
當保存圖像時,你可以選擇想要使用的圖像格式:
- JPEG:提供最好的色彩支持和繪圖,但可能會失去細節信息的壓縮,並會令文本看起來失真。
- GIF:是包含文本的圖形的更好選項,但對色彩的支持不好。它只使用 256 色固定的調色板。
- PNG:結合了 JPEG 和 GIF 兩種格式的優點,但不能直接用在網頁,而需要將其包裝在 <img> 標簽中。
質量不僅僅由圖像格式決定,還取決於你呈現原始位圖的方式。 GDI+ 允許你選擇外觀或是速度優化繪圖代碼。當選擇最佳外觀優化時,.NET 使用額外的呈現技術提高繪畫質量。例如,反鋸齒。
反鋸齒平滑了形狀和文本的鋸齒邊沿。其原理是在邊緣的邊界上添加陰影。為了使用平滑技術,可以設置 Graphics 對象的 SmoothingMode 屬性。你可以選擇 None(默認值)、HighSpeed、AntiAlias 和 HighQuality(和 AntiAlias 相似,但使用更慢的優化算法改進在液晶屏上的顯示效果)。
SmoothingMode 屬性是 Graphics 類成員的幾個有狀態的屬性之一。也就是說,你要在開始繪制之前設置其值,效果會應用到后面繪制過程中的所有文本和形狀,直到 Graphics 對象被釋放。
g.SmoothingMode = SmoothingMode.AntiAlias;
反鋸齒技術在顯示曲線時效果最明顯,將極大地提升橢圓、圓和弧線的顯示效果,但不會對直線、正方形、矩形有什么改進。
還可以對字體使用反鋸齒技術來柔滑文本的邊界。可以設置 Graphics.TextRenderingHint 屬性得到優化后的文本:
- SingleBitPerPixelGridFit:性能最快,質量最低。
- AntiAlias:進行平滑處理,質量比較好。
- AntiAliasGridFit:進行平滑處理和微調,質量較好但性能較慢。
- ClearTypeGridFit:液晶屏上顯示質量最好。
也可以使用 SystemDefault 值來使用用戶配置的字體平滑設置,這是默認設置,對於大多數計算機的默認系統設置,都啟用了文本反鋸齒。即使沒有設置這點,動態呈現的文本也通常以高質量繪制。然而,因為不一定能控制 Web 服務器的系統設置,所以如果需要在圖像上繪制文本,較好的做法是顯式地指定這些設置。
Graphics 類
Graphics 類提供了大量繪制特別形狀、圖像、文本的方法。
DrawArc() | 指定一個坐標、高度、寬度繪制橢圓的一段弧線 |
DrawBezier() DrawBeziers() | 繪制著名的貝塞爾曲線,需要由 4 個控制點定義 |
DrawClosedCurve() | 繪制一條曲線,並鏈接到端點封閉它 |
DrawCurve() | 繪制一條曲線,從技術上講,是基數樣條曲線 |
DrawEllipse() | 繪制一個由一對坐標,高,寬來定義的矩形限制范圍內的橢圓 |
DrawIcon() DrawIconUnstreched() | 繪制 Icon 對象表示的圖標,並且(可選的)可伸展以滿足一個給定矩形 |
DrawImage() | 繪制一個派生自 Image 的對象並延展以適應矩形區域。(如文件加載的 Bitmap 對象) |
DrawImageUnscaled() DrawImageUnscaledAndClipped() |
繪制一個派生自 Image 的對象,不放大並裁剪它以適應指定的矩形區域 |
DrawLine() DrawLines() | 繪制一條或多條線段,每條線段連接由坐標對指定的兩個點 |
DrawPath() | 繪制一個 GraphicsPath 對象,該對象可以代表曲線和形狀的一個組合 |
DrawPie() | 繪制一個扇形,由一個坐標對、寬度、高度、兩條射線所指定的橢圓定義 |
DrawPolygon() | 繪制由點的數組定義的多邊形 |
DrawRectangle() DrawRectangles() | 繪制一個或多個由起始點坐標、寬、高指定的矩形 |
DrawString() | 繪制給定字體的文本字符串 |
FillClosedCurve() | 繪制一條首尾連接的封閉曲線,並填充 |
FillEllipse() | 填充橢圓 |
FillPath() | 填充由一個 GraphicsPath 對象表示的形狀的內部 |
FillPie() | 填充扇形 |
FillPolygon() | 填充多邊形 |
FillRectangle() FillRectangles() | 填充一個或多個矩形 |
DrawXXX()方法繪制輪廓,FillXXX()方法填充區域。唯一例外的是 DrawString()方法(它使用指定的字體繪制填充的文本)以及 DrawIcon()和 DrawImage(),它們復制位圖圖像到繪制表面。
如果想通過將邊界用一種顏色而將填充用另一種顏色來繪制一個圖形,就需要組合一個繪制方法和一個填充方法:
g.FillRectangle(Brushes.White, 0, 0, 300, 50);
g.DrawRectangle(Pens.Green, 0, 0, 299, 49);
如果指定了不在繪圖區域內的坐標,並不會得到異常。但是,繪制在邊界之外的內容不會出現在最后的圖像上。
使用已學到的技巧,很容易創建一個用於繪制復雜 GDI+ 圖像的簡單網頁。下面這個例子使用 Graphics 類來繪制橢圓、文本消息、來自文件的圖像:
protected void Page_Load(object sender, EventArgs e)
{
Bitmap image = new Bitmap(450, 100);
Graphics g = Graphics.FromImage(image);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, 0, 0, 450, 100);
g.FillEllipse(Brushes.PaleGoldenrod, 120, 13, 300, 50);
g.DrawEllipse(Pens.Green, 120, 13, 299, 49);
Font font = new Font("Harrington", 20, FontStyle.Bold);
g.DrawString("Oranges are tasty!", font, Brushes.DarkOrange, 150, 20);
System.Drawing.Image orangeImage =
System.Drawing.Image.FromFile(Server.MapPath("oranges.gif"));
g.DrawImageUnscaled(orangeImage, 0, 0);
Response.ContentType = "image/jpeg";
image.Save(Response.OutputStream, ImageFormat.Jpeg);
g.Dispose();
image.Dispose();
}
使用 GraphicsPath
有兩個有趣的方法,DrawPath()和 FillPath(),它們用於 System.Drawing.Drawing2D 命名空間中的 GraphicsPath 類。這個類包裝了一系列連接線、曲線和文本。
GraphicsPath path = new GraphicsPath();
path.AddEllipse(0, 0, 100, 50);
path.AddRectangle(new Rectangle(100, 50, 100, 50));
g.DrawPath(Pens.Black, path);
g.FillPath(Brushes.Yellow, path);
GraphicsPath 方法
AddXXXX…() | 繪制一系列的各種基本形狀:橢圓,矩形,曲線等等 |
AddString() | 以給定字體繪制文本字符串 |
StartFigure() CloseFigure() | 前者定義一個封閉圖形的起始點,使用后者時,起始點將被一條額外的線條連接到終點 |
Transform() Warp() Widen() | 分別使用:矩陣變形、彎曲變形(矩形和平行四邊形定義)、膨脹變形 |
畫筆
當使用 Graphics 類的 DrawXxxx()方法時,圖形的邊界或者曲線是使用你提供的 Pen 對象繪制的。使用 System.Drawing.Pens 類的靜態屬性獲得一個標准畫筆,寬度都是 1 像素,只是顏色不同。例如下面:
Pen myPen = Pens.Black;
也可以創建一個自定義的 Pen 對象,配置下表列出的所有屬性即可:
DashPattern | 使用短划線和空格數組定義虛線樣式 |
DashStyle | 使用枚舉定義虛線樣式 |
LineJoin | 定義了一個圖形中連接的線段如何連接 |
PenType | 用於直線的填充類型。典型值是 SolidColor (純色),但也可使用漸變、位圖紋理或者當你創建畫筆時提供一個畫刷對象來創建一個陰影模式。 |
StartCap 、EndCap | 決定線段的起始點和終點如何呈現 |
Width | 當前畫筆畫的線的像素寬度 |
理解 LineCap 和 DashStyle 屬性最簡單的辦法是創建一個枚舉所有選項的測試頁面:
protected void Page_Load(object sender, EventArgs e)
{
int y = 60;
Bitmap image = new Bitmap(500, 400);
Graphics g = Graphics.FromImage(image);
g.FillRectangle(Brushes.White, 0, 0, 500, 400);
Pen myPen = new Pen(Color.Blue,10);
g.DrawString("LineCap Choices",
new Font("Tahoma", 15, FontStyle.Bold), Brushes.Blue, 0, 10);
foreach (LineCap cap in System.Enum.GetValues(typeof(LineCap)))
{
myPen.StartCap = cap;
myPen.EndCap = cap;
g.DrawLine(myPen, 20, y, 100, y);
g.DrawString(cap.ToString(), new Font("Tahoma", 8), Brushes.Black, 120, y - 10);
y += 30;
}
y = 60;
g.DrawString("DashStyle Choices",
new Font("Tahoma", 15, FontStyle.Bold), Brushes.Blue, 200, 10);
foreach (DashStyle dash in System.Enum.GetValues(typeof(DashStyle)))
{
myPen.DashStyle = dash;
g.DrawLine(myPen, 220, y, 300, y);
g.DrawString(dash.ToString(), new Font("Tahoma", 8), Brushes.Black, 320, y - 10);
y += 30;
}
Response.ContentType = "image/jpeg";
image.Save(Response.OutputStream, ImageFormat.Gif);
g.Dispose();
image.Dispose();
}
畫刷
畫刷用於填充線之間的空間。當繪制文本或者使用 Graphics 類的任何 FillXxxx()方法來繪制形狀的內部時,會用到畫刷。使用 Brushes 類的靜態屬性獲取一個預定義的實心畫刷:
Brush myBrush = Brushes.White;
也可以創建自定義畫刷。實心畫刷創建自類 SolidBrush;而其他類允許創建更復雜的畫刷:
- HatchBrush:有前景色、背景色和一個決定這些顏色如何組合的陰影樣式。通常,色彩通過使用條紋、網格或者點來點綴。但你仍可以選擇不常見的模式,如 brick(磚塊)、confetti(五彩)、weave(編織)、shingle(鵝卵石)。
- LinearGradientBrush:以一種漸變模式混合兩種顏色。你可以選擇任意兩種色彩(就像 hatch 畫刷那樣),然后選擇水平混合、垂直混合、對角線方向混合,還可以為漸變的兩邊指定起點。
- TextureBrush:給畫刷添加位圖。這個圖像在畫刷的着色區域被分成區塊,不管這個區域是文本還是簡單的矩形。
這里給出測試 LinearGradientBrush 所有樣式的繪制邏輯的示例代碼(其余的有興趣的童鞋可以自己測試):
protected void Page_Load(object sender, EventArgs e)
{
Bitmap image = new Bitmap(300, 300);
Graphics g = Graphics.FromImage(image);
g.FillRectangle(Brushes.White, 0, 0, 300, 300);
LinearGradientBrush myBrush;
int y = 20;
foreach (LinearGradientMode gradientStyle in
System.Enum.GetValues(typeof(LinearGradientMode)))
{
myBrush = new LinearGradientBrush(new Rectangle(20, y, 100, 60),
Color.Violet, Color.White, gradientStyle);
g.FillRectangle(myBrush, 20, y, 100, 60);
g.DrawString(gradientStyle.ToString(), new Font("Tahoma", 8),
Brushes.Black, 130, y + 20);
y += 70;
}
Response.ContentType = "image/jpeg";
image.Save(Response.OutputStream, ImageFormat.Jpeg);
g.Dispose();
image.Dispose();
}
提示:
你還可以利用這個技術創建使用畫刷的填充樣式進行繪制的畫筆。這使你可以繪制用漸變色彩和紋理填充的線。首先創建一個合適的畫刷,然后創建一個新的畫筆,其中某個被重載的畫筆的構造函數接受對一個畫刷的引用。