图形、GDI + 和图表(在网页上嵌入动态图形)

       使用 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>

image

      文件路径以 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>

image 

 

       把信息从一个页面传送到另一个页面是 GDI+ 另一个不错的应用,但是对查询字符串大小的限制意味着它能处理的数据量较有限。对于较大的数据量,可以采用 Session 传递,Session 能传递任何可序列化的数据,不过也需要顾及到服务器的开销。

posted on 2012-12-11 18:06  SkySoot  阅读(1985)  评论(0编辑  收藏  举报

导航