• 2018-8-10-WPF-使用不安全代码快速从数组转-WriteableBitmap


    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

  • 相关阅读:
    clearstatcache清除文件状态缓存
    使用mysql创建自己的物化视图
    python——复制目录结构小脚本
    开发一个jQuery插件——多级联动菜单
    pack、unpack自制二进制“数据库”
    博客爬取系统
    session放入缓存(redis)、DB
    centos+nginx从零开始配置负载均衡
    几个容易被忽略的mysql知识
    数组方式进行表单提交
  • 原文地址:https://www.cnblogs.com/lindexi/p/12085743.html
Copyright © 2020-2023  润新知