C#开发训练营

第23课:图形图像(1)

在.NET Framework类库中的System.Drawing命名空间及其子命名空间中,定义了大量的图形处理资源,本课将讨论常用类型的应用,并开始封装以毫米为单位的绘制类型——tImage类,后续课程还会逐渐扩展功能,包括基本图形和文本的绘制、扩展图形的绘制、任意角度的旋转等。

常用资源

本节介绍图形绘制的基本类型。

Bitmap类

Bitmap类用于处理位图对象,其中的Width和Height属性分别表示图像的宽度和高度,单位是像素。

图形处理中,DPI(dots per inch)是一个关于图像质量的参数,DPI值越高,图像就会越清晰,在Bitmap类中,可以使用SetResolution(xDpi, yDpi)方法设置图像的水平方向DPI和垂直方向DPI。

此外,Bitmap类中还可以直接操作像素,如使用SetPixel(x,y,color)方法绘制像素的颜色,其中,x和y指定像素坐标,color定义为Color对象,指定像素的颜色。相应的,需要获取一个像素的颜色时,可以使用GetPixel(x,y)方法。

需要将某一颜色设置为透明时,可以使用MakeTransparent(color)方法,此方法将图像中color参数指定的颜色转换为透明效果。

Graphics类

Graphics类封装了大量的绘制、交换等操作,应用时,首先需要将Graphics和Bitmap对象关联,如下面的代码。

C#
Bitmap bmp = new Bitmap(300,200);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
g.DrawLine(Pens.Black, 10,10,100,10);

代码的功能是创建一个宽度为300像素,高度为200像素的位置,然后将其与Graphics关联并将整个位置设置为白色,最后绘制一条黑色的线段。

稍后,封装tImage类时还会讨论更多的图形绘制方法和旋转等操作。

Color结构

Color结构可以处理RGBA颜色,包括红色值、绿色值、蓝色值和不透明度,这些值分别由0~255表示,其中,不透明度为0时表示完全透明,为255时表示完全不透明。

构建Color结构时,可以使用FromArgb()静态方法,它包括一些重载版本,如:

  • FromArgb(int)方法使用一个整数表示颜色值,可以使用8位十六进制数表示,每两位一组,分别表示不透明度、红色、绿色和蓝色,如0xFFFF0000表示完全不透明的红色。
  • FromArgb(int,int,int)方法用于创建不透明的RGB颜色,参数分别指定红色、绿色和蓝色,取值范围为0到255。
  • FromArgb(int,int,int,int)方法的参数依次指定红色、绿色、蓝色和不透明色,取值范围为0到255。
  • FromArgb(int,Color)方法将新的不透明度数据(参数一)添加到已存在的颜色(参数二)中。

KnownColor枚举中定义了一些颜色命名,可以通过这些颜色枚举值构建Color结构,Color.FromKnownColor(KnownColor.Red)可以创建红色结构;此外,也可以直接使用文本形式的颜色名称创建颜色,如Color.FromName("Red")同样可以创建红色结构。

读取颜色中的信息时,可以使用Color结构中的如下成员:

  • ToArgb()方法,返回颜色的整数值(int类型)。
  • ToKnownColor()方法,返回颜色的KnownColor枚举值。
  • A、R、G、B定义为只读属性,分别表示颜色中的不透明度、红色、绿色和蓝色值,返回类型为byte(0~255)。

如果需要在方法的参数中使用默认值,直接使用Color结构是不行的,此时,可以通过int类型来传递颜色数据,如:

C#
void SetBackgroundColor(int color = 0xFFFFFFFF) {...}

请注意,如果表示颜色值的数值过大,需要强制转换为int类型,并使用unchecked()语法结构,如unchecked((int) 0xFFFFFFFF)。

此外,对于KnownColor枚举中定义的命名颜色,在Color结构中也都定义了相应的属性,如Color.Red属性可以直接获取红色结构数据。

Brush类

