.NET下Graphics之文字图片处理

最近因为需要用到图片制作,所以重拾了一下Graphics的相关操作。我们的目的是输入一串字符串,使用特殊字体,生成一张奇数位文字左倾斜15度,偶数位文字右倾斜15度的图片。

1.使用自定义字体(因为字体现在有版权问题,在使用过程中,先确定是否已取得版权)。将自定义字体加入到字体序列集合(PrivateFontCollection)中,并返回其FontFamily,注意,字体路径一定要绝对路径。如下代码:

 1 /// <summary>
 2         /// 添加字体文件到客户字符集合中,并返回当前FontFamily
 3         /// </summary>
 4         /// <param name="fontPath">字体文件绝对路径</param>
 5 
 6         /// <returns></returns>
 7         public FontFamily AddFontToFamily(string fontPath)
 8         {
 9             if (string.IsNullOrWhiteSpace(fontPath) || !System.IO.File.Exists(fontPath))
10             {
11                 return  new FontFamily("黑体");//没有传字体文件,或字体文件不存在,则直接返回系统默认的黑体。
12             }
13             PrivateFontCollection pfc=new PrivateFontCollection();
14             pfc.AddFontFile(fontPath);
15             var idxCurrentFont = pfc.Families.Length - 1;
16             return pfc.Families[idxCurrentFont];
17         }

2.文字处理:

2.1.文字编辑,新建Font对象: 

 1 /// <summary>
 2         /// 相对路径转绝对路径
 3         /// </summary>
 4         /// <param name="path"></param>
 5         /// <returns></returns>
 6         public string RelativeToAbsPath(string path)
 7         {
 8             return  AppDomain.CurrentDomain.BaseDirectory + path.Replace("/", "\\");
 9         }
10 
11 /// <summary>
12         /// 使用自定义字符集
13         /// </summary>
14         /// <param name="fontFamily">使用字符集</param>
15         /// <param name="fontSize">字符大小</param>
16         /// <param name="fontStyle">字符样式</param>
17         /// <returns>字符及样式</returns>
18         public Font UseCustomFont(FontFamily fontFamily, int fontSize, FontStyle fontStyle = FontStyle.Regular)
19         {
20             var font = new Font(fontFamily, fontSize, FontStyle.Regular);
21             return font;
22         }

2.2.绘制文字,因为每个字可能定义的样式不同,所以这里采用愚笨的方法,单字处理:

 1 /// <summary>
 2         /// 生成正规的单个文字图片(不做任何处理)
 3         /// </summary>
 4         /// <param name="font">字体</param>
 5         /// <param name="content">内容</param>
 6         /// <param name="fontSize">字体大小</param>
 7         /// <param name="fontColor">字符颜色</param>
 8         /// <param name="deg">倾斜角度</param>
 9         /// <returns></returns>
10         public Bitmap CreateOneTxtImg(Font font, string content, int fontSize,Color fontColor,int deg=0)
11         {
12 
13             int mapWidth = fontSize + 200;
14 
15             int mapHeight = fontSize + 200;
16 
17             Bitmap bitmap = new Bitmap(mapWidth, mapHeight);
18 
19             Graphics graphics = Graphics.FromImage(bitmap);
20 
21             graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;//消除锯齿,枚举值
22 
23             graphics.Clear(Color.Transparent);//设置图片背景色为透明,枚举值,或自定义
24 
25             var brush=new SolidBrush(fontColor);//画笔并设置颜色
26             
27             graphics.DrawString(content, font, brush, 0, 0);
28 
29             //获取字符宽高
30             SizeF sizeF = graphics.MeasureString(content, font);
31 
32             
33 #if DEBUG 
34             //查证是否和实际字符大小有差距
35             graphics.DrawRectangle(new Pen(Color.Chartreuse), new Rectangle(0, 0, (int)sizeF.Width, (int)sizeF.Height));
36             //保存图片看效果
37             string uploadFileDirectory = "/tempuploads/usernames";
38             string fileName = uploadFileDirectory + "/" + TimeStampServices.CurrentTimeSeconds().ToString() + UtilServices.CreateNonceStr(3) + ".png";
39             string filePath = AppDomain.CurrentDomain.BaseDirectory + fileName.Replace("/", "\\");
40             if (System.IO.File.Exists(filePath))
41             {
42                 System.IO.File.Create(filePath).Close();
43             }
44             bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Png);
45 #endif
46 
47             return bitmap;
48         }

