从MemoryStream生成ImageSource的最佳实践

  • 需求
  • 思路
  • 实现
  • 小结
  • 参考

好久没有写博客了,今天刚好清明节假期,闲来无事,把最近项目中优化的一个点总结一下。

需求

最近做的项目,需要增加表情功能,需要加载近4000张表情图,供用户使用。项目使用的是WPF框架和MVVM设计模式。
浏览图片功能使用的是ListBox控件,每个ListBoxItem使用Image重写的ControlTemplate。
Image的Source属性是ImageSource类型,通常在图片少的时候通过绑定图片的路径来加载并显示图片。但现在有4000张图片,一张张的通过这种方式来显示,效率是一个很大的问题,在浏览的时候,用户很容感受到卡顿。
那么在实现功能的同时,怎样做到加载和显示流畅不卡顿?

思路

一个通常的方案就是先读取图片到内存中,然后把内存中的数据转换成ImageSource类型,然后把转换后的数据再直接与Image的Source属性进行绑定。通过实践,找到了两个方案,并对这两种方案做了对比和进一步的优化。

实现

我们先通过后台任务在软件启动的时候,把图片加载到内存中,存储为MemoryStream类型,再从MemoryStream生成ImageSource类型的对象。从MemoryStream生成ImageSource类型的对象,有两种方式:

  1. 我们知道BitmapImage是ImageSource类型的子类,可以直接MemoryStream转换为BitmapImage对象,然后把BitmapImage对象再绑定到Image的Source属性。
public static BitmapImage ToBitmapImage(MemoryStream stream)
{try{var bitmapImage = new BitmapImage();bitmapImage.BeginInit();bitmapImage.CacheOption = BitmapCacheOption.OnLoad;bitmapImage.StreamSource = stream;bitmapImage.EndInit();return bitmapImage;}catch(Exception ex){return null;}
}

这种方法可行,效率比直接绑定路径的方式要好,一旦全部转换成BitmapImage对象,后面在用户浏览图片的时候,一点都不卡顿,用户体验提高了很多。但是,还有一个问题,4000张图片在从MemoryStream转换到BitmapImage对象,大概用时要2秒多。好像还可以接受。但是生成BitmapImage对象只能在UI线程中进行,这2秒多的转化过程也势必会造成UI界面的卡顿。如果在其他线程中生成BitmapImage对象后再去与Image的Source属性绑定的话,程序就会报异常。

  1. 我们需要再对其进行优化,通过搜索我们还找到了另外一种方法可以从MemoryStream转换到BitmapSource。是基于所提供的非托管位图和调色板信息的指针,返回一个托管的
    BitmapSource。BitmapSource是ImageSource的子类。可以直接绑定到Image的Source属性。
public static ImageSource ToImageSource(MemoryStream stream)
{var bitmap = new Bitmap(stream);return Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}

同样,BitmapSource也只能在UI线程中创建,否则也会报异常。由于经过测试,这种方式转换4000张图片的效率大大提升了。从上个方法的2秒多降低到了大概1秒钟,效率提升了100%。这样在软件的启动过程中,用户也只是感受到一点点的卡顿,整体还可以接受。
但是,我们还有没有可以优化的地方?让界面一点都不卡顿。由于ImageSource对象的创建只能在主线程中进行,这块的代码无法挪到其他线程中进行。通过上面的代码,我们发现从MemorySteam转换到ImageSource经过了三个过程:
1)从MemorySteam生成Bitmap对象;
2)通过Bitmap对象调用GetHbitmap()方法获取非托管的指针;
3)从非托管的指针通过调用Imaging.CreateBitmapSourceFromHBitmap()函数转换到所需的ImageSource类型的对象。
那我们是不是可以把上面的三个步骤拆开?把1)和2)放到其他线程中,在内存里保存转换所需要的指针即可。把3)仍然放在UI线程中。
经过这样的改造,我们测试,在UI线程中转换到所需的ImageSource对象使用的时间大约90ms左右。这样几乎不影响在启动过程中UI的流畅性。

小结

经过不断的尝试和优化,最终的方案还是比较令人满意的。把优化的过程记录下来,防止忘记,也方便其他小伙伴。如有更好的方案,欢迎在留言区讨论。谢谢!
附最后的成品效果:

参考

  • https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.imaging.bitmapimage?view=net-5.0
  • https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.interop.imaging.createbitmapsourcefromhbitmap?view=net-5.0

