如何使用WPF / BitmapImage快速加载和显示图像?

时间:2016-09-14 16:08:29

标签: c# wpf image

我试图在WPF中编写一个小照片查看器,基本上模仿 Windows Photo Viewer 提供的内容。

使用Image

在窗口和全屏模式下显示
<Image Name="ImgDisplay" Source="{Binding CurrentImage.FullPath, Converter={StaticResource FilenameToImageConverter}}"/>

FilenameToImageConverter执行以下操作

public class FilenameToImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string uri = value as string;

            if (uri != null && File.Exists(uri))
            {
                BitmapImage image = new BitmapImage();
                image.BeginInit();
                image.CacheOption = BitmapCacheOption.None;
                image.UriCachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.BypassCache);
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                image.UriSource = new Uri(uri);
                image.EndInit();
                return image;
            }

            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

使用我的照片测试程序时(大约8mpx,4MB jpeg文件),显示图像的加载时间很长(2或3秒),而Windows Photo Viewer可以轻松跳过图像。我看到它首先显示图像的低分辨率版本,在显示完整图像之前不久。然而,一切都比我的方法更快。

我怎样才能做到这一点?是通过缩略图/预加载吗? 提前谢谢

修改

谢谢,给出的提示,使用DecodePixelWidth缩减以及Async / OneWay-Bindings已经大大改善了这种情况,但还不足以让一切都流畅。同样使用IsAsync=true,在加载下一张图像之前,图像将始终为空白,这是一种令人不快的效果。

通过立即显示高度缩小的版本 来解决这个问题会很好,然后在异步加载时显示完整的图像。由于涉及某种时间继承,我不知道如何使用绑定实现它。有什么建议吗?

1 个答案:

答案 0 :(得分:2)

如果你不能使用准备好的预览(缩小尺寸)图像,至少不要渲染图像的全尺寸。为避免这样做,请使用DecodePixelWidth(或DecodePixelHeight)属性。将其设置为一些合理的值(可能基于当前的显示器分辨率),您已经看到了显着的性能提升:

public class FilenameToImageConverter : IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        string uri = value as string;

        if (uri != null && File.Exists(uri)) {
            BitmapImage image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.UriSource = new Uri(uri);
            image.DecodePixelWidth = 1920; // should be enough, but you can experiment
            image.EndInit();
            return image;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new NotSupportedException();
    }
}

编辑回应评论。 使用转换器它并不容易实现你想要的,但你可以再添加一个属性到你的ViewModel并像这样做(注意你现在需要直接绑定到CurrentImage,没有转换器):

    private string _currentFile;

    public string CurrentFile
    {
        get { return _currentFile; }
        set
        {
            if (value == _currentFile) return;
            _currentFile = value;
            OnPropertyChanged();
            UpdateImage();
        }
    }

    private ImageSource _currentImage;

    public ImageSource CurrentImage
    {
        get { return _currentImage; }
        set
        {
            if (Equals(value, _currentImage)) return;
            _currentImage = value;
            OnPropertyChanged();
        }
    }

    private async void UpdateImage() {
        var file = this.CurrentFile;
        // this is asynchronous and won't block UI
        // first generate rough preview
        this.CurrentImage = await Generate(file, 320);
        // then generate quality preview
        this.CurrentImage = await Generate(file, 1920);            
    }

    private Task<BitmapImage> Generate(string file, int scale) {
        return Task.Run(() =>
        {
            BitmapImage image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.UriSource = new Uri(file);
            image.DecodePixelWidth = scale;
            image.EndInit();
            image.Freeze(); // important
            return image;
        });
    }

请注意,这只是一个示例代码,需要一些工作。例如,如果您在预览生成过程中更改了所选文件(因为它们是异步的) - 您需要取消所有待处理操作,以便不使用上一个操作覆盖当前文件预览。但这应该很容易。