IT界最近这几年,各种乱七八糟的东西不断出现,其中能用在实际工作与生活中的,大概也就那么几个。Web 前端也冒出各种框架,这就为那些喜欢乱用框架的公司提供了很好的机会,于是造成很多项目体积越来越庞大,越来越难维护。一切变得越来越没有标准,所以,很多公司在招聘码农时就特能乱写,还要求你精通 AA,BB,CC,DD,EE,FF,GG……甚至有的不下二三十项要求。老周觉得这些公司基本上是神经病,先不说世界没有人能精通那么多东西,就算真有人能精通那么多,那估计这个人也活不久了,早晚得累死的。

实际上,Web 前端你能学会三样东西就够了——HTML、CSS、JS,其他纯属娱乐。

所以,学习编程的话,你抓几个有代表性地学就好了,比如C/C++,.net,PHP,Java 这些,其余的嘛,现学现用,用完就扔。你要是想让自己变成高手的话,那你就必须挑一个方向,纵向深度发展。什么都学等于什么都不通,学乱七八糟的东西是成不了高手的。就拿黑客这一活儿来说,只有第一代,第二代黑客比较强,后面的基本是菜鸟,一代不如一代。没办法,浮躁的时代,IT业也不可幸免的。

好了,上面的都是P话,下面老周开始说正题,今天咱们谈谈如何将电子墨迹保存到图像。在近年来出现的各种花拳绣腿技术中,电子墨迹还算是有实用价值的东西。还有触控、虚拟化这些,也有一定的用途。人工智障倒是可有可无,可作为辅助,但不太可靠,最起码它代替不了人脑(笨蛋例外),我估计将来搞艺术可能吃香,毕竟机器是不懂艺术的。普工可能会大量失业,因为他们做的事情可以让机器做了(主要是重复性,机械性的工作)。

拿笔写字是人的本能,千万不要鼠标键盘用多了连笔都拿不动(这已经是“鼠标手”的轻度症状了,不及时治疗,以后会很难看的)。科技再发达,人类的本能绝不能丢,就好比哪天你连穿衣吃饭都不会了,那你活该饿死。

本文就介绍两种比较简单的方法:

第一种是运用 win 2D 封装的功能来完成。老周做的那个“练字神器”应用就是用这种方法保存你的书法作品的,其中的宣纸纸纹原理也很简单,就是分层绘制,首先在底层绘制纸张的纹理图案,然后再把墨迹绘制到底纹之上即可。

第二种不需要借助其他 Nuget 上的库,只要使用 1709 最新的 API 就能实现。

先说第一种方案。

为了演示,老周就做简单一点。下面 XAML 代码在界面上声明了一个 InkCanvas ,用来收集输入的墨迹,然后一个 Button ,点击后选择文件路径,然后保存为 png 图片。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<InkCanvas Name="inkcv"/>
<Button Content="保存墨迹" Click="OnClick"  Grid.Row="1" Margin="2,9.5"/>
</Grid>

接着,你要打开 nuget 管理器,向项目添加 Win 2D 的引用。这个老周不多说了,你懂怎么操作的。

如果你绘制的墨迹图像需要在界面上显示,可以用 CanvasControl 控件,然后处理 Draw 事件,如果不需要在界面上显示,例如这个例子,我们是直接保存为图像文件的,所以不需要在界面上添加 CanvasControl 元素了。

前面在写 UI Composition 的文章时,老周曾用过 Win 2D 做演示,负责绘制操作的是 CanvasDrawingSession 类,其中,你会发现,它有一个方法叫 DrawInk,对的,我们用的就是它,它可以把我们从用户输入收集到的墨迹绘制下来。它有两个重载,其中一个是指定是否绘制成高对比度模式。

好,理论上的屁话不多说,我直接上代码,你一看就懂的。

