C#图形图像处理(2)——格式刷、填充图形、坐标系变换

本文讨论格式刷、填充图形绘制、图形坐标系的变换等操作,并将常用操作封装到tCanvas类,绘制操作中会使用更直观的毫米作为单位。

创建tCanvas类

下面的代码,首先来看tCanvas类的基本定义。

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

namespace cfx.image
{
    public class tCanvas
    {
        protected Bitmap myBmp;
        protected Graphics myG;
        // 记录画线时的坐标
        protected float curX = 0F, curY = 0F;
        //
        protected float dpmm;
        //
        public tCanvas(float width, float height, float dpi = 300F)
        {
            // 每毫米点数
            dpmm = dpi / 25.4F;
            // 初始化图像
            myBmp = new Bitmap((int)(width * dpmm), (int)(height * dpmm));
            myBmp.SetResolution(dpi, dpi);
            //
            myG = Graphics.FromImage(myBmp);
            // 以毫米为单位
            myG.PageUnit = GraphicsUnit.Millimeter;
            // 高质量
            myG.InterpolationMode = InterpolationMode.High;
            myG.SmoothingMode = SmoothingMode.HighQuality;
        }

        // 
        public static tCanvas Create(float width, float height, float dpi = 300F)
        {
            return new tCanvas(width, height, dpi);
        }

        // 清理
        public tCanvas Clear()
        {
            myG.Clear(Color.Transparent);
            return this;
        }
        public tCanvas Clear(Color cl)
        {
            myG.Clear(cl);
            return this;
        }

        // 保存图片文件
        public tCanvas Save(string filename)
        {
            myBmp.Save(filename, tImage.GetImageFormat(filename));
            return this;
        }
    }
}

tCanvas类中,首先定义了几个字段,其中,myBmp为位图对象(Bitmap类型),myG为图形绘制对象(Graphics类型);curX和curY用于记录线条绘制时的当前坐标,定义为float类型;dpmm定义为每毫米多少像素的分辨率,定义为float类型。

构造函数和Create()静态方法用于创建tCanvas实例,参数分别是图像的宽度、高度和分辨率,其中宽度和高度使用毫米为单位,分辨默认使用300DPI,即每英寸300点。构造函数中,对myBmp、myG对象进行了初始化,设置了myG对象在绘制时使用毫米为单位,并设置图像在缩放、旋转、抗锯齿等方面使用高质量图像模式。

Clear()方法用于清理画布,不指定背景色时默认使用透明色(Transparent)。方法会返回当前实例。

Save()方法会将myBmp图像保存到图片文件,方法中使用了tImage.GetImageFormat()方法自动判断文件格式。方法同样返回当前实例。

下面讨论格式刷和填充图形的绘制。

格式刷和绘制填充矩形、椭圆、圆形

下面的代码(cfx/image/tCanvas.cs),封装了常用格式刷对象创建和填充图形绘制方法。

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

namespace cfx.image
{
    public class tCanvas
    {
        // 其它代码
        // 创建格式颜色刷对象
        public static Brush GetBrush(int red, int green, int blue)
        {
            return new SolidBrush(Color.FromArgb(red, green, blue));
        }
        // 根据图片文件创建纹理格式刷
        public static Brush GetBrush(string filename)
        {
            return new TextureBrush(Image.FromFile(filename));
        }
        // 根据图像创建纹理格式刷
        public static Brush GetBrush(Image img)
        {
            return new TextureBrush(img);
        }
        // 创建线性格式刷
        public static Brush GetBrush(int width, int height,
            Color c1, Color c2, float angle,WrapMode wrapMode = WrapMode.Tile)
        {
            return new LinearGradientBrush(
                new Rectangle(0,0, width, height), c1, c2, angle)
            {
                WrapMode = wrapMode
            };
        }
        //
        public static Brush GetBrush(int width, int height,
            Color c1, Color c2, 
            LinearGradientMode mode= LinearGradientMode.Horizontal, 
            WrapMode wrapMode = WrapMode.Tile)
        {
            return new LinearGradientBrush(
                new Rectangle(0,0, width, height), c1, c2, mode)
            {
                WrapMode = wrapMode
            };
        }
        //
        // 填充矩形
        public tCanvas FillRectangle(Brush b, float x, float y,
            float width, float height)
        {
            myG.FillRectangle(b, x, y, width, height);
            return this;
        }

        // 填充椭圆
        public tCanvas FillEllipse(Brush b, float x, float y,
            float width, float height)
        {
            myG.FillEllipse(b, x, y, width, height);
            return this;
        }

        // 填充圆形,中心坐标和半径
        public tCanvas FillCircle(Brush b, float cx, float cy, float r)
        {
            float d = r * 2;
            return FillEllipse(b, cx - r, cy - r, d, d);
        }
    }
}

