在 WPF 中使用具有大图像的图像源
我正在开发一个允许用户使用 ItemsControl 操作多个图像的应用程序。我开始运行一些测试,发现该应用程序在显示一些大图像时出现问题 - 即。它不适用于高分辨率 (21600x10800)、20MB 图像 http://earthobservatory.nasa.gov/Features/BlueMarble/BlueMarble_monthlies.php,尽管它显示来自 http://zebu.uoregon.edu 的 6200x6200、60MB 哈勃望远镜图像/hudf/hudf.jpg 就好了。
原始解决方案只是指定一个 Image 控件,其 Source 属性指向磁盘上的文件(通过绑定)。使用 Blue Marble 文件 - 图像将不会显示。现在,这可能只是隐藏在时髦的 MVVM + XAML 实现深处的一个错误 - Snoop 显示的可视化树如下所示:
Window/Border/AdornerDecorator/ContentPresenter/Grid/Canvas/UserControl/Border/ContentPresenter/Grid/Grid/Grid /Grid/Border/Grid/ContentPresenter/UserControl/UserControl/Border/ContentPresenter/Grid/Grid/Grid/Grid/Viewbox/ContainerVisual/UserControl/Border/ContentPresenter/Grid/Grid/ItemsControl/Border/ItemsPresenter/Canvas/ContentPresenter/Grid /Grid/ContentPresenter/Image...
现在调试它! WPF 可能会像这样疯狂......
无论如何,事实证明,如果我创建一个简单的 WPF 应用程序 - 图像加载得很好。我尝试找出根本原因,但我不想花几周的时间。我认为正确的做法可能是使用转换器来缩小图像 - 这就是我所做的:
ImagePath = @"F:\Astronomical\world.200402.3x21600x10800.jpg";
TargetWidth = 2800;
TargetHeight = 1866;
现在
<Image>
<Image.Source>
<MultiBinding Converter="{StaticResource imageResizingConverter}">
<MultiBinding.Bindings>
<Binding Path="ImagePath"/>
<Binding RelativeSource="{RelativeSource Self}" />
<Binding Path="TargetWidth"/>
<Binding Path="TargetHeight"/>
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>
这工作
public class ImageResizingConverter : MarkupExtension, IMultiValueConverter
{
public Image TargetImage { get; set; }
public string SourcePath { get; set; }
public int DecodeWidth { get; set; }
public int DecodeHeight { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
this.SourcePath = values[0].ToString();
this.TargetImage = (Image)values[1];
this.DecodeWidth = (int)values[2];
this.DecodeHeight = (int)values[3];
return DecodeImage();
}
private BitmapImage DecodeImage()
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = (int)DecodeWidth;
bi.DecodePixelHeight = (int)DecodeHeight;
bi.UriSource = new Uri(SourcePath);
bi.EndInit();
return bi;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
正常,除了一个“小”问题。当您仅在 Image.Source 中指定文件路径时 - 与使用 BitmapImage.DecodePixelWidth 相比,应用程序实际上使用更少的内存并且运行速度更快。如果您有多个指向同一图像的 Image 控件,请加上 Image.Source - 它们仅使用与仅加载一张图像一样多的内存。使用 BitmapImage.DecodePixelWidth 解决方案 - 每个附加 Image 控件使用更多内存,并且每个控件使用的内存都比仅指定 Image.Source 时使用的内存更多。也许 WPF 以某种方式以压缩形式缓存这些图像,而如果您指定解码的尺寸 - 感觉就像您在内存中获得未压缩的图像,而且它需要 6 倍的时间(也许没有它,缩放是在 GPU 上完成的?),加上感觉原始高分辨率图像也被加载并占用空间。
如果我只是缩小图像,将其保存到临时文件,然后使用 Image.Source 指向该文件 - 它可能会工作,但会非常慢,并且需要处理临时文件的清理。如果我可以检测到未正确加载的图像 - 也许我只能在需要时缩小它,但 Image.ImageFailed 永远不会被触发。也许它与视频内存有关,而这个应用程序只是将更多的视频内存与深度视觉树、不透明蒙版等一起使用。
实际问题:如何才能像 Image.Source 选项那样快速加载大图像,而不使用更多如果我只需要低于原始分辨率的特定分辨率,那么可以用于额外副本的内存和用于缩小图像的额外内存?另外,如果没有图像控件再使用它们,我不想将它们保留在内存中。
I am working on an application that allows users to manipulate multiple images by using ItemsControl. I started running some tests and found that the app has problems displaying some big images - ie. it did not work with the high resolution (21600x10800), 20MB images from
http://earthobservatory.nasa.gov/Features/BlueMarble/BlueMarble_monthlies.php, though it displays the 6200x6200, 60MB Hubble telescope image from http://zebu.uoregon.edu/hudf/hudf.jpg just fine.
The original solution just specified an Image control with a Source property pointing at a file on a disk (through a binding). With the Blue Marble file - the image would just not show up. Now this could be just a bug hidden somewhere deep in the funky MVVM + XAML implementation - the visual tree displayed by Snoop goes like:
Window/Border/AdornerDecorator/ContentPresenter/Grid/Canvas/UserControl/Border/ContentPresenter/Grid/Grid/Grid/Grid/Border/Grid/ContentPresenter/UserControl/UserControl/Border/ContentPresenter/Grid/Grid/Grid/Grid/Viewbox/ContainerVisual/UserControl/Border/ContentPresenter/Grid/Grid/ItemsControl/Border/ItemsPresenter/Canvas/ContentPresenter/Grid/Grid/ContentPresenter/Image...
Now debug this! WPF can be crazy like that...
Anyway, it turned out that if I create a simple WPF application - the images load just fine. I tried finding out the root cause, but I don't want to spend weeks on it. I figured the right thing to do might be to use a converter to scale the images down - this is what I have done:
ImagePath = @"F:\Astronomical\world.200402.3x21600x10800.jpg";
TargetWidth = 2800;
TargetHeight = 1866;
and
<Image>
<Image.Source>
<MultiBinding Converter="{StaticResource imageResizingConverter}">
<MultiBinding.Bindings>
<Binding Path="ImagePath"/>
<Binding RelativeSource="{RelativeSource Self}" />
<Binding Path="TargetWidth"/>
<Binding Path="TargetHeight"/>
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>
and
public class ImageResizingConverter : MarkupExtension, IMultiValueConverter
{
public Image TargetImage { get; set; }
public string SourcePath { get; set; }
public int DecodeWidth { get; set; }
public int DecodeHeight { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
this.SourcePath = values[0].ToString();
this.TargetImage = (Image)values[1];
this.DecodeWidth = (int)values[2];
this.DecodeHeight = (int)values[3];
return DecodeImage();
}
private BitmapImage DecodeImage()
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = (int)DecodeWidth;
bi.DecodePixelHeight = (int)DecodeHeight;
bi.UriSource = new Uri(SourcePath);
bi.EndInit();
return bi;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Now this works fine, except for one "little" problem. When you just specify a file path in Image.Source - the application actually uses less memory and works faster than if you use BitmapImage.DecodePixelWidth. Plus with Image.Source if you have multiple Image controls that point to the same image - they only use as much memory as if only one image was loaded. With the BitmapImage.DecodePixelWidth solution - each additional Image control uses more memory and each of them uses more than when just specifying Image.Source. Perhaps WPF somehow caches these images in compressed form while if you specify the decoded dimensions - it feels like you get an uncompressed image in memory, plus it takes 6 times the time (perhaps without it the scaling is done on the GPU?), plus it feels like the original high resolution image also gets loaded and takes up space.
If I just scale the image down, save it to a temporary file and then use Image.Source to point at the file - it will probably work, but it will be pretty slow and it will require handling cleanup of the temporary file. If I could detect an image that does not get loaded properly - maybe I could only scale it down if I need to, but Image.ImageFailed never gets triggered. Maybe it has something to do with the video memory and this app just using more of it with the deep visual tree, opacity masks etc.
Actual question: How can I load big images as quickly as Image.Source option does it, without using more memory for additional copies and additional memory for the scaled down image if I only need them at a certain resolution lower than original? Also, I don't want to keep them in memory if no Image control is using them anymore.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我使用 DecodePixelWidth 与在 XAML 上设置源做了一个简单的测试(一张图像),使用 DecodePixelWidth 加载需要 28MB,而在不缩小的情况下需要 178MB。我很确定它不会将原始图像保留在内存中。
既然您说您正在处理多个图像,我怀疑这是一个图像重复使用问题。默认情况下,WPF 将缓存 BitmapImage 对象(无论是通过代码还是在 XAML 中创建)。它会查看 SourceUri 以及 DecodePixelWidth 和 DecodePixelHeight 以查找匹配项。如果您的 TargetWidth 和 TargetHeight 发生变化,则意味着 WPF 无法重新使用其图像缓存;如果您在没有任何额外选项的情况下设置源,那么这不会成为问题。
I did a simple test (one image) with using DecodePixelWidth vs settings the Source on the XAML, and loading in with DecodePixelWidth took 28MB vs 178MB without the scaling down. I'm pretty sure it doesn't keep the original image in memory.
Since you said you're working with multiple images, I suspect that this is an image re-use issue. By default, WPF will cache a BitmapImage object (whether created by code or in XAML). It looks at the SourceUri as well as the DecodePixelWidth and DecodePixelHeight to find a match. If your TargetWidth and TargetHeight are changing, it would mean that WPF can't re-use its image cache; something that would not be a problem if you set the source without any extra options.
我也面临同样的问题,看起来当 BitmapImage 属性 CreateOptions = 设置为 BitmapCreateOptions.IgnoreColorProfile 时,它的工作速度更快。
我们可以为经常使用的位图图像创建缓存。我知道 WPF 应该自动执行此操作,但我认为它会运行得更快。如果有人尝试测量加载时间,只需写评论:)
I also faced with same problem and looks like when BitmapImage property CreateOptions = set to BitmapCreateOptions.IgnoreColorProfile its working faster.
Other thing that we can create cache for our BitmapImages that used offten. I know that WPF should do this automatically but i thinks it will works faster. If somebody will try to measure loading time just write comments:)