.NET框架WPF中加载高质量大图慢的性能优化

最近的项目中,遇到一个关于WPF中同时加载多张图片时,内存占用非常高的问题。

问题背景:

在一个ListView中同时加载多张图片,注意:我们需要加载的图片分辨率非常高。

代码:

XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    
    <Button Content="Load" Width="100" Height="35" Margin="0,10" Click="Button_Click"/>
    
    <ListView Grid.Row="1" x:Name="lvImages">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Image Source="{Binding ImageSource}" MaxWidth="800"/>
            </DataTemplate>
        </ListView.ItemTemplate>
        <ListView.Template>
            <ControlTemplate>
                <Grid>
                    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden">
                        <ItemsPresenter />
                    </ScrollViewer>
                </Grid>
            </ControlTemplate>
        </ListView.Template>
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel IsItemsHost="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.IsVirtualizing="True"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
    </ListView>
</Grid>

C#:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        lvImages.Items.Clear();
        // Image folder location: D:\Pics
        string[] files = System.IO.Directory.GetFiles(@"D:\Pics");
        List<ImageSourceModel> models = new List<ImageSourceModel>();
        foreach(var path in files)
        {
            BitmapImage image = new BitmapImage();
            image.BeginInit();
            image.UriSource = new System.Uri(path);
            image.EndInit();
            image.Freeze();
            models.Add(new ImageSourceModel() { ImageSource = image });
        }
        lvImages.ItemsSource = models;
    }
}
public class ImageSourceModel
{
    public ImageSource ImageSource { get; set; }
}

内存占用情况(此时只加载了20张图片,内存占用>1G):

优化方案:

1. 初始加载时,只加载部分图片并显示。当ScrollViewer滚动到底部时,再加载一部分。关于这个方案,可以参考 WPF MVVM模式下实现ListView下拉显示更多内容;

但是这并不能解决最终内存占用过高的情况。

2. 给图片设置DecodePixelWidth属性,

BitmapImage image = new BitmapImage();

image.BeginInit();

image.UriSource = new System.Uri(path);

image.DecodePixelWidth = 800;

image.EndInit();

image.Freeze();

models.Add(new ImageSourceModel() { ImageSource = image });

此时的内存占用如图

内存降低的非常显著,此时同样多的图片内存占用只有40M左右。

最终我们可以把优化方案1和优化方案2结合起来。这样在加载多张图片时不会出现卡顿的现象。另外从用户体验的角度我们可以在图片显示出来前,先用一个Loading的动画效果过渡下。

wpf大图片处理速度优化:指针操作,并行操作,几十倍优化

我一直用GDI+做Winform 的基于指针的图片处理,这次下决心全部移到wpf上(主要是显示布局很方便)
采用的图片是
2512*3307 的大图 830万像素
类库基于WritableBitmapEx 的wpf版本
函数是我自己写的扩展方法,只是利用了 writableBitmapEx提供的环境 ,我懒得从头到尾自己写了
 
1.标准int32数组遍历计算 release
0.28s

unsafe public static void TestGray1(this WriteableBitmap bmp)
{
    using (var context = bmp.GetBitmapContext())
    {
        int height = context.Height;
        int width = context.Width;
        for (int y = 0; y < height; y++)
        {                   
            for (int x = 0; x < width; x++)
            {
                int pos=y * context.Width + x;
                var c = context.Pixels[pos];
                var r = (byte)(c >> 16);
                var g = (byte)(c >> 8);
                var b = (byte)(c);
               
                var gray = ((r * 38 + g * 75 + b * 15) >> 7);
                var color=(255 << 24) | (gray << 16) | (gray << 8) | gray;
                context.Pixels[pos]=color;
            }
        }
    }
}

2.标准int32指针遍历计算 release

0.04s

unsafe public static void TestGray2(this WriteableBitmap bmp)
{
    using (var context = bmp.GetBitmapContext())
    {
        var ptr = context.Pixels;
        int height = context.Height;
        int width = context.Width;
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                var c = *ptr;
                var r = (byte)(c >> 16) ;
                var g = (byte)(c >> 8) ;
                var b = (byte)(c) ;
                var gray = ((r * 38 + g * 75 + b * 15) >> 7);
                var color = (255 << 24) | (gray << 16) | (gray << 8) | gray;
                *ptr = color;
                ptr++;
            }
        }
    }
}

3.colorstruct指针 遍历计算

0.02 s

