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
C#自定义TemplateImage使用模板底图,运行时根据用户或产品信息生成海报图(1)的更多相关文章
- Android中的自定义注解(反射实现-运行时注解)
预备知识: Java注解基础 Java反射原理 Java动态代理 一.布局文件的注解 我们在Android开发的时候,总是会写到setContentView方法,为了避免每次都写重复的代码,我们需要使 ...
- Docker 运行时的用户与组管理的方法
docker 以进程为核心, 对系统资源进行隔离使用的管理工具. 隔离是通过 cgroups (control groups 进程控制组) 这个操作系统内核特性来实现的. 包括用户的参数限制. 帐户管 ...
- VS2008中编译通过,但调试时出现“未使用调试信息生成二进制文件”的问题
.只要是“建立项目的时候不应建立空项目,而应当建立一个“win32控制台应用程序”.这样确实可以解决问题.只要你选择的是这个"win32控制台应用程序"则在附加选项里面选不选上“空 ...
- 我从16ASPX上下了一个程序在运行时出错是怎么回事?运行时出现用户SA登陆失败,但是我已经把数据库导入SQL
如果你账号密码正确,那你可能没有打开你的管线服务,或者没有配置好你的客户端
- T4运行时模板
可以通过Visual Studio运行时文本模板在您的应用程序在运行时生成文本字符串. 执行应用程序的计算机不必具有 Visual Studio. 运行库模板有时称为"预处理文本模板&quo ...
- 自定义注解之运行时注解(RetentionPolicy.RUNTIME)
对注解概念不了解的可以先看这个:Java注解基础概念总结 前面有提到注解按生命周期来划分可分为3类: 1.RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成clas ...
- permission 文档 翻译 运行时权限
文档位置:API24/guide/topics/security/permissions.html System Permissions 系统权限 Android is a privilege-se ...
- 【JavaSE】运行时类型信息(RTTI、反射)
运行时类型信息使得你可以在程序运行时发现和使用类型信息.--<Think in java 4th> **** 通常我们在面向对象的程序设计中我们经常使用多态特性使得大部分代码尽可能地少了解 ...
- Android开发学习之路-Android6.0运行时权限
在Android6.0以后开始,对于部分敏感的“危险”权限,需要在应用运行时向用户申请,只有用户允许的情况下这个权限才会被授予给应用.这对于用户来说,无疑是一个提升安全性的做法.那么对于开发者,应该怎 ...
随机推荐
- linux下定时任务的简单示例
1.方式一:用sleep实现定时任务: 1.1 编辑shell脚本,如下sleep_aa.sh: #!/bin/bash t1=`date "+%Y-%m-%d %H:%M:%S" ...
- python大数问题
python不需要特殊的声明,可以直接进行大数运算 验证:
- MacOS JMeter安装(多图)
本文基于 MacOS 环境下进行 Jmeter 的安装. 一.下载JMeter 本文选用 JMeter 5.3 版本安装,5.3 版本需要 JDK 1.8 + 版本环境. Jmeter 5.3 下载: ...
- JZOJ8月5日提高组反思
JZOJ8月5日提高组反思 再次炸了 虽然不是爆0 但也没差多少-- T1 想的DP 然后就打了 一开始是只能拿60的 后来想到了用前缀和优化 然后打完交了 最后一分钟测了一下空间 爆了 就赶紧把数组 ...
- springboot:读取application.yml文件
现在开发主要使用微服务框架springboot,在springboot中经常遇到读取application.yml文件的情形. 一.概述 开发过程中经常遇到要读取application.yml文件中的 ...
- 赶紧收藏吧!MyBatis-Plus万字长文图解笔记,错过了这个村可就没这个店了
简介 MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发.提高效率而生 愿景 我们的愿景是成为 MyBatis 最好的搭档 ...
- moviepy音视频开发:audio_loop实现音频内容循环重复
☞ ░ 前往老猿Python博文目录 ░ 概述 moviepy的audio_loop函数用于将音频剪辑内容循环一定次数,返回值是原剪辑内容重复指定次数对应的剪辑. 调用语法: audio_loop(a ...
- moviepy音视频剪辑:TextClip不支持中文字符以及OSError: magick.exe: unable to read font 仿宋_GB2312.ttf的解决办法
☞ ░ 前往老猿Python博文目录 ░ 一.引言 moviepy对中文和多语言环境的支持做得并不好,包括中文文件名以及用于显示文字的TextClip就是典型的中文支持方面存在问题的.对于编解码的问题 ...
- PyQt(Python+Qt)学习随笔:QTreeWidgetItem项获取项的父项或子项
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 树型部件QTreeWidget中的QTreeWidgetItem项,可以通过child(int in ...
- WindowsServer系统设置U盘引导及安装
准备一台服务器,我的服务器上图. 1.开机启动,按DEL进入BIOS.我的显示如下图,按F7进入. 2.找到设置启动项的地方 3.修改U盘启动项 4.保存退出. 5.重启服务器正常的话应该能够从U盘引 ...