Brush类定义了格式刷的基类,在绘制填充图形或文本时,如果使用纯色的格式刷,可以使用两种基本方法,如果是KnownColor枚举中定义的命名颜色,可以使用Brushes类中的定义的相关属性获取格式刷对象,如Brushes.Red就是红色的格式刷;另一种方法是创建SolidBrush对象,并在构建函数中指定颜色,如new SolidBrush(Color.FromArgb(127,127,127))就创建了一个灰色的格式刷。

此外,System.Drawing.Drawing2D命名空间中还定义了一些常用的格式刷类型,如:

  • HatchBrush类,定义指定样式和颜色的填充效果,构造函数中,参数一使用HatchStyle枚举中指定的填充图案类型,参数二指定颜色。
  • LinearGradientBrush类定义线性渐变效果的格式刷,可以通过两点、矩形与方向来定义渐变效果。
  • PathGradientBrush类定义路径渐变效果,可以通过GraphicsPath对象或一系列的点坐标指定渐变路径。

接下来了解这些格式刷的基本应用方法,首先是HatchBrush类,下面的代码创建了一个黑白格子填充的矩形。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Bitmap bmp = new Bitmap(200, 150);
        Graphics g = Graphics.FromImage(bmp);
        g.Clear(Color.White);
        //
        HatchBrush b =
            new HatchBrush(HatchStyle.LargeCheckerBoard,
            Color.White);
        g.FillRectangle(b, 0, 0, 200, 150);
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        bmp.Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

代码生成效果如图1。

图1

本例,还需要注意图片在页面中的显示方式,再单独看一下这些代码。

C#
Response.ContentType = "image/png";
Response.AppendHeader("Content-Disposition", "filename=img1.png;");
bmp.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Png);

代码中,通过Response.ContentType属性设置图片类型,也就是图片的MIME类型,这里设置为PNG图片格式。

通过Response.AppendHeader()方法向页面添加了报文头数据,这里指定发送到客户端的文件为img1.png。

最后,通过Bitmap对象的Save()方法发送位图数据,请注意这里的保存路径是当前页面中的输出流(OutputStream);同时,设置图片格式为PNG。

LinearGradientBrush类定义了线性渐变格式刷,下面的代码使用两个点定义渐变方向。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Bitmap bmp = new Bitmap(200, 150);
        Graphics g = Graphics.FromImage(bmp);
        g.Clear(Color.White);
        //
        LinearGradientBrush b = new LinearGradientBrush(
            new Point(0, 0), new Point(200, 150),
            Color.White, Color.FromArgb(unchecked((int)0xFF333333)));
        g.FillRectangle(b, 0, 0, 200, 150);
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img98.png;");
        bmp.Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

代码会绘制一个从左上角到右下角渐变的矩形,颜色从白色到深一些的灰色,绘制效果如图2。

图2

下面的代码演示了PathGradientBrush类的应用。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Bitmap bmp = new Bitmap(200, 150);
        Graphics g = Graphics.FromImage(bmp);
        g.Clear(Color.White);
        //
        PathGradientBrush b = new PathGradientBrush(new Point[] {
            new Point(0,0),new Point(100,150),new Point(200,0) });
        g.FillRectangle(b, 0, 0, 200, 150);

        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        bmp.Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

绘制效果如图3。

图3

Pen类

Pen类定义了画笔对象,在绘制线条类图形时使用,如线段、矩形、椭圆等;Pen类中基本的属性包括:

  • Brush属性,指定填充线条的Brush对象。
  • Color属性,指定线条的颜色。
  • Width属性,指定线条的宽度,单位是像素。
  • DashStyle属性,虚线样式。定义为DashStyle枚举类型,默认为Solid值,即绘制实线线条。

下面的代码演示了Pen类的应用。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Bitmap bmp = new Bitmap(400, 300);
        Graphics g = Graphics.FromImage(bmp);
        g.Clear(Color.White);
        //
        LinearGradientBrush b = new LinearGradientBrush(
            new Point(0, 0), new Point(0, 30),
            Color.White, Color.Black);
        Pen p = new Pen(b, 30);
        g.DrawRectangle(p, 10, 10, 390, 90);
        //
        p = new Pen(Color.Black, 10);
        p.DashStyle = DashStyle.Dash;
        g.DrawLine(p, 10, 200, 390, 200);
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        bmp.Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

