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以后开始,对于部分敏感的“危险”权限,需要在应用运行时向用户申请,只有用户允许的情况下这个权限才会被授予给应用.这对于用户来说,无疑是一个提升安全性的做法.那么对于开发者,应该怎 ...
随机推荐
- 一道百度java面试题的多种解法
下面是我在2018年10月11日二面百度的时候的一个问题: java程序,主进程需要等待多个子进程结束之后再执行后续的代码,有哪些方案可以实现? 这个需求其实我们在工作中经常会用到,比如用户下单一个产 ...
- 真香!Python开发工程师都选择这个数据库:因为它免费
数据库类别 既然我们要使用关系数据库,就必须选择一个关系数据库. 目前广泛使用的关系数据库也就这么几种: 付费的商用数据库: Oracle,典型的高富帅: SQL Server,微软自家产品,Wind ...
- PyQt(Python+Qt)学习随笔:Qt Designer中建立CommandLinkButton信号与Action的槽函数连接
在Qt Designer中,通过F4进行信号和槽函数连接编辑时,接收信号的对象不能是Action对象,但在右侧的编辑界面,可以选择将一个界面对象的信号与Action对象的槽函数连接起来. 如图: 上图 ...
- scrapy爬虫爬取小姐姐图片(不羞涩)
这个爬虫主要学习scrapy的item Pipeline 是时候搬出这张图了: 当我们要使用item Pipeline的时候,要现在settings里面取消这几行的注释 我们可以自定义Item Pip ...
- python 读取excel表格内不同类型的数据
不同类型数据对应值: #coding=utf-8 import os import xlrd from datetime import datetime,date newparh = os.chdir ...
- 懒松鼠Flink-Boot(Flink+Spring):一款将Flink与Spring生态完美融合的脚手架工程
目录 你可能面临如下苦恼: 接口缓存 重试机制 Bean校验 等等...... 它为流计算开发工程师解决了 有了它你的代码就像这样子: 仓库地址:懒松鼠Flink-Boot 1. 组织结构 2. 技术 ...
- XJOI contest 1590
首先 热烈庆祝"CSP-S 2020全国开放赛前冲刺模拟训练题1"圆满结束!!! 感谢大毒瘤周指导的题目.题目还是很不错的,部分分设置的也比较合理,各种神仙随便 \(\text{A ...
- 【APIO2020】交换城市(Kruskal重构树)
Description 给定一个 \(n\) 个点,\(m\) 条边的无向连通图,边带权. \(q\) 次询问,每次询问两个点 \(x, y\),求两点间的次小瓶颈路.不存在输出 -1. Hint \ ...
- vue-cli脚手架搭建vue3.0+typescripe项目
新开个项目,小项目,小.顺手就用vue吧,vue3出来也几个月了,直接上了吧.一年多没用vue了,用的时候也得再熟悉,不如直接干3了! vue官方推荐使用的脚手架是 Vite 和 vue-cli ,延 ...
- JavaScript:浏览器的本地存储
cookie.localStorage.sessionStorage的使用 <!DOCTYPE html> <html lang="en"> <hea ...