【WPF】使用 WriteableBitmap 提升 Image 性能

这篇具有很好参考价值的文章主要介绍了【WPF】使用 WriteableBitmap 提升 Image 性能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

由于中所周不知的原因,WPF 中想要快速的更新图像的显示速率一直以来都是一大难题。在本文中,我将分享一些我对于 WPF 领域的经验和见解。虽然我并不是这方面的专家,但是希望通过我的分享,能够为大家提供一些有用的信息和思考角度。

WriteableBitmap 背景

WriteableBitmap 继承至 System.Windows.Media.Imaging.BitmapSource

“巨硬” 官方介绍:  WriteableBitmap 类

WriteableBitmap使用 类可按帧更新和呈现位图。 这对于生成算法内容(如分形图像)和数据可视化(如音乐可视化工具)非常有用。

WriteableBitmap 使用两个缓冲区后台缓冲区 在系统内存中分配,并累积当前未显示的内容。 前端缓冲区 在系统内存中分配,并包含当前显示的内容。 呈现系统将前缓冲区复制到视频内存中以供显示。

两个线程使用这些缓冲区。 用户界面 (UI) 线程生成 UI,但不会将其呈现在屏幕上。 UI 线程响应用户输入、计时器和其他事件。 一个应用程序可以有多个 UI 线程。 呈现线程编写和呈现来自 UI 线程的更改。 每个应用程序只有一个呈现线程。

UI 线程将内容写入后台缓冲区。 呈现线程从前缓冲区读取内容并将其复制到视频内存。 使用更改的矩形区域跟踪对后台缓冲区所做的更改。

调用其中 WritePixels 一个重载以自动更新和显示后台缓冲区中的内容。

为了更好地控制更新,并且要对后台缓冲区进行多线程访问,请使用以下工作流:

  1. Lock 调用 方法以保留更新的后台缓冲区。
  2. 通过访问 属性获取指向后台缓冲区的 BackBuffer 指针。
  3. 将更改写入后台缓冲区。 锁定时 WriteableBitmap ,其他线程可能会将更改写入后台缓冲区。
  4. AddDirtyRect 调用 方法以指示已更改的区域。
  5. Unlock 调用 方法以释放后台缓冲区并允许在屏幕上演示。

将更新发送到呈现线程时,呈现线程会将更改后的矩形从后缓冲区复制到前缓冲区。 呈现系统控制此交换以避免死锁和重绘项目。

WriteableBitmap 渲染原理

  • 在调用 WriteableBitmapAddDirtyRect 方法的时候,实际上是调用 MILSwDoubleBufferedBitmap.AddDirtyRect,这是 WPF 专门为 WriteableBitmap 而提供的非托管代码的双缓冲位图的实现。

  • WriteableBitmap 内部数组修改完毕之后,需要调用 Unlock 来解锁内部缓冲区的访问,这时会提交所有的修改。

WriteableBitmap 使用技巧

  1. WriteableBitmap 的性能瓶颈源于对脏区的重新渲染。
    • 脏区为 0 或者不在可视化树渲染,则不消耗性能。
    • 只要有脏区,渲染过程就会开始成为性能瓶颈。
      • CPU 占用基础值就很高了。
      • 脏区越大,CPU 占用越高,但增幅不大。
  2. 内存拷贝不是 WriteableBitmap 的性能瓶颈。
    • 建议使用 Windows API 或者 .NET API 来拷贝内存数据。

特殊的应用场景,可以适当调整下自己写代码的策略:

  • 如果你希望有较大脏区的情况下降低 CPU 占用,可以考虑降低 WriteableBitmap 脏区的刷新率。
  • 如果你希望 WriteableBitmap 有较低的渲染延迟,则考虑减小脏区。

案例

测试 Demo 使用 OpenCvSharp 将视频帧读取出来,将视频帧图像数据通过 WriteableBitmap 渲染到界面的 Image 控件。

核心源码

  • 核心代码,利用双缓存区更新位图图像信息