代码绘制效果如图4。

图4

此外,Pens类中还定义了一系列命名颜色的Pen对象,它们的线宽都是1像素,线条默认为实线(Solid)。

Font类

Font类定义了字体信息,创建Font对象时,可以使用多个版本的构造函数,主要的信息包括字体名称、尺寸、样式及应用的单位等。

如下面的代码就创建了一个Arial字体,尺寸为30像素,并有加粗和斜体样式。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Bitmap bmp = new Bitmap(400, 300);
        Graphics g = Graphics.FromImage(bmp);
        g.Clear(Color.White);
        //
        Font f = new Font("Arial", 30, FontStyle.Bold | FontStyle.Italic);
        g.DrawString("HELLO", f, Brushes.Black, 10, 10);
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        bmp.Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

绘制效果如图5。

图5

使用Font对象时,还需要注意图形单位(GraphicsUnit),默认情况下使用的是像素(Pixel),在tImage类中,我们使用的单位是毫米,在指定字体时就可以将字体的单位设置为毫米,如下面的代码就创建了尺寸为5毫米的字体。

C#
Font f = new Font("Arial", 5, GraphicsUnit.Millimeter);

接下来,在tImage类的封装过程中会看到以上资源的更多应用。

tImage类基本定义

tImage类的主要功能是以毫米为单位进行图形绘制,并扩展了一些图形绘制和相关操作,如以中心对齐方式绘制正方形、圆形、等边三角形,以及图像任意角度的旋转等。

tImage类中会考虑图形的DPI设置;默认情况下,Bitmap对象的DPI是96,如果需要质量高一些的图像,需要将DPI设置到300以上,所以,tImage类中的默认DPI就是设置为300。tImage类封装文件位于/app_code/common/img/tImage.cs,下面是tImage类的基本定义。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

public class tImage :IDisposable
{
    public tImage(float width, float height, float dpi = 300F)
    {
        Dpi = dpi;
        dpmm = dpi / 25.4F;
        myBmp = new Bitmap((int)GetPx(width), (int)GetPx(height));
        myBmp.SetResolution(dpi, dpi);
        myG = Graphics.FromImage(myBmp);
        // 默认绘制数据
        DefPen = Pen0_3;
        DefBrush = Brushes.Black;
        DefFont = GetFont("Arial", 5F);
    }
    //
    public void Dispose()
    {
        myG = null;
        myBmp = null;
        GC.SuppressFinalize(this);
    }
    //
    protected Bitmap myBmp = null;
    protected Graphics myG = null;
    // 每英寸点数
    public float Dpi { get; private set; }
    // 每毫米点数
    private float dpmm;
    // 毫米单位宽度和长度
    public float GetWidth() { return GetMm(myBmp.Width); }
    public float GetHeight() { return GetMm(myBmp.Height); }
    // 像素尺寸
    public int GetPixelWidth() { return myBmp.Width; }
    public int GetPixelHeight() { return myBmp.Height; }
    // 毫米转像素pixel
    protected float GetPx(float mm)
    {
        return mm * dpmm;
    }
    // 像素转毫米
    protected float GetMm(float pixel)
    {
        return pixel / dpmm;
    }
    // Point转换
    protected PointF GetPx(PointF p)
    {
        return new PointF(GetPx(p.X), GetPx(p.Y));
    }
    protected PointF GetMm(PointF p)
    {
        return new PointF(GetMm(p.X), GetMm(p.Y));
    }
    // PointF数组转换
    protected PointF[] GetPx(PointF[] pts)
    {
        PointF[] result = new PointF[pts.Length];
        for (int i = 0; i < pts.Length; i++)
        {
            result[i] = GetPx(pts[i]);
        }
        return result;
    }
    protected PointF[] GetMm(PointF[] pts)
    {
        PointF[] result = new PointF[pts.Length];
        for (int i = 0; i < pts.Length; i++)
        {
            result[i] = GetMm(pts[i]);
        }
        return result;
    }
    //
    public void Clear(Color cl)
    {
        myG.Clear(cl);
    }
    public void Clear()
    {
        myG.Clear(Color.Transparent);
    }
    // 其它代码...
}

