【问题标题】:How to quickly load and display images with WPF/BitmapImage?如何使用 WPF/BitmapImage 快速加载和显示图像?
【发布时间】:2016-09-14 16:08:29
【问题描述】:

我正在尝试在 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 照片查看器能够轻松跳过图像。我看到它首先显示图像的低分辨率版本,然后才显示完整图像。然而,一切最终都比我的方法快得多。

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

编辑

感谢您提供的提示,使用 DecodePixelWidth 以及 Async/OneWay-Bindings 缩小规模已经大大改善了这种情况,但还不足以让一切变得流畅。同样使用IsAsync=true,在加载下一张图片之前,图片总是空白,这是一种令人不快的效果。

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

【问题讨论】:

  • 也许这个问题的建议可以提供帮助?他们建议使用单向异步绑定并将图像缩小到您实际需要显示的大小。 stackoverflow.com/questions/8960110/…
  • 如果您尝试使用 Windows 照片查看器快速跳过许多(大)图像/向后快速,您会发现实际上并没有那么快。它可以预测您将访问哪些图像并提前加载它们。较新的版本使用硬件加速。此外,如果您使用图像和 WPF 绑定,您可能会发现这很有用 blog.tedd.no/2011/07/28/…
  • 谢谢,这是宝贵的见解。 Photo Viewer 实际上是否依赖于 Thumbs.db,在我的程序中这样做是否值得?

标签: c# wpf image


【解决方案1】:

如果您不能使用准备好的预览(缩小)图像,至少不要以全尺寸渲染图像。为避免这样做,请使用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();
    }
}

编辑以响应 cmets。 仅使用转换器并不容易实现您想要的,但是您可以在 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;
        });
    }

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

【讨论】:

  • 谢谢,这是很好的第一步。我在这方面编辑了问题。
  • 如果你对 DecodePixelWidth 使用非常小的数字(比如 320),那么性能是否足够好(我当然不是要保留它——只要你将它用作预览)
  • 有点。首先异步加载 320 像素版本和 1920 像素版本的组合会很好。如何通过绑定实现这一点?
  • 这对我来说是一个不错的起点。它必须包含在视图模型中有点遗憾,但我还是会试一试
  • @Dario 如果您愿意,有几种方法可以避免将 ImageSource 放入视图模型中。也许创建从 Image 继承的控件并在那里执行,或者使用附加属性。主要使用 DecodePixelWidth 并在 backgroup 线程上执行此操作(构造 BitmapImage)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-20
  • 1970-01-01
  • 2015-04-22
  • 2017-10-20
  • 2019-05-31
  • 1970-01-01
  • 2011-11-13
相关资源
最近更新 更多