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

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

  1. Android中的自定义注解(反射实现-运行时注解)

    预备知识: Java注解基础 Java反射原理 Java动态代理 一.布局文件的注解 我们在Android开发的时候,总是会写到setContentView方法,为了避免每次都写重复的代码,我们需要使 ...

  2. Docker 运行时的用户与组管理的方法

    docker 以进程为核心, 对系统资源进行隔离使用的管理工具. 隔离是通过 cgroups (control groups 进程控制组) 这个操作系统内核特性来实现的. 包括用户的参数限制. 帐户管 ...

  3. VS2008中编译通过,但调试时出现“未使用调试信息生成二进制文件”的问题

    .只要是“建立项目的时候不应建立空项目,而应当建立一个“win32控制台应用程序”.这样确实可以解决问题.只要你选择的是这个"win32控制台应用程序"则在附加选项里面选不选上“空 ...

  4. 我从16ASPX上下了一个程序在运行时出错是怎么回事?运行时出现用户SA登陆失败,但是我已经把数据库导入SQL

    如果你账号密码正确,那你可能没有打开你的管线服务,或者没有配置好你的客户端

  5. T4运行时模板

    可以通过Visual Studio运行时文本模板在您的应用程序在运行时生成文本字符串. 执行应用程序的计算机不必具有 Visual Studio. 运行库模板有时称为"预处理文本模板&quo ...

  6. 自定义注解之运行时注解(RetentionPolicy.RUNTIME)

    对注解概念不了解的可以先看这个:Java注解基础概念总结 前面有提到注解按生命周期来划分可分为3类: 1.RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成clas ...

  7. permission 文档 翻译 运行时权限

    文档位置:API24/guide/topics/security/permissions.html  System Permissions 系统权限 Android is a privilege-se ...

  8. 【JavaSE】运行时类型信息(RTTI、反射)

    运行时类型信息使得你可以在程序运行时发现和使用类型信息.--<Think in java 4th> **** 通常我们在面向对象的程序设计中我们经常使用多态特性使得大部分代码尽可能地少了解 ...

  9. Android开发学习之路-Android6.0运行时权限

    在Android6.0以后开始,对于部分敏感的“危险”权限,需要在应用运行时向用户申请,只有用户允许的情况下这个权限才会被授予给应用.这对于用户来说,无疑是一个提升安全性的做法.那么对于开发者,应该怎 ...

随机推荐

  1. java42

    1.Random类 随机生成某个整数 Random r = new Random(); System.out.println(r.nextInt()); 伪随机数:第一次打印为随机,再次运行,数字将保 ...

  2. 第15.27节 PyQt(Python+Qt)入门学习:Model/View架构中的便利类QTreeWidget详解

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.引言 树部件(Tree Widget)是Qt Designer中 Item Widgets(It ...

  3. PyQt(Python+Qt)学习随笔:Designer中的QDialogButtonBox增加自定义按钮的方法

    在Qt Designer中可以预先定义标准按钮,相关支持的标准按钮请见<PyQt(Python+Qt)学习随笔:Designer中的QDialogButtonBox的StandardButton ...

  4. 使用PyQt(Python+Qt)+动态编译36行代码实现的计算器

    PyQt是基于跨平台的图形界面C++开发工具Qt加Python包装的一个GPL软件(GPL是GNU General Public License的缩写,是GNU通用公共授权非正式的中文翻译),Qt基于 ...

  5. Python中错误之 TypeError: object() takes no parameters、TypeError: this constructor takes no arguments

    TypeError: object() takes no parameters TypeError: this constructor takes no arguments 如下是学习python类时 ...

  6. Python实现自动整理文件

    前言 工作上的文档和资料好几个月没整理了,因为平常太忙都是随手往桌面丢.整个桌面杂乱无章全是文档和资料.几乎快占满整个屏幕了,所有我必须要整理一下了.但是手动整理太费时间了,于是我想到了python. ...

  7. pytorch实战(二)hw2——预测收入是否高于50000,分类问题

    代码和ppt: https://github.com/Iallen520/lhy_DL_Hw 遇到的一些细节问题: 1. X_train文件不带后缀名csv,所以不是规范的csv文件,不能直接用pd. ...

  8. 水星路由器自动更换IP工具

    这个工具是本人抢火车票的时候,自己写的换IP工具,仅支持自己的水星,其他水星不知道,请自测!!!! 点击更换IP,他会断开链接,重新拨号!!(达到更换IP的目的) !!开发语言:易语言(源码在下方)使 ...

  9. socket ThreadingTCPServer学习笔记

    文件上传#服务端 while True: conn,address = sk.accept() conn.sendall(bytes('欢迎你小sb',encoding='utf-8')) str_s ...

  10. 120多套各种类别微信小程序模板源码打包下载

    120多套各种类别微信小程序模板源码打包下载,以下是部分截图欢迎下载!120多套各种类别微信小程序模板源码打包下载 下载地址:https://pan.baidu.com/s/1Cfqyc9p2ZDOc ...