从MemoryStream生成ImageSource的最佳实践相关推荐

  1. jumbo 安装mysql,RAC 和 Oracle Clusterware 最佳实践和初学者指南「ID 1526083.1」

    In this Document Applies to: Oracle Database - Enterprise Edition - Version 10.2.0.1 to 11.2.0.3 [Re ...

  2. RAC 和 Oracle Clusterware 最佳实践和初学者指南(平台无关部分) (文档 ID 1526083.1)

      RAC 和 Oracle Clusterware 最佳实践和初学者指南(平台无关部分) (文档 ID 1526083.1) 转到底部 文档内容 用途   适用范围   详细信息   RAC 平台特 ...

  3. 短信验证码“最佳实践”

    1.背景 年初,从外地转移阵地到西安,转眼已两个多月.很久不写业务代码了,到了新公司,条件恶劣到前所未有,从需求,设计,架构,实现,实施,测试,bug修复,项目计划制定,项目管理,全他妈我一个人,关键 ...

  4. Java实现短信验证码最佳实践

    1.背景 2.实现 3.运行效果: 4.源码 5.总结 1.背景 年初,从外地转移阵地到西安,转眼已两个多月.很久不写业务代码了,到了新公司,条件恶劣到前所未有,从需求,设计,架构,实现,实施,测试, ...

  5. 编写高性能Java代码的最佳实践

    编写高性能Java代码的最佳实践 摘要:本文首先介绍了负载测试.基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践.最后研究了JVM特定的调优技巧.数据库端的优化和架 ...

  6. 提示和技巧:光线跟踪最佳实践

    提示和技巧:光线跟踪最佳实践 Tips and Tricks: Ray Tracing Best Practices 本文介绍了在游戏和其他实时图形应用程序中实现光线跟踪的最佳实践.我们尽可能简短地介 ...

  7. 使用ADO.NET 的最佳实践(zz)

    数据访问:使用 ADO.NET 的最佳实践(ADO.NET 技术文档) 发布日期: 4/1/2004 | 更新日期: 4/1/2004 摘要:编写 Microsoft ADO.NET 代码的最佳实践, ...

  8. 何崚谈阿里巴巴前端性能优化最佳实践

    转载:http://www.infoq.com/cn/interviews/hl-alibaba-front-end-performance-optimization 大家好,我现在在阿里巴巴园区采访 ...

  9. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...

最新文章

  1. 夯实基础,彻底掌握js的核心技术(四):ES5、ES6对象方法详解
  2. be2014备份oracle,veritas be 对oracle10g 备份异机恢复测试
  3. SQL Server 日期格式
  4. GeoServer服务器环境的搭建
  5. 30分钟学会用scikit-learn的基本回归方法(线性、决策树、SVM、KNN)和集成方法(随机森林,Adaboost和GBRT)
  6. oracle忘记sys密码处理
  7. 一个Python开源项目-哈勃沙箱源码剖析(下)
  8. 今日头条推荐算法原理 - 梳理
  9. 手把手教你做线性回归分析,实用且通俗易懂!
  10. date linux 计算日期,科技常识:linux命令详解date使用方法(计算母亲节和父亲节日期脚本示例)...
  11. 标准的html 样式,CSS 样式表代码规则
  12. mysql 字段被截断_msyql存储数据时字段被截断
  13. 如何在Photoshop中更改某人的眼睛颜色
  14. IE、360、百度三者的比较
  15. 【爬坑】解决“ImportError: cannot import name ‘soft_unicode‘ from ‘markupsafe‘ ”的问题
  16. D3.js v5.0 旭日图
  17. flutter CustomPainter 简单绘制 三角形 多边形
  18. windows验证和SQLSERVER验证有什么区别?--混合认证
  19. Qt 绘制拖动刻度尺
  20. MultiValueMap

热门文章

  1. 半年时间如何高效准备计算机保研机试?
  2. 表格列宽width手动设置不生效,内联也没用
  3. C# 开源游戏服务器框架
  4. python获取当前时间戳_python 获取当前时间戳
  5. mu4e 快捷键组合和操作附件
  6. 2023届研究生 计算机保研 浙软经验帖
  7. 把显示器挂在机箱侧面板上_电脑机箱侧面的弹性弹簧卡扣有哪些用途?
  8. 有哪些gif动画制作软件比较好用?
  9. 股票入门基础知识2:如何购买和卖出股份?
  10. 智能单测用例生成工具 Evosuite