使用不安全代码将 Bitmap 位图转为 WPF 的 ImageSource 以获得高性能和持续小的内存占用

使用不安全代码将 Bitmap 位图转为 WPF 的 ImageSource 以获得高性能和持续小的内存占用

发布于 2017-11-09 15:25 更新于 2017-11-10 06:42

在 WPF 中将一个现成的 Bitmap 位图转换成 ImageSource 用于显示一个麻烦的事儿,因为 WPF 并没有提供多少可以转过来的方法。不过产生 Bitmap 来源却非常多,比如屏幕截图、GDI 图、数组或其它非托管框架生成的图片。


WPF 官方提供了一种方法,使用 System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap() 方法。官方解释称这是托管和非托管位图相互转换所用的方法。然而此方法有一个很严重的弊端——每次都会生成全新的位图,即便每次 DeleteObject 之后,内存依然不会即时释放。

DeleteObject:

[DllImport("gdi32")]
static extern int DeleteObject(IntPtr o);

DeleteObject 的指针源于 Bitmap.GetHbitmap() 方法,且得到的指针会作为 System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap() 的参数之一。


在持续输出图像的时候(例如播放 Gif 图、持续显示屏幕截图等)不及时释放内存非常致命!为了防止重复创建图片,WriteableBitmap 似乎成了比较好的选择。

但是 WriteableBitmap 没有提供与位图 Bitmap 的互操作。然而它们都提供了像素操作。

于是,我们考虑内存拷贝来完成转换,代码如下:

public static class WriteableBitmapExtensions
{
    public static void CopyFrom(this WriteableBitmap wb, Bitmap bitmap)
    {
        if (wb == null) throw new ArgumentNullException(nameof(wb));
        if (bitmap == null) throw new ArgumentNullException(nameof(bitmap));

        var ws = wb.PixelWidth;
        var hs = wb.PixelHeight;
        var wt = bitmap.Width;
        var ht = bitmap.Height;
        if (ws != wt || hs != ht) throw new ArgumentException("暂时只支持相同尺寸图片的复制。");

        var width = ws;
        var height = hs;
        var bytes = ws * hs * wb.Format.BitsPerPixel / 8;

        var rBitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height),
            ImageLockMode.ReadOnly, bitmap.PixelFormat);

        wb.Lock();
        unsafe
        {
            Buffer.MemoryCopy(rBitmapData.Scan0.ToPointer(), wb.BackBuffer.ToPointer(), bytes, bytes);
        }
        wb.AddDirtyRect(new Int32Rect(0, 0, width, height));
        wb.Unlock();

        bitmap.UnlockBits(rBitmapData);
    }
}

我写了一个持续不断截取屏幕并输出显示的控件,在我的 The New Surface Pro 2736*1826 分辨率下内存一直保持 168M 从不变化。

这个方法的简化空间还非常大,比如,如果数据源是一个一次申请不断修改的数组,那么连 Bitmap 都可以不需要了,直接拷贝数组空间即可。我的朋友林德熙为此将这段代码简化得只剩下几行代码了:WPF 使用不安全代码快速从数组转 WriteableBitmap - 林德熙

本文会经常更新,请阅读原文: https://walterlv.com/post/convert-bitmap-to-imagesource-using-unsafe-method.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python攻城狮

爬取拉勾网招聘信息并使用xlwt存入Excel

通过浏览器自带的开发者工具查看是通过Post方式提交的,数据是通过Ajax(异步加载)得到的

9640
来自专栏玩转全栈

flutter全局数据共享通知方案

让我们先抛开Flutter这个平台说话,如果让你实现数据共享,你能想到的基础方案有哪些。

3.2K180
来自专栏jouypub

Spring Task中cron表达式详解

_{秒}:取值范围(0-59),不允许为空值,若值不合法,调度器将抛出SchedulerException异常

20910
来自专栏Windows Community

Windows Community Toolkit 4.0 - DataGrid - Overview

Windows Community Toolkit 4.0 于 2018 月 8 月初发布:Windows Community Toolkit 4.0 Rele...

9020
来自专栏大数据挖掘DT机器学习

并行爬虫和数据清洗工具(开源)

etlpy是python编写的网页数据抓取和清洗工具,核心文件etl.py不超过500行,具备如下特点 爬虫和清洗逻辑基于xml定义,不需手工编写 基于pyth...

50040
来自专栏CSDN技术头条

Java 平台反应式编程(Reactive Programming)入门

反应式编程(Reactive Programming)对有些人来说可能相对陌生一点。反应式编程是一套完整的编程体系,既有其指导思想,又有相应的框架和库的支持,并...

2.1K60
来自专栏Fundebug

JS的分号可以省掉吗?

摘要: JavaScript语言从设计之初就是考虑带分号的,使用不带分号的编码规则就要小心点啦。

17560
来自专栏守候书阁

大道至简--API设计的美学

对于前端开发而言,肯定会和API打交道,大家也都会想过怎么设计自己的API。优秀的 API 之于代码,就如良好内涵对于每个人。好的 API 不但利于使用者理解,...

18630
来自专栏IMWeb前端团队

那些年我们踩过的坑

事件背景 有一天leader给程序员cover分配了一个需求,cover一看,需求很简单嘛,就是在页面异步拉取数据展示就OK了,于是就和cgi同事阿翔对接了一下...

253100
来自专栏用户2442861的专栏

Python基础学习笔记之(一)(华工大神)

       前段时间参加微软的windows Azure云计算的一个小培训,其中Python被用的还是蛮多的。另外,一些大公司如Google(实现web爬虫...

19910

扫码关注云+社区

领取腾讯云代金券