使用 Image.Save()方法將一個影像儲存到一個響應流時,會覆蓋所有 ASP.NET 要用到的控制元件。這有一個解決方案,可以使用 HTML 的 <img> 標籤或者 Image Web 控制元件來連結到一個生成動態影像的 .aspx 檔案。
建立 GDI+ 影像通常比提供一個靜態影像慢一個數量級,因此,使用 GDI+ 多次重複繪製圖形按鈕或其他元素絕不是一個好主意。(如果真要這麼做,要考慮快取或者儲存生成的影像檔案以提高效能。)
使用 PNG 格式
PNG 是一種通用格式,這種格式將 GIF 的無失真壓縮和 JPEG 的豐富色彩結合起來以支援高質量影像。
動態生成的 PNG 影像有一個問題,即不能使用 BitMap.Save()方法。Response.OutputStream 是一個線性流,你只能從頭到尾順序寫入資料。要建立一個 PNG 檔案,.NET 需要能夠在一個檔案裡來回前後的定位,需要一個可定位的流。
解決方案也很簡單,可以建立一個 System.IO.MemoryStream 物件(記憶體裡的一個緩衝區),將影像儲存到這個物件後,就能很容易的從 MemoryStream 複製資料到 Response.OutputStream 了。
Response.ContentType = "image/png";
MemoryStream mem = new MemoryStream();
image.Save(mem,ImageFormat.Png);
mem.WriteTo(Response.OutputStream);
g.Dispose();
image.Dispose();
傳遞資訊給動態影像
當使用這個技術在網頁裡動態生成圖形時,可以在網頁中向動態生成圖形的程式碼傳遞資訊。
下面的例子,使用這一技巧建立一個資料繫結列表,顯示一個給定目錄中每個點陣圖的縮圖。
這個頁面需要設計為兩部分:包含 GridView 的頁面(多次呼叫縮圖頁面來填充列表)和動態呈現一個單一縮圖的頁面(多次被呼叫)。
首先,設計建立縮圖的頁面,為了儘可能通用,不應該硬編碼任何關於要使用的目錄和縮圖大小的資訊,應從呼叫者處獲取這些資訊:
protected void Page_Load(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(Request.QueryString["X"]) ||
string.IsNullOrEmpty(Request.QueryString["Y"]) ||
string.IsNullOrEmpty(Request.QueryString["FilePath"]))
{
// There is missing data, so don't display anything.
// Or return an image with some static error text.
}
else
{
int x = Int32.Parse(Request.QueryString["X"]);
int y = Int32.Parse(Request.QueryString["Y"]);
string file = Server.UrlDecode((Request.QueryString["FilePath"]));
...
一旦獲得了這些基本資料,就可以建立 BitMap 和 Graphics 物件了。本例中,BitMap 大小應該與縮圖大小相對應:
Bitmap image = new Bitmap(x, y);
Graphics g = Graphics.FromImage(image);
Graphics 類會自動伸縮你的影像來適應這些寬窄,使用反鋸齒技術來建立一個高質量的縮圖:
Bitmap image = new Bitmap(x, y);
Graphics g = Graphics.FromImage(image);
System.Drawing.Image thumbnail = System.Drawing.Image.FromFile(file);
g.DrawImage(thumbnail, 0, 0, x, y);
image.Save(Response.OutputStream, ImageFormat.Jpeg);
g.Dispose();
image.Dispose();
}
}
接著,是在包含 GridView 的頁面中使用這個頁面。基本思路是使用者輸入目錄路徑並單擊“提交”按鈕,此時,你的程式碼可以使用 System.IO 類來做點事:
protected void btnShow_Click(object sender, EventArgs e)
{
DirectoryInfo dir = new DirectoryInfo(txtDir.Text);
gridThumbs.DataSource = dir.GetFiles();
gridThumbs.DataBind();
}
繫結了 FileInfo 陣列之後,如何顯示完全由 GridView 模板決定。本例中,需要顯示兩段資訊,即檔案的短名和對應的縮圖。短名只需要繫結到 FileInfo.Name 屬性即可。縮圖需要使用 <img> 標籤來呼叫動態圖片頁面,不過,構建正確的 URL 有一點點麻煩,因此將該工作交給網頁類中的 GetImageUrl()方法來完成。
protected string GetImageUrl(object path)
{
return "ThumbnailViewer.aspx?x=50&y=50&FilePath="
+ Server.UrlEncode((string)path);
}
<div>
Directory:<asp:TextBox runat="server" ID="txtDir"></asp:TextBox>
<br />
<br />
<asp:Button ID="btnShow" runat="server" OnClick="btnShow_Click" Text="Show Thumbnails" />
<br />
<br />
<asp:GridView ID="gridThumbs" runat="server" AutoGenerateColumns="False" Font-Names="Verdana"
Font-Size="X-Small" GridLines="None">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<img alt="" src='<%# GetImageUrl(Eval("FullName")) %>' />
<%
# Eval("Name")
%>
<hr />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
檔案路徑以 URL 形式編碼,這是因為檔名通常包含 URL 禁止的字元,比如空格。因此這一小小改變非常重要。
使用 GDI+ 的自定義控制元件
你可能已經想急於使用 GDI+ 來建立封裝的很好的自定義控制元件,遺憾的是,ASP.NET 並沒有為你在網頁裡嵌入 GDI+ 影像變得簡單。
如你所見,使用 GDI+ 需要建立一個獨立的網頁輸出影像,然後使用 <img> 標籤將這個頁面的內容嵌入進來。因此,你不能簡單的直接在一個網頁上拖放一個使用了 GDI+ 的自定義控制元件。
你可以做的是建立一個包裝了 <img> 標籤的自定義控制元件。這個控制元件提供一個方便的程式設計介面、包括屬性、方法和事件。不過,該控制元件實際上並不能生成影像,它可以從它的屬性獲取資料,構建 URL 的查詢字串部分,然後將自己呈現為一個 <img> 標籤,該標籤指向真正完成影像生成的頁面。
這個自定義控制元件提供了更高階別的包裝,這個包裝抽象了傳遞資訊到你的 GDI+ 頁面的過程!
也可以考慮使用 HTTP 處理程式來生成影像。你的影像生成器可以具有一個自定義的副檔名。
1. 自定義控制元件類
與任何自定義控制元件類一樣,可以放在網站的 App_Code 資料夾中,或是放在一個單獨的類庫專案中。
本例中的自定義控制元件類從 Control 類派生,而不從 WebControl 類派生。因為它不能支援富樣式屬性集,因為它僅僅呈現動態圖形,而不是一個 HTML 標籤。
public class GradientLabel : Control
{
public string Text
{
get { return (string)ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
public int TextSize
{
get { return (int)ViewState["TextSize"]; }
set { ViewState["TextSize"] = value; }
}
/// <summary>
/// 漸變的起始顏色
/// </summary>
public Color GradientColorStart
{
get { return (Color)ViewState["GradientColorStart"]; }
set { ViewState["GradientColorStart"] = value; }
}
/// <summary>
/// 漸變的結束顏色
/// </summary>
public Color GradientColorEnd
{
get { return (Color)ViewState["GradientColorEnd"]; }
set { ViewState["GradientColorEnd"] = value; }
}
public Color TextColor
{
get { return (Color)ViewState["TextColor"]; }
set { ViewState["TextColor"] = value; }
}
// 建構函式將屬性設定成一些合理的預設值
public GradientLabel()
{
Text = "";
TextColor = Color.White;
GradientColorStart = Color.Blue;
GradientColorEnd = Color.DarkBlue;
TextSize = 14;
}
protected override void Render(HtmlTextWriter writer)
{
HttpContext context = HttpContext.Current;
writer.Write(string.Format(@"<img src='GradientLabel.aspx?Text={0}&TextSize={1}
&TextColor={2}&GradientColorStart={3}&GradientColorEnd={4}' />",
context.Server.UrlEncode(Text), TextSize.ToString(), TextColor.ToArgb(),
GradientColorStart.ToArgb(), GradientColorEnd.ToArgb()));
}
}
2. GDI+ 影像呈現頁面
這個呈現頁面有一個有趣的挑戰:即文字和字型大小是動態提供的!因此,不可能使用一個固定大小的點陣圖,太小會截斷文字,太大會浪費過多的記憶體且傳遞頁面太費時。
解決方案是建立所需的 Font 物件,然後呼叫 Graphics.MeasureString 引數來判斷要顯示你的文字需要多少畫素。唯一需要注意的是,要避免點陣圖過大。為避免這個風險,呈現程式碼強制 800 畫素高和寬的限制。(還可使用 DrawString()方法的另一個版本,即接受一個放置文字的矩形,有多行空間的話,這個方法會自動包裝文字,允許顯示超過幾行的大量文字。)
protected void Page_Load(object sender, EventArgs e)
{
string text = Server.UrlDecode(Request.QueryString["Text"]);
int textSize = int.Parse(Request.QueryString["TextSize"]);
Color textColor = Color.FromArgb(int.Parse(Request.QueryString["TextColor"]));
Color gradientColorStart = Color.FromArgb(int.Parse(Request.QueryString["GradientColorStart"]));
Color gradientColorEnd = Color.FromArgb(int.Parse(Request.QueryString["GradientColorEnd"]));
// Define the font.
Font font = new Font("Tahoma", textSize, FontStyle.Bold);
// Use a test image to measure the text.
Bitmap image = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(image);
SizeF size = g.MeasureString(text, font);
g.Dispose();
image.Dispose();
// Using these measurements, try to choose a reasonable bitmap size.
// If the text is large, cap the size at some maximum to
// prevent causing a serious server slowdown.
int width = (int)Math.Min(size.Width + 20, 800);
int height = (int)Math.Min(size.Height + 20, 800);
image = new Bitmap(width, height);
g = Graphics.FromImage(image);
// 使用線性漸變封裝 System.Drawing.Brush
LinearGradientBrush brush = new LinearGradientBrush(
new Rectangle(new Point(0, 0), image.Size), gradientColorStart,
gradientColorEnd, LinearGradientMode.ForwardDiagonal);
// Draw the gradient background.
g.FillRectangle(brush, 0, 0, width, height);
// Draw the label text.
g.DrawString(text, font, new SolidBrush(textColor), 10, 10);
// Render the image to the output stream.
image.Save(Response.OutputStream,ImageFormat.Jpeg);
g.Dispose();
image.Dispose();
}
除了文字的大小外,每邊都增加了 20 畫素,這允許每一位度都增加了 10 個畫素。
3. 測試效果
<cc1:GradientLabel ID="GradientLabel1" runat="server" Text="Test String"
GradientColorStart="MediumSpringGreen" GradientColorEnd="RoyalBlue">
</cc1:GradientLabel>
把資訊從一個頁面傳送到另一個頁面是 GDI+ 另一個不錯的應用,但是對查詢字串大小的限制意味著它能處理的資料量較有限。對於較大的資料量,可以採用 Session 傳遞,Session 能傳遞任何可序列化的資料,不過也需要顧及到伺服器的開銷。