Я работаю над приложением, которое позволяет пользователям манипулировать несколькими изображениями с помощью ItemsControl. Я начал выполнять некоторые тесты и обнаружил, что у приложения есть проблемы с отображением некоторых больших изображений - т.е. он не работал с высоким разрешением (21600x10800), 20 МБ изображений из
http://earthobservatory.nasa.gov/Features/BlueMarble/BlueMarble_monthlies.php, хотя он отображает изображение телескопа Хаббла 6200x6200, 60 МБ с http://zebu.uoregon.edu/hudf/hudf.jpg очень хорошо.
Исходное решение только что указало элемент управления Image со свойством Source, указывающим на файл на диске (через привязку). С файлом Blue Marble - изображение просто не будет отображаться. Теперь это может быть просто ошибка, скрытая где-то в глубине фанковой реализации MVVM + XAML - визуальное дерево, отображаемое Snoop, выглядит следующим образом:
Окно / Border / AdornerDecorator / ContentPresenter / Сетка / Холст / UserControl / Граница / ContentPresenter / Сетка / Сетка / Сетка / Сетка / Граница / Сетка / ContentPresenter / UserControl / UserControl / Граница / ContentPresenter / Сетка / Сетка / Сетка / Сетка /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.Source, если у вас есть несколько элементов управления Image, которые указывают на одно и то же изображение - они используют только столько памяти, сколько загружено только одно изображение. С решением BitmapImage.DecodePixelWidth - каждый дополнительный элемент управления Image использует больше памяти, и каждый из них использует больше, чем просто при указании Image.Source. Возможно, WPF каким-то образом кэширует эти изображения в сжатом виде, а если вы укажете декодированные размеры - создается впечатление, что вы получаете несжатое изображение в памяти, плюс это занимает 6 раз (возможно, без этого масштабирование выполняется на GPU?) Такое ощущение, что оригинальное изображение с высоким разрешением также загружается и занимает место.
Если я просто уменьшу изображение, сохраню его во временный файл, а затем использую Image.Source, чтобы указать на файл - оно, вероятно, будет работать, но будет довольно медленным и потребует обработки очистки временного файла. , Если бы я мог обнаружить изображение, которое не загружается должным образом - возможно, я мог бы только уменьшить его, если нужно, но Image.ImageFailed никогда не запускается. Может быть, это как-то связано с видеопамятью, и это приложение просто использует больше с глубоким визуальным деревом, масками непрозрачности и т.д.
Актуальный вопрос: Как я могу загружать большие изображения так быстро, как это делает опция Image.Source, не используя больше памяти для дополнительных копий и дополнительной памяти для уменьшенного изображения, если они нужны мне только при определенном разрешении ниже исходного? Кроме того, я не хочу хранить их в памяти, если управление ими больше не используется.