本文讨论画笔(Pen)和字体(Font),绘制线段、矩形、椭圆、曲线、弧线、文本,以及截取和绘制图像等内容,并继续将代码封装到tCanvas类中。
绘制线条时,主要使用画笔(Pen)对象。下面的代码(cfx/image/tCanvas.cs),首先在tCanvas类中封装画笔设置的方法。
using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; namespace cfx.image { public class tCanvas { // 其它代码 protected Pen myPen = new Pen(Color.Black, 0.1F); // public tCanvas SetPen(float width,int red,int green,int blue) { myPen.Width = width; myPen.Color = Color.FromArgb(red, green, blue); return this; } // public tCanvas SetPen(float width, Brush b) { myPen.Width = width; myPen.Brush = b; return this; } // public tCanvas SetPen(float width, Color cl) { myPen.Width = width; myPen.Color = cl; return this; } // public tCanvas SetStartCap(LineCap cap) { myPen.StartCap = cap; return this; } public tCanvas SetEndCap(LineCap cap) { myPen.EndCap = cap; return this; } public tCanvas SetLineCap(LineCap cap) { myPen.StartCap = cap; myPen.EndCap = cap; return this; } public tCanvas SetDash(DashStyle ds) { myPen.DashStyle = ds; return this; } } }
首先定义了字段myPen,默认使用黑色,0.1毫米宽度。接下来的SetPen()方法,参数包括宽度、颜色(RGB、Color或Brush)。SetStartCap()方法用于设置开始的线帽类型,SetEndCap()方法设置结束的线帽类型,SetLineCap()方法同时设置开始和结束的线帽类型,SetDash()方法设置线条的风格,如实线(Solid)和各种类型的虚线。
下面的代码(cfx/image/tCanvas.cs)定义了绘制线段的方法。
// public tCanvas MoveTo(float x,float y) { curX = x; curY = y; return this; } // public tCanvas LineTo(float x,float y) { myG.DrawLine(myPen, curX, curY, x, y); curX = x; curY = y; return this; }
MoveTo()方法会移动当前坐标,curX和curY定义为tCanvas类的内部字段,在前一篇文件有过介绍,它们定义为float类型;绘制线段时curX和curY会作为开始位置。LineTo()方法的参数需要定义线段结束位置的坐标,方法中调用了Graphics对象的DrawLine()方法绘制线段,其参数分别是Pen对象、起始坐标和结束坐标;LineTo()方法中,绘制完成后会将结束坐标保存到curX和curY字段中,并返回当前实例。
下面的代码,在Program.cs文件中测试线段的绘制及画笔对象的应用。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(1, Brushes.Black) .MoveTo(10, 10) .LineTo(100, 40) .LineTo(130, 10) .Save(@"d:\99.png"); } } }
绘制效果如下图所示。
下面的代码会显示几种线帽样式。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(3, Brushes.Black) .MoveTo(10, 10) .LineTo(100, 10) .SetLineCap(System.Drawing.Drawing2D.LineCap.Square) .MoveTo(10, 20) .LineTo(100, 20) .SetLineCap(System.Drawing.Drawing2D.LineCap.Round) .MoveTo(10, 30) .LineTo(100, 30) .SetLineCap(System.Drawing.Drawing2D.LineCap.Triangle) .MoveTo(10, 40) .LineTo(100, 40) .Save(@"d:\99.png"); } } }
第一线段线帽使用了默认设置(Flat),线段长度在坐标指定的范围之内。第二到第四条线段线帽样式分别使用了方形(Square)、圆形(Round)和三角形(Triangle),这里,线段开始和结束位置的线帽共占用一个线条宽度的长度。
除了图形线帽,还可以使用带锚点的线帽,如下面的代码。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(3, Brushes.Black) .MoveTo(10, 10) .LineTo(100, 10) .SetStartCap(System.Drawing.Drawing2D.LineCap.RoundAnchor) .SetEndCap(System.Drawing.Drawing2D.LineCap.ArrowAnchor) .MoveTo(10, 20) .LineTo(100, 20) .Save(@"d:\99.png"); } } }
带锚点的线帽中,圆形(Round)、方形(Square)和菱形(Diamond)的锚点位于图形的中心,而箭头线帽的锚点位于其指向的顶点。
MoveTo()和LineTo()方法适用于连续的线段绘制,如果需要单独的线段绘制,可以创建DrawLine()方法,如下面的代码(cfx/image/tCanvas.cs)。
public tCanvas DrawLine(float x1,float y1, float x2,float y2) { myG.DrawLine(myPen, x1, y1, x2, y2); curX = x2; curY = y2; return this; }
DrawLine()方法的参数需要指定线段开始和结束端点的坐标。
下面的代码,使用DrawLine()方法绘制几种不同样式的线段。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(1, Brushes.Black) .SetDash(System.Drawing.Drawing2D.DashStyle.Dash) .DrawLine(10, 10, 100, 10) .SetDash(System.Drawing.Drawing2D.DashStyle.Dot) .DrawLine(10, 15, 100, 15) .SetDash(System.Drawing.Drawing2D.DashStyle.DashDot) .DrawLine(10, 20, 100, 20) .SetDash(System.Drawing.Drawing2D.DashStyle.DashDotDot) .DrawLine(10, 25, 100, 25) .SetDash(System.Drawing.Drawing2D.DashStyle.Solid) .DrawLine(10, 30, 100, 30) .Save(@"d:\99.png"); } } }
代码绘制效果如下图所示。
绘制矩形时占用的全部尺寸中,宽度和高度会分别多出一个线宽的尺寸,因为线条会居中对齐图形的边界。绘制椭圆也是这样,其水平直径和垂直直径同样会比指定的宽度和高度各多出一个线宽的尺寸。
下面的代码(cfx/image/tCanvas.cs),在tCanvas类中定义了矩形和椭圆形的绘制方法。
// 绘制矩形 public tCanvas DrawRectangle(float x, float y, float width, float height) { myG.DrawRectangle(myPen, x, y, width, height); return this; } // 绘制椭圆 public tCanvas DrawEllipse(float x,float y, float width,float height) { myG.DrawEllipse(myPen, x, y, width, height); return this; }
DrawRectangle()和DrawEllipse()方法的参数都需要指定一个矩形区域,包括左上角坐标、宽度和高度。作为扩展,可以添加绘制正方形和圆形的方法,如下面的代码。
// 正方形 public tCanvas DrawSquare(float cx,float cy,float side) { float side2 = side / 2; myG.DrawRectangle(myPen, cx - side2, cy - side2, side, side); return this; } // 圆形 public tCanvas DrawCircle(float cx,float cy,float r) { float d = r * 2; myG.DrawEllipse(myPen, cx - r, cy - r, d, d); return this; }
DrawSquare()方法用于绘制正方形,参数为中心的坐标和边长;DrawCircle()方法用于绘制圆形,参数为圆心坐标和半径。
下面的代码,在Program.cs文件中测试这些图形的绘制。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(1, Brushes.Red) .DrawRectangle(10, 10, 50, 30) .DrawSquare(100, 25, 30) .SetPen(1, Brushes.Blue) .DrawEllipse(10, 60, 50, 30) .DrawCircle(100, 75, 15) .Save(@"d:\99.png"); } } }
关于边框对图形尺寸的影响,可以通过下面代码的绘制结果进行观察。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(6, Brushes.Red) .DrawRectangle(10, 10, 50, 30) .DrawRectangle(70, 10, 50, 30) .FillRectangle(Brushes.Blue, 10, 10, 50, 30) .Save(@"d:\99.png"); } } }
绘制绘制效果如下图所示。
本例中,首先使用相同的画笔(6毫米线宽、红色)绘制了两个矩形,然后使用与第一个矩形相同位置和尺寸的矩形参数绘制了蓝色填充矩形,可以观察填充矩形与边框的关系。
绘制多边形和封闭图形都需要指定多个顶点坐标,不同的是封闭图形可以设置边界的曲度(张力)。下面的代码(cfx/image/tCanvas.cs),在tCanvas类中定义了相关的绘制方法。
// 多边形 public tCanvas DrawPolygon(PointF[] pts) { myG.DrawPolygon(myPen, pts); return this; } // 封闭图形 public tCanvas DrawClosedCurve(PointF[] pts, float tension) { myG.DrawClosedCurve(myPen, pts, tension, FillMode.Alternate); return this; }
下面的代码,在Program.cs文件测试多边形的绘制。
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) .SetPen(2, Brushes.Black) .DrawPolygon(pts) .Save(@"d:\99.png"); } } }
下面的代码演示了封闭图形的绘制。
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) .SetPen(2, Brushes.Black) .DrawClosedCurve(pts, 0.5f) .Save(@"d:\99.png"); } } }
绘制贝塞尔曲线需要指定曲线两端坐标和两个控制点坐标,下面的代码(cfx/image/tCanvas.cs),在tCanvas类中定义了绘制贝塞尔曲线的方法。
// 贝塞尔曲线 public tCanvas DrawBezier(float x1,float y1,float x2,float y2, float x3,float y3,float x4,float y4) { myG.DrawBezier(myPen, x1, y1, x2, y2, x3, y3, x4, y4); return this; } // public tCanvas DrawBezier(PointF p1, PointF p2, PointF p3, PointF p4) { myG.DrawBezier(myPen, p1, p2, p3, p4); return this; } // public tCanvas DrawLine(PointF p1,PointF p2) { myG.DrawLine(myPen, p1,p2); curX = p2.X; curY = p2.X; return this; }
DrawBezier()方法中,参数需要四个点的坐标值,分别是曲线起点坐标,第一控制点坐标,第二控制点坐标,曲线终点坐标。新的DrawLine()方法重载版本使用两个PointF类型的参数作为线段端点坐标。
下面的代码,在Program.cs文件中测试贝塞尔曲线的绘制。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { PointF p1 = new PointF(30, 60); PointF p2 = new PointF(60, 10); PointF p3 = new PointF(100, 80); PointF p4 = new PointF(120, 50); // tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(2, Color.Black) .DrawBezier(p1, p2, p3, p4) .SetPen(1, Color.Red) .SetLineCap(System.Drawing.Drawing2D.LineCap.RoundAnchor) .DrawLine(p1, p2) .DrawLine(p3, p4) .Save(@"d:\99.png"); } } }
绘制结果如下图所示,其中,红色线段显示了曲线起始点与第一控制点的关系,以及曲线结束点与第二控制点的关系。
这里,弧线定义为椭圆(或圆形)边界的一部分,绘制时需要指定椭圆(或圆形)外切矩形的参数。下面的代码(cfx/image/tCanvas.cs),在tCanvas类中定义的绘制弧线的方法。
// 弧线 public tCanvas DrawArc(float x,float y, float width,float height, float startAngle,float sweepAngle) { myG.DrawArc(myPen, x, y, width, height, startAngle, sweepAngle); return this; }
DrawArc()方法中,除了画笔和矩形参数,还需要指定开始角度和旋转的角度,同样是水平向右方向为0度,顺时针方向为正角度,逆时针为负角度。下面的代码,在Program.cs文件测试弧线的绘制。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetPen(1, Color.Black) .DrawEllipse(10, 10, 100, 60) .SetPen(2, Color.Red) .DrawArc(10, 10, 100, 60, 0, -45) .Save(@"d:\99.png"); } } }
代码中首先绘制了一个椭圆,然后在椭圆的外界上绘制了一条弧线,绘制结果如下图所示。
绘制文本时需要指定字体(Font)和格式刷,其中,字体的主要属性包括字体名称、尺寸和样式(FontStyle枚举类型),而样式默认为常规(Regular),还可以设置加粗(Bold)、斜体(Italic)、贯穿线(Strikeout)、下画划(Underline)样式。
下面的代码(cfs/image/tCanvas.cs),在tCanvas类中定义了字体设置与文本绘制相关代码。
// 字体 protected Font myFont = new Font("Arial", 6F, GraphicsUnit.Millimeter); // protected Brush myFontBrush = Brushes.Black; // public tCanvas SetFont(string name, float size, bool bold = false, bool italic = false, bool underline = false, bool strikeout = false) { FontStyle fs = 0; if (bold) fs |= FontStyle.Bold; if (italic) fs |= FontStyle.Italic; if (underline) fs |= FontStyle.Underline; if (strikeout) fs |= FontStyle.Strikeout; myFont = new Font(name, size, fs, GraphicsUnit.Millimeter); return this; } // public tCanvas SetFontBrush(Brush b) { myFontBrush = b; return this; } // public tCanvas DrawString(string s,float x,float y) { myG.DrawString(s, myFont, myFontBrush, x, y); return this; }
下面的代码,在Program.cs文件中测试文本的绘制。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .DrawString("绘制文本ABCD123", 10, 10) .SetFontBrush(Brushes.Red) .SetFont("隶书", 16, bold: true, italic: true) .DrawString("绘制文本ABCD123", 10, 30) .Save(@"d:\99.png"); } } }
代码绘制结果如下图所示。
需要计算文本所占的宽度和高度时,可以在tCanvas类中封装如下方法。
// 计算文本区域尺寸 public SizeF GetStringSize(string s) { return myG.MeasureString(s, myFont); } // 居中绘制文本 public tCanvas DrawStringOnCenter(string s) { SizeF size = GetStringSize(s); float x = (myBmp.Width / dpmm - size.Width) / 2; float y = (myBmp.Height / dpmm - size.Height) / 2; return DrawString(s, x, y); }
GetStringSize()方法中,使用了Graphics对象的MeasureString()方法,方法会根据文本内容和字体计算文本区域的尺寸,请注意,myFont对象中的字体尺寸使用毫米为单位,所以,GetStringSize()方法返回的尺寸为毫米。DrawStringOnCenter()方法中,myBmp对象的Width和Height属性返回的是像素数值,需要除以dpmm(每毫米像素数量)得到毫米值。dpmm字段数据在构造函数中根据设置的DPI计算得到,在前一篇文章中有相关介绍。
下面的代码,在Program.cs文件测试文本的居中绘制。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { tCanvas.Create(150, 100).Clear(Color.Gray) .SetFont("宋体", 16, bold: true) .DrawStringOnCenter("绘制文本ABCD123") .Save(@"d:\99.png"); } } }
下面的代码(cfx/image/tCanvas.cs),在tCanvas类中定义绘制图像的方法。
// 绘制图像 public tCanvas DrawImage(Image img,float x,float y, float width,float height) { myG.DrawImage(img, x, y, width, height); return this; } // public tCanvas DrawImage(Image img, float x, float y, float width, float height, float surX, float surY, float surWidth, float surHeight) { myG.DrawImage(img, new RectangleF(x, y, width, height), new RectangleF(surX, surY, surWidth, surHeight), GraphicsUnit.Millimeter); return this; }
代码中定义了两个重载版本的DrawImage()方法,其中,第一个版本参数指定需要绘制的Image对象,以及绘制到当前tCanvas对象的区域设置。第二个版本需要指定Image对象,绘制到当前tCavnas对象的区域,以及截取的源图像的区域;需要注意的是这里使用了毫米作为单位,源图像的区域尺寸会根据其像素和DPI综合确定。
假设有文件d:\earth.png,如下图所示。图片的像素为486*389,分辨率为144DPI。
下面的代码,在Program.cs文件测试图像绘制操作。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { string sur = @"d:\earth.png"; tCanvas.Create(150, 100).Clear(Color.Gray) .DrawImage(Image.FromFile(sur), 10, 10, 60, 45) .Save(@"d:\99.png"); } } }
下面的代码会截取图像的一部分进行绘制。
using System.Drawing; using cfx.image; namespace csfx_demo { class Program { static void Main(string[] args) { string sur = @"d:\earth.png"; tCanvas.Create(150, 100).Clear(Color.Gray) .DrawImage(Image.FromFile(sur), 10, 10, 60, 45, 0, 0, 60, 45) .Save(@"d:\99.png"); } } }
请注意,截取的源图像区域取决于图像的像素和DPI。代码绘制效果如下图所示。