上述代码中为什么要加200的大小,是因为文字虽然设定了文字大小,但是实际大小并不是n*n的正方形,并且最终生成的字大小我们没法确定。Graphics.MeasureString测出来的也会有偏差我们看看实际效果,如下图,我们发现使用文字绘制四周会有留空,而且中英文的差异也是显而易见,暂时没找到精确计算字符宽高处理。现阶段可以想到的方案是截掉空白位置,但是需要测量,不能保证所有文字空白位置都是一定的值。这里不做此处理。

既然Graphics.MeasureString获取到的大小必然会大于文字的实际绘制大小,那么我们就用它的大小作为最终的显示大小进行处理(旋转)。

2.2.1.把文字范围外的内容裁掉,因为我们是从(0,0)位置写的文字,所以我们可以使用Graphics.DrawImage(img,0,0)进行处理:

 1 /// <summary>
 2         /// 图片裁剪及旋转操作
 3         /// </summary>
 4         /// <param name="textSize">内容大小</param>
 5         /// <param name="tempBitmap">内容</param>
 6         /// <returns></returns>
 7         public Bitmap ClipImg(SizeF textSize, Bitmap tempBitmap)
 8         {
 9             Bitmap bitmap = new Bitmap((int)textSize.Width, (int)textSize.Height);
10             Graphics graphics = Graphics.FromImage(bitmap);
11             graphics.Clear(Color.Transparent);
12             graphics.DrawImage(tempBitmap, 0, 0);//
13             graphics.Dispose();
14             return bitmap;
15         }

2.2.2.裁剪后进行旋转,获取旋转后的宽高,这里需要用到三角函数处理(sin(a+b),sin(a-b))的内容,大家可以补一下,不再赘述整个推导过程。需要注意的是,sin及cos用的度数值都是弧度制,所以需要先把度数转成弧度。代码如下:

 1 /// <summary>
 2         /// 获取旋转后的宽高
 3         /// </summary>
 4         /// <param name="width">原始宽</param>
 5         /// <param name="height">原始高</param>
 6         /// <param name="deg">旋转的角度</param>
 7 
 8         public static SizeF GetRotateSize(int width, int height, int deg)
 9         {
10             double radian = (deg * Math.PI / 180); ;
11             double cos = Math.Cos(radian);
12             double sin = Math.Sin(radian);
13             float newWidth = (float)(Math.Abs(width * cos) + Math.Abs(height * sin));
14             float newHeight = (float)(Math.Abs(width * sin) + Math.Abs(height * cos));
15             return new SizeF(newWidth, newHeight);
16         }

2.2.3.图片旋转。因为我们是Graphics坐标系中,坐标原点(0,0)是在左上方,所以我们做的处理时:1.把坐标原点移至中心点;2.画布旋转n度;3.把坐标原点移回原坐标原点。有两套方案进行这个操作:

1.矩阵旋转:

