使用 Skia 能做到在多个不同的平台使用相同的一套 API 绘制出相同界面效果的图片,可以将图片绘制到应用程序的渲染显示里面。在 WPF 中最稳的方法就是通过 WriteableBitmap 作为承载绘制。本文告诉大家如何封装一个支持差量绘制的控件,默认的绘制方法都是每次都是不保存上次绘制的内容,而且清空画布,重新绘制。这样的绘制方法显然效率不够高

在上一篇博客里面告诉大家如何在 WPF 中使用 Skia 绘制,请看 WPF 使用 Skia 绘制 WriteableBitmap 图片

而这样的绘制方式意味着每次都需要重新绘制画布,而不能在原有上一次绘制的基础上绘制新的内容。其实在 Skia 的 SKSurface 是不需要每次绘制完成就释放,可以保存他的值

只是需要注意和 WriteableBitmap 图片一起使用时,需要在绘制之前调用 Lock 方法,在绘制完成之后调用 Unlock 方法

此时就可以实现在相同的 SKSurface 上重复上次绘制的内容。而如果能了解绘制的界面范围的话,可以使用 WriteableBitmap 的 AddDirtyRect 方法,通过这个方法可以让 WPF 层仅更新指定范围的内容

虽然 Skia 和 WPF 两个的绘制效率都很高,但是在 WriteableBitmap 里面一定存在内存和显存的拷贝,这部分虽然在 DirtyRect 很小的时候几乎不耗性能,但是如果是在 4k 下完全重新绘制,还是稍微有点伤的。只是稍微有点

在使用 WriteableBitmap 作为 Skia 的承载,就需要再来一步,让 WriteableBitmap 在界面绘制。在 WPF 中最简单的绘制 WriteableBitmap 的方法就是使用 Image 控件了

下面写一个继承 Image 控件的 SkiaCanvas 控件

这个控件十分简单,在 Loaded 事件里面将会创建 WriteableBitmap 和 SKSurface 两个字段,请看代码

    public class SkiaCanvas : Image
{
public SkiaCanvas()
{
Loaded += SkiaCanvas_Loaded;
} private void SkiaCanvas_Loaded(object sender, RoutedEventArgs e)
{
var writeableBitmap = new WriteableBitmap(PixelWidth, PixelHeight, 96, 96, PixelFormats.Bgra32,
BitmapPalettes.Halftone256Transparent); _writeableBitmap = writeableBitmap; var skImageInfo = new SKImageInfo()
{
Width = PixelWidth,
Height = PixelHeight,
ColorType = SKColorType.Bgra8888,
AlphaType = SKAlphaType.Premul,
ColorSpace = SKColorSpace.CreateSrgb()
}; SKSurface surface = SKSurface.Create(skImageInfo, writeableBitmap.BackBuffer);
_skSurface = surface; Source = writeableBitmap;
} private WriteableBitmap _writeableBitmap = null!; // 这里的 null! 是 C# 的新语法,是给智能分析用的,表示这个字段在使用的时候不会为空
private SKSurface _skSurface = null!; // 实际上 null! 的含义是我明确给他一个空值,也就是说如果是空也是预期的 public int PixelWidth => (int) Width;
public int PixelHeight => (int) Height;
}

也就是说在使用 SkiaCanvas 控件的时候,需要先设置他的宽度和高度,也不支持后续更改哈

在创建完成了 SKSurface 字段,就可以通过调用他的绘制方法在 WriteableBitmap 上绘制内容。不过在绘制之前需要调用 Lock 等方法,在输入绘制命令完成之后需要调用更新的代码,这部分代码可以封装一个方法

        public void Draw(Action<SKCanvas> action)
{
Draw(canvas =>
{
action(canvas);
return null;
});
} public void Draw(Func<SKCanvas, Int32Rect?> draw)
{
var writeableBitmap = _writeableBitmap;
writeableBitmap.Lock(); var canvas = _skSurface.Canvas;
var dirtyRect = draw(canvas);
canvas.Flush(); dirtyRect ??= new Int32Rect(0, 0, PixelWidth, PixelHeight); writeableBitmap.AddDirtyRect(dirtyRect.Value);
writeableBitmap.Unlock();
}

