由于经常需要基于固定的一个模板底图,生成微信小程序分享用的海报图,如果每次都调用绘图函数,手动编写每个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. 01-Python里字符串的常用操作方法--replace()函数

    1. replace()  函数 作用: 替换字符串 语法: replace('需要替换的子串', '新的子串', '替换的次数(可不传)') # 当替换次数不传时,默认全部替换 实例一: mystr ...

  2. SpringCloud 源码系列(1)—— 注册中心 Eureka(上)

    Eureka 是 Netflix 公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡.熔断器.网关等)一起,被 Spring Cloud 整合为 Spring C ...

  3. 12_Sensor简单实例

    列出Android手机所支持的Sensor. package com.example.sensorlist; import java.util.List; import android.app.Act ...

  4. golang 自学系列(三)—— if,for,channel

    golang 自学系列(三)-- if,for,channel 一般情况下,if 语句跟大多数语言的 if 判断语句一样,根据一个 boolean 表达式结果来执行两个分支逻辑. 但凡总是有例外,go ...

  5. Spring 源码学习 04:初始化容器与 DefaultListableBeanFactory

    前言 在前一篇文章:创建 IoC 容器的几种方式中,介绍了四种方式,这里以 AnnotationConfigApplicationContext 为例,跟进代码,看看 IoC 的启动流程. 入口 从 ...

  6. NDK&JNI开发总结

    NDK&JNI开发总结 简介 附个不错的博客 https://www.jianshu.com/p/87ce6f565d37 在Android Framework中,需要提供一种媒介或 桥梁,将 ...

  7. SSM之Spring框架--->>墨盒案例

    分析: 程序中包括打印机(Printer).墨盒(Ink).和纸张(Paper).三类组件 首先创建一个新项目文件Spring_Box 我们来定义墨盒和纸张的接口类 墨盒接口Ink.java文件内容如 ...

  8. Spring Cloud Alibaba 初体验(六) Seata 及结合 MyBatis 与 MyBatis-Plus 的使用

    一.下载与运行 本文使用 Seata 1.1.0:https://github.com/seata/seata/releases Windows 环境下双击 bin/seata-server.bat ...

  9. 第10.2节 查看导入的Python模块

    在Python中,要查看导入模块,可以使用sys.modules来查看,不过sys包含了所有导入模块包括内建模块,如果需要过滤掉内建模块甚至扩展模块,则需要对sys.modules进行一下过滤. 一. ...

  10. Win10激活失败并提示错误代码0xC004C003的解决方法

    亲测,可用. 步骤如下: 进入cmd,管理员权限登录 slmgr.vbs /upk slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX slmgr /skms zh.us ...