title author date CreateTime categories
WPF 使用不安全代码快速从数组转 WriteableBitmap
lindexi
2018-08-10 19:16:53 +0800
2018-2-13 17:23:3 +0800
WPF

本文告诉大家一个快速的方法,直接把数组转 WriteableBitmap

先来说下以前的方法,以前使用的是 BitmapSource ,这个方法是大法官方提供的。

BitmapSource.Create(LogicalWidth, LogicalHeight, 96, 96, PixelFormats.Bgra32, null,
dest,
stride);

其中 dest 是一个大数组,数据大小为 $$ LogicalWidthLogicalHeight4 $$ ,经常在转换的时候出现内存不足异常。假如现在内存占用是 1.5G ,转换的图片大小是 2000*2000 ,于是几乎一跑就出现内存不足。

为何还有 500 M 内存却出现内存不足?因为图片转换需要的是一段大的连续内存空间。砸桌子,再说一次,图片转换需要一段【大的连续】内存空间,虽然有500M内存,但是连续的空间没有那么多。

这就是以前方法的缺点。

使用不安全代码转换是把数组直接复制到WriteableBitmap,请看使用不安全代码将 Bitmap 位图转为 WPF 的 ImageSource 以获得高性能和持续小的内存占用 - walterlv,这里讲了如何从 Bitmap 转 WriteableBitmap ,于是下面只需要把数组转 Bitmap 就可以了。

我在最后会给大家全部代码,所以现在讲原理。

如果已经拿到了数组,知道数组的存放,那么就可以直接把数组复制到 WriteableBitmap 就可以显示。数组的存放就是数组是如何放数据的,是不是还在想,上面的 dest 是一个大数组,他的计算是
$$ LogicalWidthLogicalHeight4 $$ 为什么是*4,因为存放的数据是 A R B G 一个点需要4个int来放。那么放的顺序是什么?这就是PixelFormat指定的类型,可以使用Bgra32或者其他的格式,不过指定了格式就需要数组存放和指定一样

