WPF 自己封装 Skia 差量绘制控件
使用 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 差量绘制控件的更多相关文章
- 【WPF学习】第二十三章 列表控件
WPF提供了许多封装项的集合的控件,本章介绍简单的ListBox和ComboBox控件,后续哈会介绍更特殊的控件,如ListView.TreeView和ToolBar控件.所有这些控件都继承自Item ...
- 将webkit内核封装为duilib的浏览器控件
转载请说明出处,谢谢~~ 原本的duilib是自带浏览器控件的,但是使用了IE内核,我在做仿酷狗音乐播放器时,在右侧乐库要用到浏览器控件,而我使用自带的IE控件却发现了不少缺点,这也是duilib一直 ...
- 《Programming WPF》翻译 第5章 7.控件模板
原文:<Programming WPF>翻译 第5章 7.控件模板 如果仔细的看我们当前的TTT游戏,会发现Button对象并没有完全为我们工作.哪些TTT面板有内圆角? 图5-14 这里 ...
- WPF中嵌入WinForm中的webbrowser控件
原文:WPF中嵌入WinForm中的webbrowser控件 使用VS2008创建WPF应用程序,需使用webbrowser.从工具箱中添加WPF组件中的webbrowser发现其中有很多属性事件不能 ...
- WPF从我炫系列4---装饰控件的用法
这一节的讲解中,我将为大家介绍WPF装饰控件的用法,主要为大家讲解一下几个控件的用法. ScrollViewer滚动条控件 Border边框控件 ViewBox自由缩放控件 1. ScrollView ...
- WPF自定义控件(二)の重写原生控件样式模板
话外篇: 要写一个圆形控件,用Clip,重写模板,去除样式引用圆形图片可以有这三种方式. 开发过程中,我们有时候用WPF原生的控件就能实现自己的需求,但是样式.风格并不能满足我们的需求,那么我们该怎么 ...
- WPF编程,通过KeyFrame 类型制作控件线性动画的一种方法。
原文:WPF编程,通过KeyFrame 类型制作控件线性动画的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/articl ...
- Unity编辑器 - 使用GL绘制控件
Unity编辑器 - 使用GL绘制控件 控件较为复杂时,可能造成界面卡顿,在EditorGUI中也可以灵活使用GL绘制来提升性能. 以绘制线段为例: using UnityEngine; using ...
- WPF 构建无外观(Lookless)控件
原文:WPF 构建无外观(Lookless)控件 构建一个用户可以使用Template属性设置外观的WPF控件需要以下几步 1.继承自System.Windows.Controls.Control 2 ...
- WPF 实现跑马灯效果的Label控件,数据绑定方式实现
原文:WPF 实现跑马灯效果的Label控件,数据绑定方式实现 项目中需要使用数据绑定的方式实现跑马灯效果的Label,故重构了Label控件:具体代码如下 using System; using S ...
随机推荐
- 三维模型3DTile格式轻量化在数据存储的重要性分析
三维模型3DTile格式轻量化在数据存储的重要性分析 三维模型3DTile格式轻量化在数据存储中占有重要地位.随着科技的不断发展,尤其是空间信息科技的进步,人们对于三维地理空间数据的需求日益增长.然而 ...
- C#人脸对比服务(基于虹软人脸识别SDKV4.1封装)
软件截图 项目截图 部分代码 using System; using System.Collections.Generic; using System.Linq; using System.Tex ...
- RelationNet:学习目标间关系来增强特征以及去除NMS | CVPR 2018
论文基于NLP的注意力机制提出了目标关系模块,通过与其它目标的比对增强当前目标的特征,而且还可以代替NMS进行端到端的重复结果去除,思想十分新颖,效果也不错 来源:晓飞的算法工程笔记 公众号 论文 ...
- KingbaseES V8R6集群运维案例之---repmgrd进程启动无法访问共享内存
案例说明: KingbaseES V8R6集群在启动repmgrd进程时,出现'unable to write to shared memory'故障,导致repmgrd进程启动终止. 适用版本: K ...
- KingbaseES数据库运维案例之---permission denied to create "sys_catalog.xxx"
KingbaseES数据库运维案例之---permission denied to create "sys_catalog.bdsj_bdgl_test" 案例说明: 在Kin ...
- C++设计模式 - 解析器模式(Interpreter)
领域规则模式 在特定领域中,某些变化虽然频繁,但可以抽象为某种规则.这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案. 典型模式 Interpreter Interpre ...
- JWT登录认证-项目BotBattle
目录 session 授权认证原理 密码存储与加密 jwt(JSON Web Token)验证 JWT 的无状态认证机制 实践与调试 实现目标:在没有判断登录认证的情况下,访问任意界面,直接跳转到登录 ...
- #KD-Tree#洛谷 3710 方方方的数据结构
题目 区间加,区间乘,单点查询,撤销修改 分析 由于可以离线,不妨把下标看成第一维,时间看成第二维,那么修改操作相当于在一个矩形上加或者乘, 不妨把查询的节点看作是二维平面上的点,这样实际上就可以用 ...
- #差分约束系统#CodeChef Digit Matrix&洛谷 7515 [省选联考 2021 A 卷] 矩阵游戏
洛谷传送门 DGMATRIX 分析 先任意构造出一个不一定满足值域的矩阵,现在只需要满足值域就可以了. 可以发现,给一行或一列依次加一减一2*2矩阵的和仍然不变,并且如果有解一定能构造出一组方案. 因 ...
- 12个月大厂主机免费领AWS Azure Google-Cloud还不快到碗里来
目录 简介 AWS Azure Google Cloud Oracle 总结 简介 最近有个朋友问我哪里有免费主机可以领,说实话这个问题也困扰了我很久,之前也在网上寻找免费主机,可是免费的基本上都有一 ...