• .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

     

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

    人生之旅,无尽。人生之旅,有尽。此生与您相遇便是缘分,请多指教!
  • 相关阅读:
    github中建立pages
    批处理指令_windows定时关机脚本
    批处理指令_同步数据脚本
    python_笔记
    python_操作outlook
    按键精灵_一些踩过的坑
    linux_一些踩过的坑
    python_快速排序
    python_jira库的一些常用操作
    jira_一些踩过的坑
  • 原文地址:https://www.cnblogs.com/aven90/p/9274539.html
Copyright © 2020-2023  润新知