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

Dar*_*rio 5 c# wpf image

我正在尝试在 WPF 中编写一个小照片查看器,基本上模拟Windows 照片查看器提供的功能。

以窗口和全屏模式显示是使用Image

<Image Name="ImgDisplay" Source="{Binding CurrentImage.FullPath, Converter={StaticResource FilenameToImageConverter}}"/>
Run Code Online (Sandbox Code Playgroud)

其中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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

然而,当用我的照片(大约 8mpx、4MB jpeg 文件)测试该程序时,显示图像的加载时间很长(2 或 3 秒),而 Windows 照片查看器能够轻松地跳过图像。我看到它首先显示图像的较低分辨率版本,然后显示完整图像。然而一切最终都比我的方法快得多。

我怎样才能实现这个目标?都是通过缩略图/预加载吗?先感谢您

编辑

谢谢,给出的提示,缩小使用DecodePixelWidth以及异步/单向绑定已经大大改善了情况,尽管还不足以使一切变得流畅。另外IsAsync=true,在加载下一个图像之前,图像将始终为空白,这是一个令人不快的效果。

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

Evk*_*Evk 4

如果您无法使用准备好的预览(缩小比例)图像,至少不要以全尺寸渲染图像。为了避免这种情况,请使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑回应评论。仅使用转换器并不容易实现您想要的效果,但是您可以向 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;
        });
    }
Run Code Online (Sandbox Code Playgroud)

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