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 为 96float 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 开发,取得手机萤幕大小两种方法. 原文:C# Windows Phone 8 WP8 开发,取得手机萤幕大小两种方法. 一般我们在开发Windows Ph ...

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

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

  3. html5保存到桌面,win8.1将IE浏览器网页内容保存到电脑桌面的两种方法

    win8.1系统使用IE浏览器浏览网页的时候,看到有用的网页内容,想要保存下来,这样即使在没有联网的情况下也能够打开该网页查看.那么win8.1将IE浏览器网页内容保存到电脑桌面呢?下面小编介绍两种方 ...

  4. 开发板通过usb连linux,虚拟机Linux系统和开发板通过USB转串口连接的两种方法

    通过USB转串口连接虚拟机中的Linux系统和开发板,开始时候总是出现错误,显示什么串口重叠,最后求助高手帮我通过其他连接方式解决了问题,今天在网上查找资料,终于明白了开始错误的原因:开发板提供的驱动 ...

  5. 画板数据保存成文件的两种方法小结

    前面我学习了绘制画板以及画板的重绘,最近学习将画板数据保存成文件,与大家分享下.关于画板的保存有两种方式.先说说用队列来保存文件吧:首先需要将重绘时用来保存对象的队列按我们定义的文件格式写入文件: / ...

  6. MATLAB笔记:打开数据文件的三种方法+读取数据文件的两种方法+保存数据文件的两种方法

    1.打开数据文件 1.1 直接打开文件 PATHNAME = 'C:\Users\s55\Desktop\dat'; FILENAME = '\data_1.dat'; str0=strcat(PAT ...

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

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

  8. Markdown pad2 保存为pdf的两种方法

    法一:使用专业版Markdown pad2 激活方式详见我的另一篇博客 MarkdownPad2注册码--亲测有效 具体方法:打开MarkdownPad2--文件--导出--Export PDF 法二 ...

  9. 【Win 10 应用开发】Toast通知激活应用——前台后台

    原文:[Win 10 应用开发]Toast通知激活应用--前台&后台 老周最近热衷于讲故事,接下来还是讲故事时间. 有人问我:你上大学的时候,有加入过学生会吗?读大学有没有必要加入学生会? 哎 ...

最新文章

  1. 干货 | 金融级消息队列的演进 — 蚂蚁金服的实践之路
  2. Tomcat 配置和spring-framework MVC配置简介
  3. art-template入门(五)之模板变量
  4. 简自动类型提升,精度损失类型强制转换,常用转义字符,简单帮你回顾Java基本数据类型整形浮点型字符型布尔型Boolean及其运算规则
  5. Github+docsify打造在线文档网站
  6. arp协议、arp应答出现的原因、arp应答过程、豁免ARP详细解答附图(建议电脑观看)
  7. dakai微信小程序 ios_iOS APP拉起微信小程序
  8. Martin Odersky Scala编程公开课 第三周作业
  9. 在两个林间做Exchange邮箱的迁移
  10. bugzilla发送邮件慢的问题
  11. easyui 提示框组件_jQuery EasyUI 教程-Tooltip(提示框)
  12. QQ文件中转站 发送给好友的功能 哪去了?
  13. DART语言学习整理
  14. 接收的参数为日期类型、controller控制层进行数据保存、进行重定向跳转
  15. 台式计算机连网需要什么,台式电脑无线网络连接需要什么设备
  16. PS 如何去除光晕的黑色背景
  17. mysql三叶草,温州日报瓯网 - 面对温州话,你被困住了吗?
  18. 数图互通高校房产管理模块,公租房管理是怎样对合同、续租,申请审核审批管理的;
  19. 名帖295 张瑞图 行书《行书帖选》
  20. ▷Scratch课堂丨【编程趣味卡3】制作音乐

热门文章

  1. 李开复对话彭特兰:AI 不是单打独斗,应避免 AI 冷战!
  2. Space X和NASA到底有什么关系?
  3. AR普及后的未来是怎样的?
  4. 全文!《2018中国IoT报告》!
  5. 机器人时代的资本主义:21世纪的工作,收入和财富
  6. MIT:大脑如何跟踪运动中的物体?
  7. 技术面试 vs 实际岗位 | 每日趣闻
  8. 迷惑行为赏析:把 jpg 图片加密后卖了 247 万
  9. 拿大厂机器学习岗 offer,吐血整理的面试秘籍!
  10. 吃透这套架构体系,三年成为架构师!