.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

 

本文應該有很多不細膩的地方,比如文字處理方面,獲取大小的精度問題,請大家多多指教,如有不對的地方,請指出。有更好的方案處理,也請不吝賜教,感恩!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM