【WPF】学习笔记(三)——这个家伙跟电子签名板有个约定
这篇博客依旧是以电子签名板为基础而展开的,主要是对前文(【WPF】学习笔记(一)——做一个简单的电子签名板)存在的部分问题进行解释,以及部分小功能的添加。由于这篇博客是建立在学习笔记一的基础上的,所以希望各位在正式阅览本文之前,对前文有一个大体的了解。
先简单谈谈这篇博客的目标:1.解决前文将电子签名保存至本地时存在的问题 2.为电子签名加上水印(文本) 3.全局异常捕获、是否以管理员权限运行、防止应用多开这些小功能的实现
1. 解决前文将电子签名保存至本地时存在的问题
1.1 前文存在的问题
很抱歉最初在写WPF学习笔记一的时候没有发现这个问题,并给部分童鞋带来了一定的误导,接下来请容我小小的说明一下。如图,之前的方式虽然能将签(涂)名(鸦)完整地保存下来,但实际上,签名的截图只是成为了整个图片的一部分,而不是全部(额~就是那块很尴尬的透明边框啦)。那如果我们只是想要把需要的那部分保留下来,该怎么办呢?
1.2 初步解决问题的方法
考虑使用图片裁剪的方法,只将所需要的部分给裁下来就可以了。接下来为大家提供一个图片裁剪的方法:
/// <summary>
/// 图片裁剪
/// </summary>
/// <param name="bmpSource">用于裁剪的图片</param>
/// <param name="rect">需要裁剪(保留)的区域</param>
/// <param name="dpiX">横向dpi</param>
/// <param name="dpiY">纵向dpi</param>
/// <returns></returns>
public static BitmapSource Clip(this BitmapSource bmpSource, Int32Rect rect, double dpiX, double dpiY)
{
/* BitsPerPixel表示每个像素占多少位(PixelFormats.Pbgra32为32位) */
var stride = bmpSource.Format.BitsPerPixel * rect.Width / ;
var bytes = new byte[rect.Height * stride];
bmpSource.CopyPixels(rect, bytes, stride, ); return BitmapSource.Create(rect.Width, rect.Height, dpiX, dpiY, PixelFormats.Pbgra32, null, bytes, stride);
}
方法很简单,按照套路走就行了(提供需要裁剪的图片以及需要保留的区域等信息)。
需要说明一下的是:
A.由于在之前的程序中使用的dpi是72,所以调用这个方法时,传入的dpi参数也应该对应地使用72。
B.为了方便图片裁剪,可以在保存签名的时候,将签名部分移动到最左上角的位置(移动方法稍后进行详细说明)
而后我们可能会有这么一个问题:裁剪后的图片在尺寸方面不太符合我们预期的目标(裁剪以后尺寸就变小了),所以接下来再为各位提供一个简单的图片缩放的方法:
// scaleX表示横向缩放比,scaleY表示纵向缩放比
TransformedBitmap scaledSource = bmpSource.Transform(new ScaleTransform(scaleX, scaleY));
/// <summary>
/// 图片变换
/// </summary>
/// <param name="bmpSource">用于变换的图片</param>
/// <param name="transform">变换内容</param>
/// <returns></returns>
public static TransformedBitmap Transform(this BitmapSource bmpSource, Transform transform)
{
return new TransformedBitmap(bmpSource, transform);
}
需要注意的一点是:这个地方不是操作矢量图,所以缩放效果并不好(放大图片的情况下会导致图片失真)
现在,我们来总结一下:这个部分提供的解决方案是建立在前文的思想基础上的,但最终的效果顶多只能算是差强人意。那有没有更好的解决方案呢?于是我们需要分析下造成问题的根本原因。
1.3 造成问题的根本原因
可以很明确地说,原因的根源就在于前文提到的dpi设置问题。即下图展示的这行代码:
1.4 问题分析与解决
我们先来确定两个知识点:A.Windows下默认dpi是96 B.dpi设置的大小会直接影响签名结果的缩放
由于我们将dpi设置为了72,因此最终渲染出来的签名部分实际上是缩小后的结果
于是我们应该观察一下将前文中的dpi修改为默认dpi的结果:
我们发现,图片并没有被完整地保存下来(这也是之前我将dpi设置为72的原因),而且我们发现:在保存的图片中,签名部分距离图片左边和上边都有一定的距离(有一定的偏移量)。于是我们可以作出这样一个猜想:是不是由于这个偏移量导致了图片显示不全呢?
接下来我们就尝试将在保存截图前将这个偏移量消除掉:
/* 为了方便对比,这里同时贴上了之前的写法 */ /* 之前的写法 */
var renderBitmap = new RenderTargetBitmap((int)ink.ActualWidth, (int)ink.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
renderBitmap.Render(ink); /* 现在的写法 */
var renderBitmap = new RenderTargetBitmap((int)ink.ActualWidth, (int)ink.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(ink);
context.DrawRectangle(visualBrush, null, new Rect(new Point(), ink.RenderSize));
} renderBitmap.Render(drawingVisual);
可以看出来,在后一个写法中以控件ink为基础,渲染了一个对应的Visual,我们将这个Visual放置在左上角,并最终用这个Visual来渲染出最终的截图。看看运行的效果:
嗯O(∩_∩)O,我给满分!!
2. 加水印
主要是遇上了这么一个需求:给用户签名的截图打上水印文本,标识用户签名的时间等信息
好吧,这个问题的解决方案在网上一搜一大把,我在此就仅仅将自己写的方法作个简单的展示吧,如果能为各位提供一点点参考便再好不过了:
/// <summary>
/// 为图片添加水印文字,并将结果保存至输出目录
/// </summary>
/// <param name="srcpath">源图片完整路径</param>
/// <param name="tarpath">输出路径</param>
/// <param name="text">水印文字</param>
/// <param name="font">文字信息,默认"宋体", 15, FontStyle.Regular</param>
/// <param name="brush">水印画刷</param>
/// <param name="ratioEnabled">是否按照比例给定水印偏移量</param>
/// <param name="lateralOffset">横向偏移量的值或者比例。若给定偏移比例,则应当为一个[-1, 1]区间的数值,-1表示居左,0表示居中,1表示居右</param>
/// <param name="longitudinalOffset">纵向偏移量的值或者比例。若给定偏移比例,则应当为一个[-1, 1]区间的数值,-1表示置顶,0表示居中,1表示置底</param>
/// <returns></returns>
public static bool AddWaterText(string srcpath, string tarpath, string text, Font font, System.Drawing.Brush brush, bool ratioEnabled = true, float lateralOffset = , float longitudinalOffset = )
{
bool res = false; try
{
var img = Image.FromFile(srcpath); // 新建与原图等大的空白位图
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
bmp.SetResolution(96f, 96f); // 由于Windows默认dpi为96,这一行代码可以省略。修改此处的值可以对水印文字进行缩放。 var graphics = Graphics.FromImage(bmp);
graphics.Clear(System.Drawing.Color.Transparent);
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; // 将图片绘制到位图上
graphics.DrawImage(img, new Rectangle(, , img.Width, img.Height), , , img.Width, img.Height, GraphicsUnit.Pixel); // 水印占据空间测量
font = font ?? new Font("宋体", , System.Drawing.FontStyle.Bold);
var markSize = graphics.MeasureString(text, font); // 水印位置计算
var offsetX = lateralOffset; // 根据给定的偏移量计算位置
var offsetY = longitudinalOffset; if(ratioEnabled) // 根据给定的偏移比例计算位置
{
// 计算空白区域中心点位置
var hcenter = (img.Width - markSize.Width) / ;
var vcenter = (img.Height - markSize.Height) / ; // 确保偏移系数在[-1, 1]范围内
lateralOffset = lateralOffset < - ? - : (lateralOffset > ? : lateralOffset);
longitudinalOffset = longitudinalOffset < - ? - : (longitudinalOffset > ? : longitudinalOffset); // 偏移量
offsetX = hcenter + lateralOffset * hcenter;
offsetY = vcenter + longitudinalOffset * vcenter;
} // 将水印绘制到位图上
graphics.DrawString(text, font, brush, offsetX, offsetY); // 释放img占用的资源,避免tarpath == srcpath的情况下因文件资源被占用导致的报错
img.Dispose(); // 将处理后的位图保存至输出目录
var imgFormat = tarpath.EndsWith(".jpg") ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png;
bmp.Save(tarpath, imgFormat); res = true;
}
catch { } return res;
}
稍微看一下测试结果:
3. 几个小功能的实现
这里实现的几个小功能都很基础,在编程过程中也有可能使用到,所以在这里简单的写一写。
PS:这几个小功能都编写在App.xaml.cs这个文件中
3.1 全局异常捕获
个人认为:没有加上全局异常捕获的应用不能算是完整的应用。因为当应用的功能堆积到一定程度的情况下,往往总会有意想不到的情况发生,如果没有全局异常捕获,那么平时没有处理到的异常就容易导致应用闪退,造成比较糟糕的用户体验。
把这个功能放到对应的构造函数App()中,或者override(我比较愿意称它为覆写)的OnStartup方法中都是比较好的。主要需要处理的异常有三类:Task的异常、UI线程的异常和非UI线程的异常
// 这篇博客有详细解释: https://www.mgenware.com/blog/?p=231
// 主要是处理Task执行中抛出的异常,通常在垃圾回收时终结器执行线程中被抛出
TaskScheduler.UnobservedTaskException += (s, e) =>
{
// TODO:可以进行把异常信息写到日志文件之类的处理 // 使未觉察异常被觉察到
e.SetObserved();
}; // 处理应用程序域内的未处理异常(非UI线程异常)
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
// TODO:可以进行把异常信息写到日志文件之类的处理 }; // 处理UI线程的异常
this.DispatcherUnhandledException += (s, e) =>
{
// TODO:可以进行把异常信息写到日志文件之类的处理 e.Handled = true;
};
3.2 是否以管理员权限运行
/// <summary>
/// 判断是否以管理员权限运行
/// </summary>
/// <returns></returns>
private bool IsAdmin()
{
var current = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(current); return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
3.3 防止应用多开
对于这个问题,我个人并没有进行太深入的研究,只是大概知道下面这种方式是可行,这个部分仅为各位提供一个参考。如果想要了解详情,建议搜索并了解更多相关知识。在StackOverflow中,有这么一篇帖子: http://stackoverflow.com/questions/646480/is-using-a-mutex-to-prevent-multiple-instances-of-the-same-program-from-running,感兴趣的童鞋可以了解下。
// 设置为全局变量,保证其生命周期
Mutex mutex;
private void App_Startup(object sender, StartupEventArgs e)
{
bool res; mutex = new Mutex(true, "Lary.Wpf.Demo", out res); if (!res)
{
MessageBox.Show("咳咳,不允许双开的哈( ‵▽′)ψ"); Environment.Exit();
}
}
4. Demo
http://files.cnblogs.com/files/lary/UserSignatureDemo_20170514.rar
【WPF】学习笔记(三)——这个家伙跟电子签名板有个约定的更多相关文章
- WPF学习笔记三之绑定
1.绑定模式 <TextBlock Margin="10" Text="LearningHard" Name="lbtext" Fon ...
- Oracle学习笔记三 SQL命令
SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)
- [Firefly引擎][学习笔记三][已完结]所需模块封装
原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读: 笔记三主要就是各个模块的封装了,这里贴 ...
- JSP学习笔记(三):简单的Tomcat Web服务器
注意:每次对Tomcat配置文件进行修改后,必须重启Tomcat 在E盘的DATA文件夹中创建TomcatDemo文件夹,并将Tomcat安装路径下的webapps/ROOT中的WEB-INF文件夹复 ...
- java之jvm学习笔记三(Class文件检验器)
java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...
- VSTO学习笔记(三) 开发Office 2010 64位COM加载项
原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...
- Java IO学习笔记三
Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...
- NumPy学习笔记 三 股票价格
NumPy学习笔记 三 股票价格 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.&l ...
- Learning ROS for Robotics Programming Second Edition学习笔记(三) 补充 hector_slam
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
随机推荐
- 【C语言】浅谈可变参数与printf函数
一.何谓可变参数 int printf( const char* format, ...); 这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用& ...
- MySQL---事务知识,你搞明白没有?
MySQL - 事务 在学习事务这一概念前,我们需要需要构思一个场景 场景构思 假设该场景发生于一个银行转账背景下,月中,又到了发工资的日子.潭州教育科技集团打算给Tuple老师发放一个月的工资.(此 ...
- API测试自动化——基于CDIF的SOA基本功能(实例篇)
今天我们通过一些实例来体验一下API的自动化测试,感受一下基于CDIF的SOA的一些基本功能. 传统的测试工具在测试一个API的时候,必须手动填写这个API所需要接收的所有信息,比如一个查询航班动态的 ...
- [深入学习Redis]RedisAPI的原子性分析
在学习Redis的常用操作时,经常看到介绍说,Redis的set.get以及hset等等命令的执行都是原子性的,但是令自己百思不得其解的是,为什么这些操作是原子性的? 原子性 原子性是数据库的事务中的 ...
- [原]C#与非托管——封送和自动封送
之前说到了如何从C函数声明通过简单的查找替换生成一份C#的静态引用声明(C#与非托管——初体验),因为只是简单说明,所以全部采用的是基础类型匹配和自动封送.自动封送虽然能省去我们不少编码时间,但如果不 ...
- 老李分享:QTP的录制原理以及实现
老李分享:QTP的录制原理以及实现 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:9088 ...
- 利用<meta http-equiv="refresh" content="0;URL=?id='.$id.'" />一条一条的更新数据
<meta http-equiv="refresh" content="0;URL=?id='.$id.'" /> 解释:页面定时刷新,后面加url ...
- javascript 函数的多义性
所谓多义性指的是一种语法多种概念,多种用法.javascript function有三个概念三种用法 1 直接当函数被调用 function foo() {...} foo() 2 在函数下挂载静态函 ...
- PHPCMS笔记第二弹
熟练地使用PHPCMS可以插入模板,将静态站转变为动态站也更加方便,多加练习还是有好处的 将index.html的头和尾拆分出来,分别放在header.html和footer.html文件夹中,这三个 ...
- c++中关于值对象与其指针以及const值对象与其指针的问题详细介绍
话不多说,先附上一段代码与运行截图 //1 const int a = 10; //const 值对象 int *ap = (int *)&a;//将const int*指针强制转化为int* ...