不过,在页面类的构造函数中,我们得先设置一下书写的参数,比如笔触大小、颜色等。

        public MainPage()
{
this.InitializeComponent();
// 支持笔,手触,鼠标输入
inkcv.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch;
// 设定笔迹颜色为红色
InkDrawingAttributes data = new InkDrawingAttributes();
data.Color = Colors.Red;
// 笔触大小
data.Size = new Size(15d, 15d);
// 忽略笔的倾斜识别,毕竟只有新型的笔才有这感应
data.IgnoreTilt = true;
// 更新参数
inkcv.InkPresenter.UpdateDefaultDrawingAttributes(data);
}

随后就可以处理 Button 的 Click 事件了。

        private async void OnClick(object sender, RoutedEventArgs e)
{
// 如果没有输入墨迹,那就别浪费 CPU 时间了
if(inkcv.InkPresenter.StrokeContainer.GetStrokes().Any() == false)
{
return;
} // 选择保存文件
FileSavePicker picker = new FileSavePicker();
picker.FileTypeChoices.Add("PNG 图像", new string[] { ".png" });
picker.SuggestedFileName = "sample";
picker.SuggestedStartLocation = PickerLocationId.Desktop;
StorageFile file = await picker.PickSaveFileAsync();
if (file == null) return; // 建一个在内存中用的画板(不显示在 UI 上)
// 获取共享的 D2D 设备引用
CanvasDevice device = CanvasDevice.GetSharedDevice();
// 图像大小与 InkCanvas 控件大小相同
float width = (float)inkcv.ActualWidth;
float height = (float)inkcv.ActualHeight;
// DPI 为 96
float dpi = 96f;
CanvasRenderTarget drawtarget = new CanvasRenderTarget(device, width, height, dpi);
// 开始作画
using(var drawSession = drawtarget.CreateDrawingSession())
{
// 我们上面设置了用的是红笔
// 为了生成图片后看得清楚
// 把墙刷成白色
drawSession.Clear(Colors.White);
// 画墨迹
drawSession.DrawInk(inkcv.InkPresenter.StrokeContainer.GetStrokes());
}
// 保存到输出文件
await drawtarget.SaveAsync(await file.OpenAsync(FileAccessMode.ReadWrite), CanvasBitmapFileFormat.Png, 1.0f);
// 释放资源
drawtarget.Dispose();
}

运行应用后,随便写点啥上去。如下图。

然后点击按钮,保存一下。生成的图片如下图所示。

好,第一种方案完结,接下来咱们用第二种方案。

这是 1709 (秋季创作者更新)的新功能。新的 SDK 中增加了一个 CoreInkPresenterHost 类(位于 Windows.UI.Input.Inking.Core 命名空间),使用这类,你可以不需要 InkCanvas 控件,你可以把墨迹接收图面放到任意的 XAML 元素上。因为该类公开一个 RootVisual 属性,注意它不是指向 XAML 可视化元素,而是 ContainerVisual 对象。这是 UI Composition 中的容器类。

老周前不久刚写过一堆与 UI Composition 有关的文章,如果你不了解相关内容,可以看老周前面的烂文。通过前面对 UI Composition 的学习,我们知道,可以将可视化对象添加到任意 XAML 可视化元素上。对,这个 CoreInkPresenterHost 类就是运用了这个特点,使得墨迹收集可以脱离 InkCanvas 控件,以后,你爱在哪个元素上收集墨迹都行,比如,你想让用户可以对图像进行涂鸦,你就可以把这个类放到 Image 元素上。

P话少说,咱们来点干货。下面的例子,其界面和前一个例子相似,只是没有用上 InkCanvas 控件,而只是声明了个 Border 元素。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border Name="bd" Margin="3" BorderThickness="1" BorderBrush="Green"/>
<Button Grid.Row="1" Margin="4,8" Content="保存墨迹" Click="OnClick"/>
</Grid>

然后切换到代码文件,在页面类的构造函数中,进行一下初始化。初始化的东西挺多,包括用 Compositor 创建用来承载墨迹的容器 Visual ,以及设置笔触参数。

        CoreInkPresenterHost inkHost = null;