也就是调用 Draw 方法,传入具体的绘制逻辑就可以完成绘制了。这部分的绘制逻辑有一个优势在于不需要等待绘制时机,随时都可以进行绘制。而 WPF 将会在框架层的绘制命令收集时自动更新和收集。或者换句话说,这里的绘制逻辑有坑在于不能做到对准界面更新

上面这个方法是提供差量更新的,也就是每次绘制的内容都会在上一次画布的基础上继续绘制

下面写一点代码试试,在鼠标划过应用时,绘制出鼠标划过的点,将这些点连为线

如果没有差量更新,也就是需要咱自己去存放记录之前鼠标划过哪些点,在有差量更新的辅助就可以只记录上一次的一个点

在 XAML 代码添加如下代码

    <Grid MouseMove="UIElement_OnMouseMove">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<local:SkiaCanvas x:Name="Image" Width="1920" Height="1080" Margin="10,10,10,10"></local:SkiaCanvas>
</Grid>

上面代码给 SkiaCanvas 一个固定的宽度和高度,为什么需要给他这个值,在上文告诉了大家

接下来在 UIElement_OnMouseMove 方法,也就是 Grid 容器收到的鼠标划过的事件,将划过的点作为线段在画布中

        private void Draw(Action<SKCanvas> action)
{
Image.Draw(action);
} private void UIElement_OnMouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this); Draw(canvas =>
{
using var skPaint = new SKPaint() {Color = new SKColor(0, 0, 0), TextSize = 100};
canvas.DrawLine(new SKPoint((float) _lastPosition.X, (float) _lastPosition.Y),
new SKPoint((float) position.X, (float) position.Y), skPaint);
}); _lastPosition = position;
} private Point _lastPosition = new Point(0, 0);

可以看到逻辑十分简单,如果我需要让画布重新绘制,可以调用 Clear 方法,请看代码

        private void Button_OnClick(object sender, RoutedEventArgs e)
{
Draw(canvas =>
{
canvas.Clear();
using var skPaint = new SKPaint() {Color = new SKColor(0, 0, 0), TextSize = 100};
canvas.DrawLine(10, 10, 100, 100, skPaint);
});
}

因此这个控件就支持重新绘制和差量更新绘制内容的功能

如果每次都能返回具体更新的范围,那么这个控件的绘制效率还是不错的

本文的代码放在 github 欢迎小伙伴访问

更多 WPF 渲染请看 渲染相关

WPF 自己封装 Skia 差量绘制控件的更多相关文章

  1. 【WPF学习】第二十三章 列表控件

    WPF提供了许多封装项的集合的控件,本章介绍简单的ListBox和ComboBox控件,后续哈会介绍更特殊的控件,如ListView.TreeView和ToolBar控件.所有这些控件都继承自Item ...

  2. 将webkit内核封装为duilib的浏览器控件

    转载请说明出处,谢谢~~ 原本的duilib是自带浏览器控件的,但是使用了IE内核,我在做仿酷狗音乐播放器时,在右侧乐库要用到浏览器控件,而我使用自带的IE控件却发现了不少缺点,这也是duilib一直 ...

  3. 《Programming WPF》翻译 第5章 7.控件模板

    原文:<Programming WPF>翻译 第5章 7.控件模板 如果仔细的看我们当前的TTT游戏,会发现Button对象并没有完全为我们工作.哪些TTT面板有内圆角? 图5-14 这里 ...

  4. WPF中嵌入WinForm中的webbrowser控件

    原文:WPF中嵌入WinForm中的webbrowser控件 使用VS2008创建WPF应用程序,需使用webbrowser.从工具箱中添加WPF组件中的webbrowser发现其中有很多属性事件不能 ...

  5. WPF从我炫系列4---装饰控件的用法

    这一节的讲解中,我将为大家介绍WPF装饰控件的用法,主要为大家讲解一下几个控件的用法. ScrollViewer滚动条控件 Border边框控件 ViewBox自由缩放控件 1. ScrollView ...

  6. WPF自定义控件(二)の重写原生控件样式模板

    话外篇: 要写一个圆形控件,用Clip,重写模板,去除样式引用圆形图片可以有这三种方式. 开发过程中,我们有时候用WPF原生的控件就能实现自己的需求,但是样式.风格并不能满足我们的需求,那么我们该怎么 ...

  7. WPF编程,通过KeyFrame 类型制作控件线性动画的一种方法。

    原文:WPF编程,通过KeyFrame 类型制作控件线性动画的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/articl ...

  8. Unity编辑器 - 使用GL绘制控件

    Unity编辑器 - 使用GL绘制控件 控件较为复杂时,可能造成界面卡顿,在EditorGUI中也可以灵活使用GL绘制来提升性能. 以绘制线段为例: using UnityEngine; using ...

  9. WPF 构建无外观(Lookless)控件

    原文:WPF 构建无外观(Lookless)控件 构建一个用户可以使用Template属性设置外观的WPF控件需要以下几步 1.继承自System.Windows.Controls.Control 2 ...

  10. WPF 实现跑马灯效果的Label控件,数据绑定方式实现

    原文:WPF 实现跑马灯效果的Label控件,数据绑定方式实现 项目中需要使用数据绑定的方式实现跑马灯效果的Label,故重构了Label控件:具体代码如下 using System; using S ...

