由於經常需要基於固定的一個模板底圖,生成微信小程式分享用的海報圖,如果每次都呼叫繪圖函式,手動編寫每個placeholder的填充,重複而且容易出錯,因此,封裝一個TemplateImage,用於填充每個需要畫上資料的地方,
先看看呼叫的方式:
_homeShareTemplate.Generate(new TemplateItem[] //Generate返回新的Bitmap { new StringTemplateItem() //日期 { Location = new Point(80 * 2, 78*2), Font = new Font("宋體", 42, FontStyle.Bold, GraphicsUnit.Pixel), Color = Color.FromArgb(0x8e, 0x1a, 0x22), Value = DateTime.Now.ToString("yyyy.MM.dd"), Horizontal = HorizontalPosition.Center }, new StringTemplateItem() //農曆 { Location = new Point(230*2, 166*2), //MaxWidth = 15, Font = new Font("宋體", 22, FontStyle.Bold, GraphicsUnit.Pixel), Color = Color.FromArgb(0x8e, 0x1a, 0x22), StringFormat = new StringFormat(StringFormatFlags.DirectionVertical), Value = GetMonthCalendar(DateTime.Now) }, new StringTemplateItem() //星期 { Location = new Point(256*2, 175*2), //MaxWidth = 15, Font = new Font("宋體", 24, FontStyle.Bold, GraphicsUnit.Pixel), Color = Color.FromArgb(0x8e, 0x1a, 0x22), StringFormat = new StringFormat(StringFormatFlags.DirectionVertical), Value = GetWeekName(DateTime.Now) }, new ImageTemplateItem() //圖片 { Image = (Bitmap) Bitmap.FromFile(Path.Join(Directory.GetCurrentDirectory(),weather.MainImageUrl)), Location = new Point(81*2, 108*2), Size = new Size(132*2, 133*2) }, new StringTemplateItem() { Location = new Point(88*2, 257*2), MaxWidth = 125*2, Font = new Font("楷體", 30, FontStyle.Bold, GraphicsUnit.Pixel), Color = Color.FromArgb(0x17, 0x14, 0x0e), Value = weather.Content.Left(44) }, new StringTemplateItem() //宜 { Location = new Point(35*2+3,294*2), Color = Color.FromArgb(0x8f, 0x1A, 0x22), Font = new Font("宋體", 38, FontStyle.Bold, GraphicsUnit.Pixel), StringFormat = new StringFormat(StringFormatFlags.DirectionVertical), //MaxWidth = 14, Value = weather.Yi.Left(4) }, new StringTemplateItem() //忌 { Location = new Point(228*2+3,294*2), Color = Color.FromArgb(0x8f, 0x1A, 0x22), Font = new Font("宋體", 38, FontStyle.Bold, GraphicsUnit.Pixel), StringFormat = new StringFormat(StringFormatFlags.DirectionVertical), //MaxWidth = 14, Value = weather.Ji.Left(4) }, new QrCodeTemplateItem() //二維碼 { Location = new Point(188*2, 421*2), Size = new Size(73*2, 72*2), QrCode = "http://ssssss.com/sdfsdfsdfs/sss" } });
輸出的效果如下:
完整的功能由一個TemplateImage作為模板圖管理的類+N個根據需要輸出的各種資料處理類,可根據實際需求進行擴充套件不同的型別,預設有:String,Image,QrCode三種:
單個模板圖管理類的定義:
public class TemplateImage:IDisposable { private Bitmap _templateSource = null; private Stream _sourceStream = null; private FileSystemWatcher _wather = null; public TemplateImage(Bitmap templateSource) { _templateSource = templateSource; } /// <summary> /// 模板圖片的建構函式 /// </summary> /// <param name="templatePath">模板圖片檔案絕對路徑</param> /// <param name="isWatchFileModify">是否自動監控檔案,當檔案有變動時,自動重新載入模板檔案 /// </param> public TemplateImage(string templatePath,bool isWatchFileModify=true) { if (!File.Exists(templatePath)) { throw new FileNotFoundException(nameof(templatePath)); } //開啟模板檔案路徑,在跳出建構函式後,自動釋放file物件,防止長久佔用檔案,導致無法替換模板檔案 using var file = File.OpenRead(templatePath); var data = file.ReadAllBytes(); var s1 = new ByteStream(data); //這裡s1肯定不能關閉,否則,再呼叫Bitmap.Clone函式的時候,會報錯 _sourceStream = s1; _templateSource = (Bitmap) Bitmap.FromStream(s1); if (isWatchFileModify) //如果啟用檔案監控,則自動監控模板圖片檔案 { _wather = new FileSystemWatcher(templatePath); _wather.EnableRaisingEvents = true; _wather.Changed += wather_changed; } } private void wather_changed(object sender, FileSystemEventArgs e) { if (e.ChangeType == WatcherChangeTypes.Changed || e.ChangeType== WatcherChangeTypes.Created ) { using var file = File.OpenRead(e.FullPath); var data = file.ReadAllBytes(); var oldValue = _sourceStream; var templateSource = _templateSource; var s1 = new ByteStream(data); var newTemplateSource = (Bitmap) Bitmap.FromStream(s1); _sourceStream = s1; _templateSource = newTemplateSource; oldValue.Close(); oldValue.Dispose(); templateSource.Dispose(); } } public SmoothingMode SmoothingMode { set; get; } = SmoothingMode.AntiAlias; public TextRenderingHint TextRenderingHint { set; get; } = TextRenderingHint.AntiAlias; public CompositingQuality CompositingQuality { set; get; } = CompositingQuality.HighQuality; /// <summary> /// 根據傳入的資料,套入模板圖片,生成新的圖片 /// </summary> /// <param name="settings"></param> /// <returns></returns> public Bitmap Generate(TemplateItemBase[] settings) { //Clone一個新的Bitmap物件 var newImg = (Bitmap)_templateSource.Clone(); var g1 = Graphics.FromImage(_templateSource); try { using (var g = Graphics.FromImage(newImg)) { g.SmoothingMode = SmoothingMode; g.TextRenderingHint = TextRenderingHint; g.CompositingQuality = CompositingQuality; foreach (var item in settings) { item.Draw(g, newImg.Size); //呼叫每個Item的Draw畫入新的資料 } return newImg; } } catch (Exception e) { Console.WriteLine(e); throw; } } public void Dispose() { _templateSource.Dispose(); _sourceStream?.Close(); _sourceStream?.Dispose(); } }
至此,一個模板圖片類已定義完成,接下來需要定義一個Placeholder的基類:
1 public abstract class TemplateItemBase 2 { 3 /// <summary> 4 /// 水平方向對其方式,預設為Custom,使用Location定位 5 /// </summary> 6 public HorizontalPosition Horizontal { set; get; } = HorizontalPosition.Custom; 7 8 /// <summary> 9 /// 垂直方向對其方式,預設為Custom,使用Location定位 10 /// </summary> 11 public VerticalPosition Vertical { set; get; } = VerticalPosition.Custom; 12 13 /// <summary> 14 /// 輸出項定位 15 /// </summary> 16 public Point Location { set; get; } 17 18 public abstract void Draw(Graphics graphics,Size newBitmapSize); 19 20 }
這個基類定義了每個placeholder的定位方式,Custom表示使用Location自定義位置.
然後開始來定義每個不同型別的TemplateItem:
1.String型別:
1 /// <summary> 2 /// 普通字串項 3 /// </summary> 4 public class StringTemplateItem : TemplateItemBase 5 { 6 /// <summary> 7 /// 文字字串值 8 /// </summary> 9 public string Value { set; get; } 10 11 /// <summary> 12 /// 字型資訊 13 /// </summary> 14 public Font Font { set; get; } 15 16 /// <summary> 17 /// 字型顏色 18 /// </summary> 19 public Color Color { set; get; }= Color.Black; 20 21 /// <summary> 22 /// 文字輸出的最大寬度,如果為0,則自動,,如果非0,則只用最大寬度,並自動根據最大寬度修改計算字串所需高度 23 /// </summary> 24 public int MaxWidth { set; get; } = 0; 25 26 /// <summary> 27 /// 字串輸出引數 28 /// </summary> 29 /// <example> 30 /// 如縱向輸出: 31 /// new StringFormat(StringFormatFlags.DirectionVertical) 32 /// 33 /// </example> 34 public StringFormat StringFormat { set; get; } 35 36 public override void Draw(Graphics graphics,Size newBitmapSize) 37 { 38 var location = this.Location; 39 SizeF size=default(Size); 40 if (this.Horizontal== HorizontalPosition.Center || this.Vertical== VerticalPosition.Middle) 41 { 42 location = new Point(this.Location.X,this.Location.Y); 43 44 if (this.MaxWidth>0) 45 { 46 size = graphics.MeasureString(this.Value, this.Font,this.MaxWidth); 47 } 48 else 49 { 50 size = graphics.MeasureString(this.Value, this.Font); 51 } 52 53 if (this.Horizontal== HorizontalPosition.Center) 54 { 55 var newx = newBitmapSize.Width / 2 - (int)(size.Width / 2); 56 location.X = newx; 57 } 58 59 if (this.Vertical== VerticalPosition.Middle) 60 { 61 var newy= newBitmapSize.Height / 2 - (int)(size.Height / 2); 62 location.Y = newy; 63 } 64 } 65 else if(MaxWidth>0) 66 { 67 size = graphics.MeasureString(this.Value, this.Font,this.MaxWidth); 68 } 69 70 if (MaxWidth>0) 71 { 72 graphics.DrawString(this.Value, this.Font,new SolidBrush(this.Color), new RectangleF(location,size),StringFormat); 73 } 74 else 75 { 76 graphics.DrawString(this.Value, this.Font,new SolidBrush(this.Color), location,StringFormat); 77 } 78 79 80 } 81 }
2.純圖片型別:
1 /// <summary> 2 /// 傳入一個圖片 3 /// </summary> 4 public class ImageTemplateItem:TemplateItemBase 5 { 6 /// <summary> 7 /// 圖片資料 8 /// </summary> 9 public Bitmap Image { set; get; } 10 11 /// <summary> 12 /// 圖片輸出到模板圖的時候的大小 13 /// </summary> 14 public Size Size { set; get; } 15 16 public override void Draw(Graphics graphics,Size newBitmapSize) 17 { 18 var location = this.Location; 19 20 //計算垂直居中或水平居中的情況下的定位 21 if (this.Horizontal== HorizontalPosition.Center || this.Vertical== VerticalPosition.Middle) 22 { 23 location = new Point(this.Location.X,this.Location.Y); 24 25 if (this.Horizontal== HorizontalPosition.Center) 26 { 27 var newx = newBitmapSize.Width / 2 - this.Size.Width / 2; 28 29 location.X = newx; 30 } 31 32 if (this.Vertical== VerticalPosition.Middle) 33 { 34 var newy= newBitmapSize.Height / 2 - this.Size.Height / 2; 35 location.Y = newy; 36 } 37 } 38 39 //此處後續可優化為使用Lockbits的方式 40 graphics.DrawImage(Image,new Rectangle(location,this.Size),new Rectangle(0,0,this.Image.Width,this.Image.Height),GraphicsUnit.Pixel); 41 42 } 43 }
3.QrCode的方式,使用QRCoder類庫:
1 /// <summary> 2 /// 二維碼項 3 /// </summary> 4 public class QrCodeTemplateItem : TemplateItemBase 5 { 6 /// <summary> 7 /// 二維碼內實際儲存的字元資料 8 /// </summary> 9 public string QrCode { set; get; } 10 11 /// <summary> 12 /// 二維碼中心的icon圖示 13 /// </summary> 14 public Bitmap Icon { set; get; } 15 16 /// <summary> 17 /// 二維碼尺寸 18 /// </summary> 19 public Size Size { set; get; } 20 21 /// <summary> 22 /// 容錯級別,預設為M 23 /// </summary> 24 public QRCodeGenerator.ECCLevel ECCLevel { set; get; } = QRCodeGenerator.ECCLevel.M; 25 26 public override void Draw(Graphics graphics,Size newBitmapSize) 27 { 28 var location = this.Location; 29 30 if (this.Horizontal== HorizontalPosition.Center || this.Vertical== VerticalPosition.Middle) 31 { 32 location = new Point(this.Location.X,this.Location.Y); 33 34 if (this.Horizontal== HorizontalPosition.Center) 35 { 36 var newx = newBitmapSize.Width / 2 - this.Size.Width / 2; 37 38 location.X = newx; 39 } 40 41 if (this.Vertical== VerticalPosition.Middle) 42 { 43 var newy= newBitmapSize.Height / 2 - this.Size.Height / 2; 44 location.Y = newy; 45 } 46 } 47 48 using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) 49 using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(QrCode,ECCLevel)) 50 using (QRCode qrCode = new QRCode(qrCodeData)) 51 using (Bitmap qrCodeImage = qrCode.GetGraphic(20,Color.Black,Color.White,Icon)) 52 { 53 graphics.DrawImage(qrCodeImage,new Rectangle(location,this.Size),new Rectangle(0,0,qrCodeImage.Width,qrCodeImage.Height),GraphicsUnit.Pixel); 54 55 } 56 } 57 }
後續的優化:
1.Image畫入的優化處理,考慮是否可以用Lockbits進行優化
2.增加不同型別的新的Item
完整的程式碼詳見:https://github.com/kugarliyifan/Kugar.Core/blob/master/Kugar.Core.NetCore/Images/TemplateImage.cs