代码中,首先定义了一系列GetBrush()方法,用于创建常用的格式刷对象,包括以下重载版本:

  • 通过RGB颜色,参数分别是红(red)、绿(green)、蓝(blue)的值,取值范围为0到255。方法会返回SolidBrush对象。
  • 使用图片文件路径或Image对象为参数时,可以使用图像作为填充纹理,返回类型为TextureBrush对象。
  • 返回线性渐变格式刷对象(LinearGradientBrush类型)时,首先需要指定矩形区域的宽度和高度,然后是两个渐变颜色,接下来,可以指定渐变的角度或线性转变模式(LinearGradientMode),最后使用WrapMode枚举指定平铺方式。指定渐变方向的角度时应注意,水平向右方向为0度,顺时针方向为正角度。

FillRectangle()方法用于绘制填充矩形,参数包括格式刷、矩形左上角x和y坐标、宽度(width)和高度(height)。

FillEllipse()方法用于绘制填充椭圆,其参数为格式刷,以及椭圆外切矩形的左上角x和y坐标、宽度(width)和高度(height)。

FillCircle()方法用于绘制填充圆形,其参数为格式刷,圆点x和y坐标、半径。

下面的代码,在Program.cs测试填充图形的绘制。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            tCanvas.Create(150,100).Clear(Color.White)
                .FillCircle(Brushes.Blue, 50, 50, 25)
                .Save(@"d:\99.png");
        }
    }
}

代码中会创建150*100毫米的画布对象,分辨率使用默认的300DPI;然后调用Clear()方法将背景色设置为灰色,并在(50,50)毫米处绘制了一个半径为25毫米的蓝色圆形;最后,将绘制的图片保存到d:\99.png文件。绘制效果如下图所示。

绘制填充圆形

下面的代码会创建一个渐变色填充的矩形。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            tCanvas.Create(150, 100).Clear(Color.Gray)
                .FillRectangle(tCanvas.GetBrush(100, 50, Color.Yellow, Color.Red),
                0, 0, 100, 50)
                .Save(@"d:\99.png");
        }
    }
}

代码中使用了默认的水平渐变模式,绘制图形如下。

绘制渐变色矩形

绘制填充多边形和封闭图形

下面的代码(cfx/image/tCanvas.cs)封装绘制填充多边形和封闭图形的方法。

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

namespace cfx.image
{
    public class tCanvas
    {
        // 其它代码
        // 填充多边形
        public tCanvas FillPolygon(Brush b, PointF[] pts)
        {
            myG.FillPolygon(b, pts);
            return this;
        }

        // 填充封闭图形
        public tCanvas FillClosedCurve(Brush b,
            PointF[] pts,
            float tension = 0F,
            FillMode fillMode = FillMode.Alternate)
        {
            myG.FillClosedCurve(b, pts, fillMode,tension);
            return this;
        }
    }
}

代码中,FillPolygon()方法用于绘制填充多边形,参数一为格式刷,参数二为包含多边形顶点坐标的PointF数组。

FillClosedCurve()方法绘制封闭图形,参数一为格式刷;参数二为顶点坐标;参数三为边的曲度(张力),其值为0时绘制为直线线段,大于0时绘制为曲线;参数四指定填充模式。

下面的代码,在Program.cs文件中测试填充多边形的绘制。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            PointF[] pts = new PointF[]
            {
                new PointF(10,10),
                new PointF(60,60),
                new PointF(100,30),
            };
            //
            tCanvas.Create(150, 100).Clear(Color.Gray)
                .FillPolygon(Brushes.Blue,pts)
                .Save(@"d:\99.png");
        }
    }
}

代码通过三个点绘制了填充三角形,绘制效果如下图所示。

绘制封闭多边形

下面的代码绘制了边为曲线的封闭图形。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            PointF[] pts = new PointF[]
            {
                new PointF(10,10),
                new PointF(60,60),
                new PointF(100,30),
            };
            //
            tCanvas.Create(150, 100).Clear(Color.Gray)
                .FillClosedCurve(Brushes.Blue,pts,0.5F)
                .Save(@"d:\99.png");
        }
    }
}

绘制效果如下图所示。

绘制封闭图形

绘制填充扇形与饼图

下面的代码,在tImage类中添加绘制填充扇形的方法。

C#
        // 填充扇形
        public tCanvas FillPie(Brush b, float cx, float cy, float r,
            float startAngle, float sweepAngle)
        {
            float d = r * 2;
            myG.FillPie(b, cx - r, cy - r, d, d, startAngle, sweepAngle);
            return this;
        }

Graphics对象的FillPie()方法中,参数分别是格式刷对象、扇形所在圆(或椭圆)外切矩形左上角X和Y坐标、矩形宽度和高度,开始角度、旋转的角度(即扇形的角度)。这里,tImage.FillPie()方法绘制的是位于圆形的扇形,参数分别是格式刷、圆心坐标、半径、开始角度和旋转角度。下面的代码,在Program.cs文件中测试填充扇形的绘制。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            tCanvas.Create(150, 100).Clear(Color.Gray)
                .FillPie(Brushes.Blue, 75, 50, 50, 0, 45)
                .Save(@"d:\99.png");
        }
    }
}