public MainPage()
{
this.InitializeComponent(); // 组装一个 UI,把一个可视化容器放到 Border 上
Visual bdvisual = ElementCompositionPreview.GetElementVisual(bd);
var compositor = bdvisual.Compositor;
// 创建一个容器
ContainerVisual inkContainer = compositor.CreateContainerVisual();
// 此时因为各元素的宽度和高度都为0,所以用动画来更新容器的大小
var expressAnimate = compositor.CreateExpressionAnimation();
expressAnimate.Expression = "bd.Size";
expressAnimate.SetReferenceParameter("bd", bdvisual);
inkContainer.StartAnimation("Size", expressAnimate);
// 设置容器与 Border 关联
ElementCompositionPreview.SetElementChildVisual(bd, inkContainer); // 处理墨迹收集关联
inkHost = new CoreInkPresenterHost();
inkHost.RootVisual =
inkContainer;
inkHost.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen | Windows.UI.Core.CoreInputDeviceTypes.Touch;
// 设置笔触参数
InkDrawingAttributes attrib = new InkDrawingAttributes();
attrib.Color = Colors.SkyBlue;
attrib.Size = new Size(15f, 15f);
attrib.IgnoreTilt = true;
// 更新参数
inkHost.InkPresenter.UpdateDefaultDrawingAttributes(attrib);
}

创建了容器 Visual 后,记得要通过 CoreInkPresenterHost 对象的 RootVisual 属性来关联。当然你不能忘了把这个 visual 加到 Border 的子元素序列上。

现在处理 Click 事件,用 RenderTargetBitmap 类,把 Border 的内容画出来,这样会连同它上面的墨迹也一起画出来。

            // 这个类可以绘制 XAML 元素,以前介绍过
RenderTargetBitmap rtarget = new RenderTargetBitmap();
await rtarget.RenderAsync(bd);

然后用图像编码器写入文件就行了。

            // 获取像素数据
var pxBuffer = await rtarget.GetPixelsAsync();
// 开始为图像编码
using(var stream = await outFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)rtarget.PixelWidth, (uint)rtarget.PixelHeight, 96d, 96d, pxBuffer.ToArray());
await encoder.FlushAsync();
}

完整的事件处理代码如下。

        private async void OnClick(object sender, RoutedEventArgs e)
{
if (inkHost.InkPresenter.StrokeContainer.GetStrokes().Any() == false)
return; FileSavePicker picker = new FileSavePicker();
picker.FileTypeChoices.Add("PNG 图像文件", new string[] { ".png" });
picker.SuggestedFileName = "sample"; StorageFile outFile = await picker.PickSaveFileAsync();
if (outFile == null)
return; // 这个类可以绘制 XAML 元素,以前介绍过
RenderTargetBitmap rtarget = new RenderTargetBitmap();
await rtarget.RenderAsync(bd);
// 获取像素数据
var pxBuffer = await rtarget.GetPixelsAsync();
// 开始为图像编码
using(var stream = await outFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)rtarget.PixelWidth, (uint)rtarget.PixelHeight, 96d, 96d, pxBuffer.ToArray());
await encoder.FlushAsync();
}
}

好,完事了,现在运行一下,直接中 Border 元素上写点东东。

然后点击底部的按钮保存为图片,如下图所示。

OK,本文就扯到这里了,开饭,不然饭菜凉了。

