C#自定义TemplateImage使用模板底图,运行时根据用户或产品信息生成海报图(1)

由于经常需要基于固定的一个模板底图,生成微信小程序分享用的海报图,如果每次都调用绘图函数,手动编写每个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

 

posted @ 2020-12-30 09:22  启天  阅读(871)  评论(6编辑  收藏  举报