构造函数中需要设置图像的尺寸和DPI,这里,尺寸的单位使用毫米,DPI默认为300,使用此数据时,可以使用Dpi只读属性读取。dpmm字段数据为当前DPI下的每毫米点数(像素),用于像素和毫米单位之间的转换。

myBmp对象定义Bitmap类型,用于在内部处理图像;myG对象定义为Graphics类型,用于图形绘制等操作。

GetWidth()和GetHeight()方法以毫米为单位返回图像的尺寸;获取图片像素尺寸的宽度和高度时分别使用GetPixelWidth()和GetPixelHeight()方法。

GetPx()方法的功能是将毫米单位转换为像素,参数可以是单个值,也可以是PointF数组。

GetMm()方法的功能是将像素转换为毫米值,参数同样可以是单个数据或PointF数组。

Clear()方法用于清理图像,如果设置颜色,会将图像设置为此颜色,如果不设置颜色,则保持图像透明。

此外,在构造函数中还设置了一些默认的数据,这些辅助资源及相关操作定义如下。

C#
// 常用Pen对象,黑色,线宽0.1-1mm,1.5mm和2mm
public Pen Pen0_1 { get { return new Pen(Color.Black, GetPx(0.1F)); } }
public Pen Pen0_2 { get { return new Pen(Color.Black, GetPx(0.2F)); } }
public Pen Pen0_3 { get { return new Pen(Color.Black, GetPx(0.3F)); } }
public Pen Pen0_4 { get { return new Pen(Color.Black, GetPx(0.4F)); } }
public Pen Pen0_5 { get { return new Pen(Color.Black, GetPx(0.5F)); } }
public Pen Pen0_6 { get { return new Pen(Color.Black, GetPx(0.6F)); } }
public Pen Pen0_7 { get { return new Pen(Color.Black, GetPx(0.7F)); } }
public Pen Pen0_8 { get { return new Pen(Color.Black, GetPx(0.8F)); } }
public Pen Pen0_9 { get { return new Pen(Color.Black, GetPx(0.9F)); } }
public Pen Pen1_0 { get { return new Pen(Color.Black, GetPx(1F)); } }
public Pen Pen1_5 { get { return new Pen(Color.Black, GetPx(1.5F)); } }
public Pen Pen2_0 { get { return new Pen(Color.Black, GetPx(2F)); } }
// 默认操作对象
protected Pen DefPen = null;
protected Brush DefBrush = null;
protected Font DefFont = null;
//
public void SetPen(Color cl, float width)
{
    DefPen = new Pen(cl, GetPx(width));
}
public Pen GetPen(Color cl, float width)
{
    return new Pen(cl, GetPx(width));
}
public Pen GetPen()
{
    return DefPen;
}
//
public void SetBrush(Brush b)
{
    DefBrush = b;
}
//
public Font GetFont(string familyName, float size)
{
    return new Font(familyName, size, GraphicsUnit.Millimeter);
}
//
public Font GetFont(string familyName, float size, FontStyle fontStyle)
{
    return new Font(familyName, size, fontStyle, GraphicsUnit.Millimeter);
}

首先定义了一系列的Pen对象,颜色为黑色,宽度分别是0.1到1mm,以及1.5mm和2mm。

DefPen对象保存上次使用的Pen对象,默认为0.3毫米黑色。DefBrush对象保存上次使用的Brush对象,默认为黑色格式刷。DefFont对象保存上次使用的字体,默认为Arial字体,尺寸为5毫米。

SetPen()方法用于设置当前使用的Pen对象,参数包括颜色和宽度;设置的数据会保存在DefPen对象中。请注意,这里使用的宽度单位是毫米,方法中会使用GetPx()方法进行转换。