private void ShowImage()
{
    Bitmap.Lock();

    bitmap = frame.ToBitmap();

    bitmapData = bitmap.LockBits(new Rectangle(new System.Drawing.Point(0, 0), bitmap.Size),
        System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

    Bitmap.WritePixels(rect, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride, 0, 0);

    bitmap.UnlockBits(bitmapData);
    bitmap.Dispose();

    Bitmap.Unlock();
}

完整的 ViewModel 代码

public class MainWindowViewModel : Prism.Mvvm.BindableBase
{
    #region 属性、变量、命令

    private WriteableBitmap _bitmap;
    /// <summary>
    /// UI绑定的资源对象
    /// </summary>                
    public WriteableBitmap Bitmap
    {
        get => _bitmap;
        set => SetProperty(ref _bitmap, value);
    }

    /// <summary>
    /// OpenCvSharp 视频捕获对象
    /// </summary>
    private static VideoCapture videoCapture;

    /// <summary>
    /// 视频帧
    /// </summary>
    private static Mat frame = new Mat();

    private static BitmapData bitmapData = new BitmapData();
	
	private static Bitmap bitmap;
    
    Int32Rect rect;

    static int width = 0, height = 0;

    /// <summary>
    /// 打开文件
    /// </summary>
    public DelegateCommand OpenFileCommand { get; set; }

    public DelegateCommand MNCommand { get; set; }

    #endregion

    public MainWindowViewModel()
    {
        videoCapture = new VideoCapture();

        OpenFileCommand = new DelegateCommand(OpenFile);
        MNCommand = new DelegateCommand(MN);
    }

    #region 私有方法

    private void OpenFile()
    {
        OpenFileDialog open = new OpenFileDialog()
        {
            Multiselect = false,
            Title = "请选择文件",
            Filter = "视频文件(*.mp4, *.wmv, *.mkv, *.flv)|*.mp4;*.wmv;*.mkv;*.flv|所有文件(*.*)|*.*"
        };

        if (open.ShowDialog() is true)
        {           
            ShowMove(open.FileName);
        }
    }

    /// <summary>
    /// 获取视频
    /// </summary>
    /// <param name="fileName">文件路径</param>
    private void ShowMove(string fileName)
    {
        videoCapture.Open(fileName, VideoCaptureAPIs.ANY);

        if (videoCapture.IsOpened())
        {
            var timer = (int)Math.Round(1000 / videoCapture.Fps) - 8;
            width = videoCapture.FrameWidth;
            height = videoCapture.FrameHeight;

            Bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
            rect = new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight);

            while (true)
            {
                videoCapture.Read(frame);
                if (!frame.Empty())
                {
                    ShowImage();
                    Cv2.WaitKey(timer);
                }
            }
        }
    }

    private void ShowImage()
    {
        Bitmap.Lock();

        bitmap = frame.ToBitmap();

        bitmapData = bitmap.LockBits(new Rectangle(new System.Drawing.Point(0, 0), bitmap.Size),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

        Bitmap.WritePixels(rect, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride, 0, 0);

        bitmap.UnlockBits(bitmapData);
        bitmap.Dispose();

        Bitmap.Unlock();
    }
}

测试结果

测试结果,经供参考,更精准的性能测试请使用专业工具。

  • VS Debug模式下的性能监测,以及Windows任务管理器中的资源占用,可以看出各项资源的使用是比较稳定的。
  • 发布之后独立运行资源的占用应该会有5%的降低。

【WPF】使用 WriteableBitmap 提升 Image 性能,# WPF,wpf,性能优化,c#,opencv,WriteableBitmap文章来源地址https://www.toymoban.com/news/detail-820587.html

到了这里,关于【WPF】使用 WriteableBitmap 提升 Image 性能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • WPF性能优化:Freezable 对象

    Freezable是WPF中一个特殊的基类,用于创建可以冻结(Freeze)的可变对象。冻结一个对象意味着将其状态设置为只读,从而提高性能并允许在多线程环境中共享对象。 我们定义画刷资源的时候常常会这样写: 代码中的 o:Freeze=\\\"True\\\" 其实就是使用 Freezable 的 Freeze 方法冻结画刷,

    2024年02月08日
    浏览(36)
  • WPF 性能优化-高刷新绘图

    笔者之前接到一个需求,需要在WPF上实时显示病人实时的生理信号(心电图等)。团队开发,需求很快做完了(Unit test 效果图如下) 但是后来发布到产品上发现,资源占用比本机的要大。本地监控后,发现随着时间推移内存和Page Faults 都在增长,如果在产品上长期(几个月甚至

    2024年02月04日
    浏览(37)
  • wpf 使用BitmapImage给Image的Source赋值,并释放原占用资源,避免删除原文件时导致程序崩溃

    wpf 使用BitmapImage给Image的Source赋值,并释放原占用资源,避免删除原文件时导致程序崩溃,示例代码如下: 使用这个方法: 请注意,BitmapImage的Freeze方法会将资源从垃圾收集器中释放,因此不会对应用程序的内存使用造成负担。不过请注意,在使用Image控件的时候,最好能及

    2024年01月15日
    浏览(50)
  • WPF列表性能提高技术

    WPF数据绑定系统不仅需要绑定功能,还需要能够处理大量数据而不会降低显示速度和消耗大量内存,WPF提供了相关的控件以提高性能,所有继承自 ItemsControl 的控件都支持该技术。 UI虚拟化是列表仅仅为当前显示项创建容器对象的一种技术。例如ListBox控件具有1000条记录,但是

    2024年02月07日
    浏览(35)
  • C++享元模式探索:轻松优化内存使用和性能提升之道

    享元模式(Flyweight Pattern)是一种结构型设计模式,其主要目的是通过共享相似对象以减少内存占用和提高程序性能。在享元模式中,相似对象的公共部分被提取出来,并存储在共享的享元对象中。每个实例对象只需存储其特有的状态,而公共状态则从享元对象中获取。这样

    2023年04月23日
    浏览(53)
  • 界面控件Telerik UI for WPF——Windows 11主题精简模式提升应用体验

    Telerik UI for WPF拥有超过100个控件来创建美观、高性能的桌面应用程序,同时还能快速构建企业级办公WPF应用程序。Telerik UI for WPF支持MVVM、触摸等,创建的应用程序可靠且结构良好,非常容易维护,其直观的API将无缝地集成Visual Studio工具箱中。 随着最近R2 2023的发布,Telerik

    2024年02月10日
    浏览(56)
  • WPF绑定之道:为何选择属性而非字段,提升灵活性与可控性

      概述: WPF支持绑定到对象的属性而不是字段,主要因为属性提供了更多控制和扩展性。属性包含get和set方法,支持数据验证和通知属性更改,而字段通常被认为是内部实现。使用属性使WPF能够更灵活、可控地与数据交互,提高代码的可读性和可维护性。 WPF(Windows Presenta

    2024年03月25日
    浏览(47)
  • WPF实战学习笔记18-优化设计TodoView

    修复新增项目无法编辑问题 更新MyToDo.Api/Service/ToDoService.cs 更新MyToDo.Api/Service/MemoService.cs 增加了对完成状态的区分 更新MyToDo.Api/Service/TodoView.xaml 增加了选项卡删除功能 更新删除请求URI 更新MyToDo.Api/Service/Baservice.cs 添加删除命令并初始化 更新文件:MyToDo/ViewModel/TodoViewModel.cs

    2024年02月15日
    浏览(40)
  • WPF/C#实现图像滤镜优化方案:打造炫目视觉体验!

    原因:我之所以想做这个项目,是因为在之前查找关于C#/WPF相关资料时,我发现讲解图像滤镜的资源非常稀缺。此外,我注意到许多现有的开源库主要基于CPU进行图像渲染。这种方式在处理大量图像时,会导致CPU的渲染负担过重。因此,我将在下文中介绍如何通过GPU渲染来有

    2024年04月16日
    浏览(71)
  • 深入理解WPF中的Dispatcher:优化UI操作的关键

      概述: Dispatcher是WPF中用于协调UI线程和非UI线程操作的关键类,通过消息循环机制确保UI元素的安全更新。常见用途包括异步任务中的UI更新和定时器操作。在实践中,需注意避免UI线程阻塞、死锁,并使用CheckAccess方法确保在正确的线程上执行操作。这有助于提升应用程序

    2024年02月04日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包