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. 65、django之模型层(model)--添加、单表查询、修改基础

    上篇带大家简单做了一下图书表的创建.简单的查看和删除,今天会先简单介绍添加和修改,因为添加和修改与删除一样都很简单,本篇会相对多介绍一点单表查询,大家都知道数据库中查询是最重要的一部分,毕竟无论是修改 ...

  2. [PGM] Bayes Network and Conditional Independence

    2 - 1 - Semantics & Factorization 2 - 2 - Reasoning Patterns 2 - 3 - Flow of Probabilistic Influ ...

  3. 初学者Web介绍一些前端开发中的基本概念用到的技术

    Web开发是比较费神的,需要掌握很多很多的东西,特别是从事前端开发的朋友,需要通十行才行.今天,本文向初学者介绍一些Web开发中的基本概念和用到的技术,从A到Z总共26项,每项对应一个概念或者技术. ...

  4. DOM Exception error 类型

    INDEX_SIZE_ERR  code 1                              索引是负值,或者超过了索引值 DOMSTRING_SIZE_ERR  code 2       ...

  5. Task及Mvc的异步控制器 使用探索

    微软的Task已经出来很久了,一直没有去研究,以为就是和Thread差不多的东西.直到最近看到了Task的使用介绍,发现比Thread的语法要精炼多了,于是便在项目中用上了. 结果就出问题了,数据库连 ...

  6. JavaScript系列----面向对象的JavaScript(1)

    1.面向对象的编程 1.1.什么是面向对象编程 面向对象编程:即是把能够完成独立完成一部分功能的代码封装在一起,组成一个类. 举个例子来说: 这里有一把枪, 枪的种类很多,有步枪,机关枪,阻击枪... ...

  7. ldap数据库--ODSEE--suffix

    ldap数据库的suffix是建立ldap之间复制协议的基础,suffix的创建也可以通过管理界面进行,也可以通过命令行进行.不同点是通过管理界面创建的suffix会自动创建一条对应该suffix的匿 ...

  8. 基于QEMU的ARM Cortex-A9开发板Vexpress-ca9的Linux内核的编译和运行

    宿主机:Ubuntu16.04 x64(Linux内核4.4.0) 交叉编译工具链:gcc-arm-linux-gnueabiarm-linux-gcc:4.4.3QEMU:2.5.0Linux ke ...

  9. Java 核心内容相关面试题【4】

    spingmvc 和 structs的区别 我们用struts2时采用的传统的配置文件的方式,并没有使用传说中的0配置. spring3 mvc可以认为已经100%零配置了(除了配置spring mv ...

  10. 《天书夜读:从汇编语言到windows内核编程》四 windows内核调试环境搭建

    1) 基础篇是讲理论的,先跳过去,看不到代码运行的效果要去记代码是一个痛苦的事情.这里先跳入探索篇.其实今天的确也很痛苦,这作者对驱动开发的编译与调试环境介绍得太模糊了,我是各种尝试,对这个环境的搭建 ...