最近因為需要用到圖片制作,所以重拾了一下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
本文應該有很多不細膩的地方,比如文字處理方面,獲取大小的精度問題,請大家多多指教,如有不對的地方,請指出。有更好的方案處理,也請不吝賜教,感恩!