【Win 10 应用开发】将墨迹保存到图像的两种方法的更多相关文章

  1. C# Windows Phone 8 WP8 开发,取得手机萤幕大小两种方法。

    原文:C# Windows Phone 8 WP8 开发,取得手机萤幕大小两种方法. 一般我们在开发Windows Phone App时,需要取得萤幕的大小来自定义最佳化控制项的大小,但是开如何取得萤 ...

  2. WPF编程,将控件所呈现的内容保存成图像的一种方法。

    原文:WPF编程,将控件所呈现的内容保存成图像的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/detai ...

  3. php 下载保存文件保存到本地的两种方法

    第一种: 1 <? ?> 或 <?php //下载文件保存到本地//www.jbxue.comfunction downfile($fileurl){ob_start(); $fil ...

  4. 【C/C++开发】C++实现字符串替换的两种方法

    替换字符串replace() erase() //C++ 第一种替换字符串的方法用replace()|C++ 第二种替换字符串的方法用erase()和insert()[ C++string|C++ r ...

  5. 【Win 10 应用开发】启动远程设备上的应用

    这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...

  6. 【Win 10 应用开发】导入.pfx证书

    这个功能其实并不常用,一般开发较少涉及到证书,不过,简单了解一下还是有必要的. 先来说说制作测试证书的方法,这里老周讲两种方法,可以生成用于测试的.pfx文件. 产生证书,大家都知道有个makecer ...

  7. win 10 用户上传头像保存的文件夹路径

    win 10 用户上传头像保存的文件夹路径 C:\Users\Administrator(用户名)\AppData\Roaming\Microsoft\Windows\AccountPictures

  8. 【Win 10应用开发】多窗口视图

    Windows App一般情况下,同一时刻只能有一个应用程序实例在运行,为了在特殊需求下可以同时呈现不同的UI,SDK提供了多视图操作支持. 应用程序可以创建新的应用视图,以新的视图为基础可以呈现与主 ...

  9. 【Win 10 应用开发】UI Composition 札记(八):用 XamlLight 制作灯光效果

    前面老周已介绍过灯光的使用,如果你忘了,请用九牛二虎之力猛点击这里去复习一下.本篇老周再介绍另一种添加灯光的方法,这种方法是专为 XAML 元素而设计的,可以很方便地为可视化元素添加灯光效果. 不知道 ...

随机推荐

  1. 从零自学Hadoop(25):Impala相关操作下

    阅读目录 序 导入数据 查询 系列索引 本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 序 上一 ...

  2. angualr高级篇之elem.scope()、elem.isolateScope和$compile(elem)(scope)中scope的区别

    在angular的使用过程中我们经常用$rootScope.$new()为elem创建一个新的作用域scope,然后使用$compile(elem)(scope)编译这个含有指令的元素.那么这里传进去 ...

  3. zookeeper启动后的注意事项

    在各节点中运行zkServer.sh start后 1.首先看进程是否存在 QuorumPeerMain. 2.查看zookeeper的运行状态 zkServer.sh status,会出现一个lea ...

  4. python基础6 迭代器 生成器

    可迭代的:内部含有__iter__方法的数据类型叫可迭代的,也叫迭代对象实现了迭代协议的对象 运用dir()方法来测试一个数据类型是不是可迭代的的. 迭代器协议是指:对象需要提供next方法,它要么返 ...

  5. Web前端性能优化全攻略[转载]

    1. 尽量减少 HTTP 请求 (Make Fewer HTTP Requests) 作为第一条,可能也是最重要的一条.根据 Yahoo! 研究团队的数据分析,有很大一部分用户访问会因为这一条而取得最 ...

  6. android视频播放器系列(一)——系统播放器

    使用系统播放器(intent隐士调用)可以播放本地视频和网络视频,但是使用方式上稍微有点差别: 一.播放本地视频 Uri uri = Uri.parse("本地视频地址");Int ...

  7. redis数据库安装及简单的增删改查

    redis下载地址:https://github.com/MSOpenTech/redis/releases. 解压之后,运行 redis-server.exe redis.windows.conf  ...

  8. Delphi工程版本号修改工具

    自动修改某目录下符合条件的Delphi工程(dproj)版本号, 支持命令行调用支持通配符忽略文件 -p [Path] 在[Path]路径下查询所有dproj文件(可以为空, 默认路径为程序当前路径) ...

  9. windows中更换Jdk版本不生效

    本机已经安装了jdk1.7,而比较早期的项目需要依赖jdk1.6,于是同时在本机安装了jdk1.6和jdk1.7. 安装jdk1.6前,执行java -version得到 C:\Users\liuxi ...

  10. C#操作Excel(读取)

    一.使用OleDb,这个法子好像不大好使.容易读错.引用System.Data.OleDb;     /**//// <summary>        /// 返回Excel数据源     ...