应该是已经到极限速度了[除了后面的并行方式],我已经想不出还有什么方法可以提高处理速度

而且这种方式是最直观的,最容易理解的处理方式,也便于以后维护

[StructLayout(LayoutKind.Sequential)]
public struct PixelColor
{
    public byte Blue;
    public byte Green;
    public byte Red;
    public byte Alpha;
}
    unsafe public static void TestGray3(this WriteableBitmap bmp)
    {
        using (var context = bmp.GetBitmapContext())
        {
            var ptr = (PixelColor*)context.Pixels;
            int height = context.Height;
            int width = context.Width;
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    var c = *ptr;
                    var gray = ((c.Red * 38 + c.Green * 75 + c.Blue * 15) >> 7);
                    (*ptr).Green=(*ptr).Red=(*ptr).Blue = (byte)gray;
                    ptr++;
                }
            }
        }
    }

4.作为对比,我又测试了一下 GDI+的 指针处理图片的速度

0.06s

public static unsafe Bitmap ToGray(Bitmap img)
{
    var rect = new System.Drawing.Rectangle(0, 0, img.Width, img.Height);
    var data = img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    var ptr = (ColorType*)data.Scan0.ToPointer();
    var bytes = new Int32[img.Width * img.Height];
    var height = img.Height;
    var width = img.Width;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            var color = *ptr;
            var gray = ((color.R * 38 + color.G * 75 + color.B * 15) >> 7);
            (*ptr).R = (*ptr).G = (*ptr).B = (byte)gray;
            ptr++;
         }
    }
    img.UnlockBits(data);
    return img;
}

5.重头戏来了。我一直对Parallel.For 很迷惑,为什么他的消耗时间是普通for的好几倍。今天仔细研究了一下,发现原来是用错了

0.01秒   release

笔记本i5cpu,如果台式机的I7会更加强悍,速度会成半成半降低。

主要是利用了微软的任务并行库的循环并行化的方法。

注意:默认的并行循环对于函数体很小的情况是很慢的,这种情况必须用Partitioner 创建循环体,这在MSDN有介绍,是关键之中的关键

unsafe public static void TestGray5(this WriteableBitmap bmp)
{ 
    using (var context = bmp.GetBitmapContext())
    {
        int height = context.Height;
        int width = context.Width;
        Parallel.ForEach(Partitioner.Create(0, height), (h) =>
        {
            var ptr = (PixelColor*)context.Pixels;
            ptr += h.Item1 * width;
            for (int y = h.Item1; y < h.Item2; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    var c = *ptr;
                    var gray = ((c.Red * 38 + c.Green * 75 + c.Blue * 15) >> 7);
                    (*ptr).Green = (*ptr).Red = (*ptr).Blue = (byte)gray;
                    ptr++;
                }
            }
        });
    }
}

感想

1.绝对不要在循环体内使用属性或函数,很有可能会降低数倍计算速度。

因为属性本质上是个函数,而在循环体内最好不要再调用函数,如果确实需要用内联代码的方式,c#没有inline,那么copy代码吧,反正为了速度。

2. 用指针移位操作 似乎比 直接数组访问要快10倍啊

我感觉要么是cache命中的原因,要么是 数组本身存取被属性封装了。相当于又调用了函数。

3.TPL 任务并行库果真好用,看来微软早已考虑过大量数据并行的循环优化问题09年,只是我一直用错了方法,才觉得很慢。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索指针
, 内存
, 优化
, new
, 性能优化
用户体验
高质量大图、ios点击查看大图框架、wpf ui 框架、wpf 开源ui框架、wpf mvvm框架,以便于您获取更多的相关知识。

时间: 2016-11-02

.NET框架WPF中加载高质量大图慢的性能优化的相关文章

CI(CodeIgniter)框架视图中加载视图的方法

本文实例讲述了CI(CodeIgniter)框架视图中加载视图的方法.分享给大家供大家参考,具体如下: CI做为php的一个轻量级框架,其自身具备很多优点,在此我重点想说的是视图中加载视图. 1:在Application\config\database.php文件中设置好CodeIgniter 数据库变量之后,紧接着在Application\config\config.php文件中设置基础 URL.例如我的基础 URL 是:http://localhost/codeigniter/ 2:接下来创

js-如何将60多M的超大图片切成瓦片并再gis或openlayer或其他地图中加载?

