由于经常需要基于固定的一个模板底图,生成微信小程序分享用的海报图,如果每次都调用绘图函数,手动编写每个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. 三万字无坑搭建基于Docker+K8S+GitLab/SVN+Jenkins+Harbor持续集成交付环境

    写在前面 最近在 K8S 1.18.2 版本的集群上搭建DevOps环境,期间遇到了各种坑.目前,搭建环境的过程中出现的各种坑均已被填平,特此记录,并分享给大家! 文章和搭建环境所需要的yml文件已收 ...

  2. ASP.NET Core管道详解[6]: ASP.NET Core应用是如何启动的?[下篇]

    要承载一个ASP.NET Core应用,只需要将GenericWebHostService服务注册到承载系统中即可.但GenericWebHostService服务具有针对其他一系列服务的依赖,所以在 ...

  3. Linux之【安装系统后的调优和安全设置】

    关闭SElinux功能 •修改配置文件使其永远生效 第一种修改方法vi vi /etc/sysconfig/selinuc 或者 vi /etc/selinux/config修改: SELINUX=d ...

  4. js实现视频截图,视频批量截图,canvas实现

    截取视频的某一时间的图像并保存 利用canvas的绘画能力画出视频某一帧的视频画面, 获得到图像之后转换成base64图像, 再利用a标签的实现自动保存到本地 html代码 <!DOCTYPE ...

  5. keil/MDK代码配色

    个人配色方案,仅供参考.

  6. 第7.19节 Python中的抽象类详解:abstractmethod、abc与真实子类

    第7.19节 Python中的抽象类详解:abstractmethod.abc与真实子类 一.    引言 前面相关的章节已经介绍过,Python中定义某种类型是以实现了该类型对应的协议为标准的,而不 ...

  7. Python正则运算符优先级re.findall('(.)*',"abc")、re.findall('(.*)',"abc")、re.findall('(.?)*',"abc")的执行结果的影响分析

    我们分别执行三个语句: >>> re.findall('(.)*',"abc") ['c', ''] >>> re.findall('(.*)' ...

  8. Centos 7 下的java安装

    安装java 下载jdk的安装包,放到 /usr/local 目录下 创建一个 java 的目录存放 jdk 1 mkdir java 解压java安装包 1 tar -zxvf "jdk名 ...

  9. 百度前端技术学院-基础-day25-27

    倒数开始 滴答滴 滴答滴 task1 题目: 我们现在来做一个最简单的时钟,通过小练习来学习 Date,复习定时,然后再练习一下函数的封装具体需求如下: 在页面中显示当前日期及时间,按秒更新 格式为 ...

  10. 【题解】「AT4303」[ABC119D] Lazy Faith

    AT4303 [ABC119D] Lazy Faith[题解][二分] AT4303 translation 有 \(a\) 个点 \(s\),有 \(b\) 个点 \(t\),问从点 \(x\) 出 ...