Использование источника изображений с большими изображениями в WPF - PullRequest
3 голосов
/ 24 марта 2010

Я работаю над приложением, которое позволяет пользователям манипулировать несколькими изображениями с помощью 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, не используя больше памяти для дополнительных копий и дополнительной памяти для уменьшенного изображения, если они нужны мне только при определенном разрешении ниже исходного? Кроме того, я не хочу хранить их в памяти, если управление ими больше не используется.

Ответы [ 2 ]

2 голосов
/ 26 марта 2010

Я провел простой тест (одно изображение) с использованием DecodePixelWidth и настроек Source на XAML, и загрузка с DecodePixelWidth заняла 28 МБ против 178 МБ без уменьшения. Я почти уверен, что это не сохранит исходное изображение в памяти.

Поскольку вы сказали, что работаете с несколькими изображениями, я подозреваю, что это проблема повторного использования изображений. По умолчанию WPF будет кэшировать объект BitmapImage (созданный с помощью кода или в XAML). Он ищет SourceUri, а также DecodePixelWidth и DecodePixelHeight, чтобы найти совпадение. Если ваши TargetWidth и TargetHeight меняются, это будет означать, что WPF не может повторно использовать свой кэш изображений; то, что не будет проблемой, если вы установите источник без каких-либо дополнительных опций.

0 голосов
/ 24 октября 2013

Я также столкнулся с той же проблемой и выглядит, когда BitmapImage свойство CreateOptions = установлено на BitmapCreateOptions.IgnoreColorProfile работает быстрее.

Другое дело, что мы можем создать кеш для наших BitmapImages, которые использовали offten. Я знаю, что WPF должен делать это автоматически, но я думаю, что это будет работать быстрее. Если кто-то попытается измерить время загрузки, просто напишите комментарии:)

...