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. 队列(存储结构数组)--Java实现

    /*队列:其实也是一种操作受限的线性表 *特点:先进先出 *队尾指针:负责元素的进队 *队头指针:负责元素的出队 *注意:普通队--容易浪费空间,一般队列使用最多的就是循环队列--指针环绕 *队列的实 ...

  2. Hadoop完全分布式环境搭建

    前言 本文搭建了一个由三节点(master.slave1.slave2)构成的Hadoop完全分布式集群(区别单节点伪分布式集群),并通过Hadoop分布式计算的一个示例测试集群的正确性. 本文集群三 ...

  3. appium测试代码nullpoint

    今天写了个简单向上滑动,执行到向上滑动操作,报nullpoint异常,经过各种乱碰终于解决了,现记录一下过程,以备以后参考! 环境背景:java+testng+appium 在@Test下调用 dir ...

  4. 关于Makefile,Makefile.in,Makefile.am,Configure功能及相互关系的问题

    makefile写法 在 Unix 上写程式的人大概都碰过 Makefile,尤其是用 C 来开发程式的人.用 make来开发和编译程式的确很方便,可是要写出一个 Makefile就不简单了.偏偏介绍 ...

  5. Being a Good Boy in Spring Festival(尼姆博弈)

    Being a Good Boy in Spring Festival Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 ...

  6. 0_Simple__clock + 0_Simple__clock_nvrtc

    使用 clock() 函数在CUDA核函数内部进行计时,将核函数封装为PTX并在另外的代码中读取和使用. ▶ 源代码:文件内建核函数计时. #include <stdio.h> #incl ...

  7. Unity3D手机斗地主游戏开发实战(03)_地主牌显示和出牌逻辑(不定期更新中~~~)

    Hi,之前有同学说要我把源码发出来,那我就把半成品源码的链接放在每篇文件的最后,有兴趣的话可以查阅参考,有问题可以跟我私信,也可以关注我的个人公众号,互相交流嘛.当然,代码也是在不断的持续改进中~ 上 ...

  8. JavaSE高级1

    内部类 内部类概念: 所谓内部类(Inner Class),顾名思义,就是将一个类定义在另一个类的内部.内部的类称之为内部类. 内部类的主要特点: 内部类可以很好的实现隐藏,可以使用protected ...

  9. 【深度学习系列】PaddlePaddle之数据预处理

    上篇文章讲了卷积神经网络的基本知识,本来这篇文章准备继续深入讲CNN的相关知识和手写CNN,但是有很多同学跟我发邮件或私信问我关于PaddlePaddle如何读取数据.做数据预处理相关的内容.网上看的 ...

  10. DNA序列对齐问题

    问题描述: 该问题在算法导论中引申自求解两个DNA序列相似度的问题. 可以从很多角度定义两个DNA序列的相似度,其中有一种定义方法就是通过序列对齐的方式来定义其相似度. 给定两个DNA序列A和B,对齐 ...