因为没有直接从数组转 WriteableBitmap 所以需要先把数组转 Bitmap ,可以使用的方法请看下面

                unsafe
{
fixed (int* ptr = _dest)
{ try
{
using (Bitmap image = new Bitmap(LogicalWidth, LogicalHeight, LogicalWidth * 4,
PixelFormat.Format16bppArgb1555, new IntPtr(ptr)))
{ }
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}

Bitmap 的数据类型可以是任意,因为只是把他的数据转换到 WriteableBitmap 所以不需要指定他的数据

获得 Bitmap 就可以把他转 WriteableBitmap ,请看下面的代码

                unsafe
{
fixed (int* ptr = _dest)
{ try
{
using (Bitmap image = new Bitmap(LogicalWidth, LogicalHeight, LogicalWidth * 4,
PixelFormat.Format16bppArgb1555, new IntPtr(ptr)))
{
CopyFrom(source,image);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}

代码的 CopyFrom 就是吕毅提供的方法。

使用这个函数更新,不需要在更新了修改 Image 的 Source 因为会自动更新,用这个方法播放 gif 的性能比找到的Magick.NET库的性能都好。

对比一下性能,这时原先的 BitmapSource 方法占用内存

这是使用不安全代码占用内存

实际跑一张 gif 图的性能

可以看到这个方法可以节省很多的内存,而且占用的 cpu 很低,因为没有很多gc

但是不要太高兴,因为不安全代码的exception是接不住的,下面请修改一下代码,让他输入错误,于是就出现异常,结果程序就关了。

所以使用这个方法还是很大的坑。

全部的代码:

           Application.Current?.Dispatcher.BeginInvoke((Action) (() =>
{
unsafe
{
fixed (int* ptr = _dest)
{ try
{
using (Bitmap image = new Bitmap(LogicalWidth, LogicalHeight, LogicalWidth * 4,
PixelFormat.Format16bppArgb1555, new IntPtr(ptr)))
{
CopyFrom(_source, image);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
})); private void UpdateSource()
{
Application.Current?.Dispatcher.Invoke(() =>
{
_source = new WriteableBitmap(LogicalWidth, LogicalHeight, 96, 96,
System.Windows.Media.PixelFormats.Bgra32, null);
});
} public static void CopyFrom(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);
}

我把代码给小伙伴看,他说可以直接从数组转 WriteableBitmap ,我使用他的想法,修改了程序,请看代码

              unsafe
{
fixed (int* ptr = _dest)
{
_source.Lock(); var bytes = LogicalWidth * LogicalHeight * _source.Format.BitsPerPixel / 8; Buffer.MemoryCopy(ptr,_source.BackBuffer.ToPointer(), bytes, bytes); _source.AddDirtyRect(new Int32Rect(0, 0, LogicalWidth, LogicalHeight));
_source.Unlock();
}
}

实际上微软已经提供了不安全代码的转换,请看下面代码

bitmapImage.WritePixels(new Int32Rect(0, 0, 宽度, 高度), 图片数据, stride, 0)

stride 一般就是 4*宽度 因为一个像素使用4个byte

2018-8-10-WPF-使用不安全代码快速从数组转-WriteableBitmap的更多相关文章

  1. WPF 使用不安全代码快速从数组转 WriteableBitmap

    原文:WPF 使用不安全代码快速从数组转 WriteableBitmap 本文告诉大家一个快速的方法,直接把数组转 WriteableBitmap 先来说下以前的方法,以前使用的是 BitmapSou ...

  2. 01 mybatis框架整体概况(2018.7.10)-

    01 mybatis框架整体概况(2018.7.10)- F:\廖雪峰 JavaEE 企业级分布式高级架构师课程\廖雪峰JavaEE一期\第一课(2018.7.10) maven用的是3.39的版本 ...

  3. 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H)

    目录 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H) 竞赛事件相关 竞赛链接 竞赛题目 总结 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H) 竞赛事件相关 竞赛 ...

  4. Burn Down Chart(2018.6.4~2018.6.10)

    Burn Down Chart (2018.6.4~2018.6.10) 娄雨禛[前端部分] 曾子轩[后端部分+燃尽图] 前端 1. 娄雨禛+李鑫 1)在总工程中完成跳转,实现图片显示,并发布到Git ...

  5. 申请Office 365一年免费的开发者账号攻略(2018年10月份版本)

    要进行Office 365开发,当然需要有完整的Office 365环境才可以.为了便于广大开发人员快速地启动这项工作,微软官方给所有开发人员提供了免费的一年开发者账号   那么如何申请Office ...

  6. IntelliJ IDEA 最新激活码(截止到2018年10月14日)

    IntelliJ IDEA 注册码: EB101IWSWD-eyJsaWNlbnNlSWQiOiJFQjEwMUlXU1dEIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYX ...

  7. 新手C#SQL Server使用记录2018.08.10

    主键(PrimaryKey):主键就是每个数据行(记录)的唯一标识,不会有重复值的列(字段)才能当做主键.一个表可以没有主键,但是这样会很难处理表,因此一般情况表都要设置主键. 主键有两张选用策略,分 ...

  8. c#字符串加载wpf控件模板代码 - 简书

    原文:c#字符串加载wpf控件模板代码 - 简书 ResourceManager resManagerA = new ResourceManager("cn.qssq666.Properti ...

  9. WPF Grid 用 C# 代码后台设置

    WPF Grid 用 C# 代码后台设置 运行环境:Window7 64bit,.NetFramework4.61,C# 6.0: 编者:乌龙哈里 2017-02-21 参考: System.Wind ...

随机推荐

  1. vue使用axios实现前后端通信

    安装依赖 npm install --save axios # vue-axios是对axios的简单封装 npm install --save vue-axios 用例 在main.js里面进行全局 ...

  2. 多重背包 /// 单调队列DP oj1943

    题目大意: em.... 就是多重背包 挑战340页的东西 ...自己的笔记总结总是比较乱的 重点:原始的状态转移方程中 更新第 i 种物品时 重量%w[i] 的值不同 则它们之间是相互独立的: 1- ...

  3. vue表格之tableHeaderColor(修改表头背景色)

    <el-table :header-cell-style="tableHeaderColor"></el-table> // 更改表头样式 tableHea ...

  4. 使用 /proc 文件系统

    /proc 文件系统是一个特殊的软件创建的文件系统, 内核用来输出消息到外界. /proc 下 的每个文件都绑到一个内核函数上, 当文件被读的时候即时产生文件内容. 我们已经见到 一些这样的文件起作用 ...

  5. 锋利的Jquery(p的onclick()事件)

    1.一个p元素的点击事件 <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="serve ...

  6. vue之vant组件下拉加载更多

    vant地址:https://youzan.github.io/vant/#/zh-CN/intro 基础用法 List 组件通过loading和finished两个变量控制加载状态,当组件滚动到底部 ...

  7. SpringBoot学习笔记(三):SpringBoot集成Mybatis、SpringBoot事务管理、SpringBoot多数据源

    SpringBoot集成Mybatis 第一步我们需要在pom.xml里面引入mybatis相关的jar包 <dependency> <groupId>org.mybatis. ...

  8. Synchronized理解及用法

    加锁: 1.同步实例方法,锁是当前实例对象 2.同步类方法,锁的是当前类对象 3.同步代码块,锁是括号里面的对象 原理: JVM内置锁通过synchronized使用,通过内部对象Monitor(监视 ...

  9. Python实例1-Collatz 序列

    编写一个名为 collatz()的函数,它有一个名为 number 的参数.如果参数是偶数,那么 collatz()就打印出 number // 2, 并返回该值.如果 number 是奇数, col ...

  10. 安装office2019

    win10系统安装office2019 安装文件下载 https://pan.baidu.com/s/1VnqJ-hNwysPKBhdzE3FSww#list/path=%2F&parentP ...