随机推荐

  1. 懂一点前端—Vue快速入门

    01. 什么是 Vue Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架,是当下很火的一个 JavaScript MVVM 库,是以 数据驱动和组件化 的思想构建的 ...

  2. KingbaseES查找慢查询语句和阻塞会话

    在处理数据库性能问题时,识别和分析慢查询及阻塞会话是至关重要的步骤.数据库管理员和开发人员常常需要依赖特定的工具和查询语句来追踪这些性能瓶颈. 当数据库响应变慢或出现处理延迟时,第一步通常是查找那些执 ...

  3. Android为按钮Button添加事件

    匿名内部类 1 <!--匿名内部类方式--> 2 <Button 3 android:id="@+id/btn2" 4 android:layout_width= ...

  4. echarts 中国地图tooltip实现提示框

    point: 鼠标位置,如 [20, 40]. params: 同 formatter 的参数相同. dom: tooltip 的 dom 对象. rect: 只有鼠标在图形上时有效,是一个用x, y ...

  5. Python企业面试题1 —— 基础篇

    1. b.B.KB.MB.GB的关系? b ---- 位(bit) B ---- 字节(一个字节等于8位) 1 B = 8 bit 1 KB = 1024 B 1 MB = 1024 KB 1 GB ...

  6. #左偏树,树形dp#洛谷 1552 [APIO2012]派遣

    题目 分析 那我指定管理层之后,选择薪水越小的人越好, 考虑小根堆,由于需要合并,所以采用左偏树 代码 #include <cstdio> #include <cctype> ...

  7. HarmonyOS SDK开放能力,服务鸿蒙生态建设,打造优质应用体验

    华为开发者大会2023(HDC.Together)于8月4日至6日在东莞松山湖举行,在HarmonyOS端云开放能力技术分论坛上,华为为广大开发者们介绍了HarmonyOS SDK开放能力在基础开发架 ...

  8. HMS Core分析服务智能运营,“智能时机”上线,轻松提升Push点击

    对于运营者来说,消息推送一直是提升用户活跃与转化的重要工具,如何在提升转化的情况下,同时不降低用户的接受程度,这一直是运营不断追求的目标. 好的推送不只在于优质的推送内容,还需要把握合适的时机.在合适 ...

  9. django admin后台自定义数据保存方式

    故事背景是这样的: 为了方便工作中数据的整理,需要开发一个 管理系统,用于记录一些事情. 该系统不需要精美的前端的页面,只需要使用django的admin后台管理就可以了. 我需要在添加数据的时候,把 ...

  10. 活动开启 | 以梦筑码 · 不负韶华 开发者故事征集令,讲出你的故事,有机会参加HDC.Together 2023

      HarmonyOS面世以来,经历了3大版本迭代,系统能力逐步完善,生态加速繁荣.一路前行,是开发者们点亮漫天星光.点滴贡献,聚沙成塔,开发者们正用代码改变世界. 是梦想,激励我们一路前行.在黎明到 ...