问题描述 如何将60多M的超大图片切成瓦片并再gis或openlayer或其他地图中加载? 项目需要展示某地区的地图,客户已提供带有特定数据的超大高清图片,这么大不能直接加载,那么有什么软件可以将这张大图片按分辨率切为有规则的小块,并且gis openlayer这些地图软件哪个比较简单方便实现地图上瓦片背景的动态加载?

WPF动态加载程序集

问题描述 WPF动态加载的程序集如果是窗体可以直接运行:假如把运行的窗体关闭后,它在AppDomain中还是存在的,那么下次加载的时候就可以根据名称直接打开运行:我想知道的是怎么防止程序集同时运行多次?(就是防止同一个窗体打开多次,在AppDomain中如何防止)PS:不要说什么模态窗体 解决方案 解决方案二:单例解决方案三:一般都是运行之前遍历已经存在的进程这种方法很不靠谱,强烈要求微软从底层实现这个功能解决方案四:在Application.Current.Windows中判断窗体名称,如果存

在网站优化中如何获取高质量外链

摘要: 在网站优化过程过,有一个是比较让一些站长头疼的事,那就是寻找网站外链的渠道.有的网站可以让我们带外链但是JS形式,有的网站可以带超链接的但里面却加了nofollow标签,这些外 在网站优化过程过,有一个是比较让一些站长头疼的事,那就是寻找网站外链的渠道.有的网站可以让我们带外链但是JS形式,有的网站可以带超链接的但里面却加了nofollow标签,这些外链资源加了也等于没有加.那么我们在网站优化中,到底获取高质量外链有哪些渠道呢? 新闻源媒体 其实现在在网上有许多可以投稿的高权重新闻媒体,

Tiger系列一:从XML中加载属性

xml|加载 JDK1.5(代号Tiger)中更新了java.util.Properties类,提供了从XML文件中读写key-value对属性的简单方法:loadFromXML()和storeToXML() 1.基本加载属性的方法 l Sample属性文件:sample.properties foo=barfu=bazl 加载属性的Sample程序 import java.io.FileInputStream;import java.util.Properties; public class

Flash中加载影片时,Loading的位置对影片的影响

loading|加载 在论坛经常看到Loading加载方式的探讨,不过大多都是讨论用何种加载方法及代码,今天我想对Flash中加载影片时,Loading的位置对影片的影响.Flash Loading有多种制作及加载方式,本文研究的重点是讨论主影片中加载外部swf文件时Loading的位置对影片的影响.也就是将Loading写在主影片内部还是被加载影片中.     首先从缓存进行考虑,由于浏览器可以缓存Flash文件,因此Loading放在主影片内部,或者被加载影片中都没有影响,浏览器第一次会自动

Flash动态文本框中加载HTML格式文本

动态|加载|文本框 在Flash中可以利用Actionscript可以在动态文本框中加载HTML格式的文本,这个教程就不给大家具体讲解HTML标记了,如果您不熟悉可以查看本站HTML基础知识栏目内容. 效果如下: 点击这里下载源文件(解压密码:www.webjx.com) 建立一个Flash文档,然后设置如下字体. 在上面图示中如果你没有设置多行.将文本呈现为HTML两项,那么我们可以使用下面代码: myText.html = true; myText.multiline = true; 然后设

Windows7中加载Virtual PC映像技巧

笔者在Windows7中使用了Virtual PC来生成Windows XP的虚拟机,在使用过程中一直非常稳定.不过 ,每次需要使用到虚拟机中的文件的时候却都要开启Virtual PC通过网络共享来获得,显得极其不方便和 占用系统资源. 那么,有没有更加简化的方法来轻松取得虚拟机中的文件呢?其实,Windows7已经为我们提供了一种快 捷方法.我们只需在计算机中加载虚拟机的VPC映像就可以达到目的了. 首先,我们点击"开始-所有程序-管理工具-计算机管理"命令打开"计算机管理

从资源中加载皮肤

由于单位上最近要开发一个内部使用的小型项目,我需要一个运行稳定的,能够将皮肤文件放到资源里的并且易于使用的界面开发包,当然,免费的最好.于是利用google一阵狂搜,嘿,真还找到了这样的一个开发库:AppFace For VC 0.2. AppFace For VC 支持Win9X/NT/2K/XP,UNICODE/ANSI,能够对目标进程里的所有Widows标准控件,系统菜单,通用对话框等实现换肤,对非商业用途而言,它是完全免费的.关键的是AppFace的使用非常简单,很容易添加到已有的工程中