代码绘制效果如下图所示。

绘制填充扇形

请注意,绘制扇形时,角度0同样是水平向右的方向,顺时针为正角度。

对于扇形的绘制,还可以扩展一些操作,如下面的代码。

C#
        public tCanvas FillPie(Brush b,float x,float y,
            float width,float height,
            float startAngle,float sweepAngle)
        {
            myG.FillPie(b, x, y, width, height, startAngle, sweepAngle);
            return this;
        }

        // 绘制饼图
        public static Brush[] myColorBrush = new Brush[]
        {
            Brushes.Orange,Brushes.AliceBlue,Brushes.OrangeRed,
            Brushes.GreenYellow,Brushes.LightBlue,Brushes.LightGray,
            Brushes.Brown,Brushes.LightSteelBlue,Brushes.Chocolate
        };
        //
        public tCanvas FillPie(float x, float y,
            float width, float height, float[] data)
        {
            // 求数据之和
            float sum = 0F;
            for (int i = 0; i < data.Length; i++)
                sum += data[i];
            // 根据数据占比计算旋转角度并绘制扇形
            float curAngle = 0F, sweepAngle;
            Random rnd = new Random();
            for (int i = 0; i < data.Length; i++)
            {
                sweepAngle = data[i] / sum * 360;
                Brush b = (i < myColorBrush.Length) ?
                    myColorBrush[i] : 
                    GetBrush(rnd.Next(1,255), rnd.Next(1, 255), rnd.Next(1, 255));
                myG.FillPie(b, x, y, width, height, curAngle, sweepAngle);
                curAngle += sweepAngle;
            }
            //
            return this;
        }

代码中添加了两个FillPie()方法的重载版本,其中,第一个版本参数与Graphics对象的FillPie()方法参数相对应;第二个版本可以通过一组数据绘制饼图,其中使用了myColorBrush静态字段作为默认的颜色格式刷,请注意,绘制扇形时,如果数据索引小于myColorBrush元素数量则使用myColorBrush中定义的颜色格式刷,否则使用随机颜色格式刷,这里,随机颜色中的R、G、B取值在1到254之间,不会出现白色和黑色。工作中,如果需要显示饼图的图例,可以在方法中使用输出参数保存使用过的颜色格式刷列表。

下面的代码,在Program.cs文件中测试饼图的绘制。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            float[] data = { 51, 30, 15, 30, 26 };
            tCanvas.Create(150, 100).Clear(Color.Gray)
                .FillPie(0,0,100,100,data)
                .Save(@"d:\99.png");
        }
    }
}

绘制效果如下图所示。

绘制饼图

变换(Transform)

为了方便操作,还可以在tCanvas类中创建一些常用图形的绘制方法,如下面的代码就是绘制填充正方形的方法。

C#
        // 填充正方形
        public tCanvas FillSquare(Brush b, float cx, float cy, float side)
        {
            float side2 = side / 2;
            myG.FillRectangle(b, cx - side2, cy - side2, side, side);
            return this;
        }

FillSquqre()方法中,参数分别是格式刷、正方形中心的x和y坐标,以及正方形的边长。

接下来,在tImage类中封装一些坐标系变换相关的方法,如下面的代码。

C#
        // 旋转变换
        public tCanvas RotateTransform(float angle)
        {
            myG.RotateTransform(angle);
            return this;
        }

        // 原点平移
        public tCanvas TranslateTransform(float dx, float dy)
        {
            myG.TranslateTransform(dx, dy);
            return this;
        }

        // 缩放变换
        public tCanvas ScaleTransform(float scale)
        {
            myG.ScaleTransform(scale, scale);
            return this;
        }

        // 重置变换
        public tCanvas ResetTransform()
        {
            myG.ResetTransform();
            return this;
        }

代码中定义的方法包括:

  • RotateTransform(),旋转变换,参数为旋转的角度。
  • TranslateTransform(),原点平移变换。
  • ScaleTransform(),缩放变换。
  • ResetTransform(),重置,即恢复默认状态。

绘制旋转45度的正方形可以使用如下代码。

C#
using System.Drawing;
using cfx.image;

namespace csfx_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            tCanvas.Create(150, 100).Clear(Color.Gray)
                .TranslateTransform(75, 50)
                .RotateTransform(45)
                .FillSquare(Brushes.Red, 0, 0, 60)
                .ResetTransform()
                .Save(@"d:\99.png");
        }
    }
}

绘制结果如下图所示。

图形变换应用

代码中,首先创建了150*100毫米的画布,并设置背景色为灰色。然后,通过TranslateTransform()方法将原点(0,0)位置向右移动75毫米,向下移动50毫米;接下来通过RotateTransform()方法旋转45度,并绘制正方形,此时原点在图形的中间位置,旋转也会以图形中心为准进行。最后,恢复变换前的状态,并保存图片文件。