GetPen()方法用于创建新的Pen对象或返回DefPen。通过参数设置颜色和宽度可以创建当前图像适用的Pen对象;如果不设置参数,则返回DefPen对象。

SetBrush()方法用于设置默认的格式刷对象,数据保存到DefBrush对象。

GetFont()方法会根据字体名称、尺寸、风格信息创建当前对象使用的字体系对象。

获取tImage绘制的位图对象时,可以使用tImage.GetBitmap()方法,定义如下。

C#
public Bitmap GetBitmap(bool clone = false)
{
    if (clone) return (Bitmap)myBmp.Clone();
    else return myBmp;
}

方法中定义了一个参数,指定是否返回位图对象的副本,默认为false,会直接返回myBmp对象的引用。

接下来讨论具体的图形绘制。

绘制线条

线条是基本的图形,在tImage类中使用DrawLine()方法绘制,如下面的代码。

C#
// 线条
public void DrawLine(Pen p, float x1, float y1, float x2, float y2)
{
    myG.DrawLine(p, GetPx(x1), GetPx(y1), GetPx(x2), GetPx(y2));
    DefPen = p;
}
public void DrawLine(float x1, float y1, float x2, float y2)
{
    DrawLine(DefPen, x1, y1, x2, y2);
}
// 多线条绘制
public void DrawLines(Pen p, params PointF[] pts)
{
    myG.DrawLines(p, GetPx(pts));
    DefPen = p;
}
public void DrawLines(params PointF[] pts)
{
    DrawLines(DefPen,pts);
}

DrawLine()方法中,需要指定线条开始和结束的坐标位置,如果不指定Pen对象,则使用DefPen中的信息;指定Pen对象时,还会将其信息保存到DefPen对象。

DrawLines()方法用于绘制连续的线段,参数中需要指定线段的端点坐标。

下面的代码演示了线条绘制方法的应用。

C#
using System;
using System.Drawing;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        tImage img = new tImage(200, 150, 300);
        img.Clear(Color.White);
        //
        img.SetPen(Color.Black, 1);
        img.DrawLine(10, 10, 190, 10);
        //
        img.DrawLines(new PointF[] {
            new PointF(10,50),new PointF(190,50),
            new PointF(190,80),new PointF(10,80)});
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        img.GetBitmap().Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

代码绘制效果如图6。

图6

默认情况下绘制的是实线,如果需要绘制点线或虚线,可以通过设置Pen对象的DashStyle属性实现,如下面的代码。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        tImage img = new tImage(200, 150, 300);
        img.Clear(Color.White);
        //
        Pen pen1 = img.GetPen(Color.Black, 2F);
        pen1.DashStyle = DashStyle.Dot;
        img.DrawLine(pen1, 10, 10, 190, 10);
        //
        pen1.DashStyle = DashStyle.Dash;
        img.DrawLine(pen1, 10, 40, 190, 40);
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        img.GetBitmap().Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

代码绘制效果如图7。

图7

此外,DashStyle枚举中定义的类型包括:

  • Solid,实线,这是线条的默认样式。
  • Dash,虚线。
  • Dot,点线。
  • DashDot,由短线和点循环的样式。
  • DashDotDot,由一短线和两点循环的样式。
  • Custom,自定义样式,需要使用Pen对象的DashPattern属性设置样式数据,定义为一个float数组,其中的数据定义长度比例,线条会按此模式循环绘制。

下面的代码演示了自定义样式的使用。

C#
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        tImage img = new tImage(200, 150, 300);
        img.Clear(Color.White);
        //
        Pen pen1 = img.GetPen(Color.Black, 2F);
        pen1.DashStyle = DashStyle.Custom;
        pen1.DashPattern = new float[] { 1f,2f,3f,4f,5f,1f};
        img.DrawLine(pen1, 10, 10, 190, 10);
        // 输出
        Response.ContentType = "image/png";
        Response.AppendHeader("Content-Disposition",
            "filename=img1.png;");
        img.GetBitmap().Save(Response.OutputStream,
            System.Drawing.Imaging.ImageFormat.Png);
    }
}

绘制效果如图8。

图8