1  Matrix matrix = graphics.Transform;
2  matrix.RotateAt(deg, new PointF(旋转点x, 旋转点y);
3  graphics.Transform = matrix;

 

2.设置graphics:

1 int moveX=偏移量x;
2 int moveY=偏移量y;
3 graphics.TranslateTransform(moveX, moveY);
4 graphics.RotateTransform(deg);
5 graphics.TranslateTransform(-moveX, -moveY);

因为我们的画布大小是图片经过旋转后外接矩形的大小,所以此时绘图的初始坐标应该是((int)(旋转后画布大小宽/ 2 - 内容宽 / 2), (int)(旋转后画布大小高/ 2 - 图片高 / 2)),关于这个点,大家也自行脑补一下,不再赘述。采用2.2.3 方法一进行旋转代码如下:

 1 /// <summary>
 2         /// 图片旋转操作
 3         /// </summary>
 4         /// <param name="textSize">内容大小</param>
 5         /// <param name="tempBitmap">内容</param>
 6         /// <param name="deg">旋转角度</param>
 7         /// <returns></returns>
 8         public Bitmap RotateImg(SizeF textSize,Bitmap tempBitmap,int deg)
 9         {
10             var sizeF = GetRotateSize(textSize.Width, textSize.Height, deg);
11 
12             Bitmap bitmap = new Bitmap((int)sizeF.Width, (int)sizeF.Height);
13 
14             Graphics graphics = Graphics.FromImage(bitmap);
15 
16             graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;//消除锯齿,枚举值
17 
18             graphics.Clear(Color.Transparent);//设置图片背景色为透明,枚举值,或自定义
19 
20 
21             Matrix matrix = graphics.Transform;
22             matrix.RotateAt(deg, new PointF((float)bitmap.Width / 2, (float)bitmap.Height / 2));
23             graphics.Transform = matrix;
24  
25             graphics.DrawImage(tempBitmap, new Rectangle((int)(bitmap.Width / 2 - tempBitmap.Width / 2), (int)(bitmap.Height / 2 - tempBitmap.Height / 2), tempBitmap.Width, tempBitmap.Height));
26 
27             graphics.ResetTransform();
28 
29             graphics.Dispose();
30 
31             return bitmap;
32 
33            
34         }

此时,我们修改一下2.2的CreateOneTxtImg方法:

 1 /// <summary>
 2         /// 生成正规的单个文字图片(不做任何处理)
 3         /// </summary>
 4         /// <param name="font">字体</param>
 5         /// <param name="content">内容</param>
 6         /// <param name="fontSize">字体大小</param>
 7         /// <param name="fontColor">字符颜色</param>
 8         /// <param name="deg">倾斜角度</param>
 9         /// <returns></returns>
10         public Bitmap CreateOneTxtImg(Font font, string content, int fontSize,Color fontColor,int deg=0)
11         {
12 
13             int mapWidth = fontSize + 200;
14 
15             int mapHeight = fontSize + 200;
16 
17             Bitmap bitmap = new Bitmap(mapWidth, mapHeight);
18 
19             Graphics graphics = Graphics.FromImage(bitmap);
20 
21             graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;//消除锯齿,枚举值
22 
23             graphics.Clear(Color.Transparent);//设置图片背景色为透明,枚举值,或自定义
24 
25             var brush=new SolidBrush(fontColor);//画笔并设置颜色
26             
27             graphics.DrawString(content, font, brush, 0, 0);
28 
29             //获取字符宽高
30             SizeF sizeF = graphics.MeasureString(content, font);
31 
32             var clipBitmap = ClipImg(sizeF, bitmap);
33             bitmap.Dispose();
34             graphics.Dispose();
35             var rotateBitmap = RotateImg(sizeF, clipBitmap, deg);
36             clipBitmap.Dispose();
37 #if DEBUG 
38             //查证是否和实际字符大小有差距
39             //graphics.DrawRectangle(new Pen(Color.Chartreuse), new Rectangle(0, 0, (int)sizeF.Width, (int)sizeF.Height));
40             //保存图片看效果
41             string uploadFileDirectory = "/tempuploads/usernames";
42             string fileName = uploadFileDirectory + "/" + TimeStampServices.CurrentTimeSeconds().ToString() + UtilServices.CreateNonceStr(3) + ".png";
43             string filePath = AppDomain.CurrentDomain.BaseDirectory + fileName.Replace("/", "\\");
44             if (System.IO.File.Exists(filePath))
45             {
46                 System.IO.File.Create(filePath).Close();
47             }
48             rotateBitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Png);
49 #endif
50 
51             return bitmap;
52         }

可以看到,如下效果:

基本操作已经完成,最后我们计算每个字的宽总和,以及字图的高度最大值,生成最终的图片。

 1 /// <summary>
 2         /// 内容最终宽高
 3         /// </summary>
 4         /// <param name="bitmaps">所有内容</param>
 5         /// <returns></returns>
 6 
 7         public Size GetAllWidthAndHeihgt(List<Bitmap> bitmaps)
 8         {
 9             int width = 0;
10             int height = 0;
11             foreach (var item in bitmaps)
12             {
13                 width += item.Width;
14                 if (item.Height > height)
15                 {
16                     height = item.Height;
17                 }
18             }
19             return new Size(width, height);
20         } 

内容整合:

 1 /// <summary>
 2         /// 文转图
 3         /// </summary>
 4         /// <param name="fontPath">字体相对路径</param>
 5         /// <param name="fontSize">字体大小</param>
 6         /// <param name="fontColor">字符颜色</param>
 7         /// <param name="textContent">需处理的内容</param>
 8         /// <param name="filePath">生成的图片路径</param>
 9         /// <returns></returns>
10         public bool TextToImg(string fontPath, int fontSize, Color fontColor, string textContent = "", string filePath = "")
11         {
12             if (string.IsNullOrWhiteSpace(fontPath) || string.IsNullOrWhiteSpace(textContent))
13             {
14                 return false;
15             }
16 
17             fontPath = RelativeToAbsPath(fontPath);
18 
19             if (fontColor.IsEmpty)
20             {
21                 fontColor = Color.FromArgb(255, 255, 255, 255);
22             }
23 
24             var fontFamily = AddFontToFamily(fontPath);
25 
26             var font = UseCustomFont(fontFamily, fontSize);
27 
28             List<Bitmap> lstTextImg = new List<Bitmap>();
29 
30             foreach (var txt in textContent)
31             {
32                 var idxInContent = textContent.IndexOf(txt);//当前字符在字符串的索引位置
33                 int deg = -15;
34                 if (idxInContent % 2 == 1)
35                 {
36                     deg = 15;
37                 }
38                 var bitMap = CreateOneTxtImg(font, txt.ToString(), fontSize, fontColor, deg);
39                 lstTextImg.Add(bitMap);
40             }
41             Bitmap endBitmap = CreateResult(lstTextImg);
42             endBitmap.Save(filePath, ImageFormat.Png);
43 endBitmap.Dispose();
44 return true; 45 }

 

调用示例,记得生成文件时要释放文件资源,否则会一直被占用中:

 1 /// <summary>
 2         /// 文转图接口测试
 3         /// </summary>
 4         /// <param name="fontColor">字符颜色</param>
 5         /// <param name="fontSize">字符大小</param>
 6         /// <param name="textContent">字符串内容</param>
 7         /// <returns></returns>
 8         public ActionResult TextToImgTest(string fontColor, int fontSize = 60, string textContent = "测试123avenAVEN")
 9         {
10             Color color = Color.Red;
11             if (!string.IsNullOrWhiteSpace(fontColor))
12             {
13                 string[] argb = fontColor.Split(new char[] {','});
14                 if (argb.Length == 4)
15                 {
16                     color = Color.FromArgb(int.Parse(argb[0]), int.Parse(argb[1]), int.Parse(argb[2]),
17                         int.Parse(argb[3]));
18                 }
19             }
20             //最终保存图片的地址
21             string uploadFileDirectory = "/tempuploads/usernames";
22             if (Directory.Exists(uploadFileDirectory))
23             {
24                 Directory.CreateDirectory(uploadFileDirectory);
25             }
26             string fileName = uploadFileDirectory + "/" + TimeStampServices.CurrentTimeSeconds().ToString() + UtilServices.CreateNonceStr(3) + ".png";
27             string filePath = AppDomain.CurrentDomain.BaseDirectory + fileName.Replace("/", "\\");
28 
29             if (!System.IO.File.Exists(filePath))
30             {
31                 System.IO.File.Create(filePath).Close();//创建完后记得释放,否则资源被占用
32             }
33 
34             bool result = TextToImg("/fonts/mfxingyan-noncommercial-regular.ttf", fontSize, color, textContent, filePath);
35             return Content(result.ToString());
36         }

End

 

本文应该有很多不细腻的地方,比如文字处理方面,获取大小的精度问题,请大家多多指教,如有不对的地方,请指出。有更好的方案处理,也请不吝赐教,感恩!

posted @ 2018-07-09 17:06  阿剑|Aven  阅读(2028)  评论(2编辑  收藏  举报