Шаблоны оптимизации производительности растровых изображений - PullRequest
36 голосов
/ 16 февраля 2012

Я нашел несколько шаблонов для оптимизации обработки растровых изображений в WPF.Я, однако, не понимаю, когда использовать каждый шаблон.Поскольку я думаю, что это распространенная проблема, я резюмировал, что я понимаю и что я думаю, и прошу вашей помощи.Если вы можете добавить шаблоны , объясните , чем они отличаются , объясните, используют ли они CPU или GPU , и научите , когда использовать каждый и как их объединить , это было бы огромной помощью!

Контекст - Сценарий «Сетка» изображений:

Мое приложениедолжен отображать много растровых изображений.Изображения отображаются на экране в виде сетки, состоящей из строк и столбцов (необязательно классы Grid или UniformGrid, подумайте о представлении Window Media Player в Window Media).Изображения могут перемещаться между различными ячейками сетки.Некоторые изображения в произвольных ячейках могут быть заменены другими.Изображения должны быть кликабельными, должны содержать контекстное меню, должны быть выбираемыми, перетаскиваемыми и т. Д. Другими словами, «объединение маленьких пуговиц в одно большое растровое изображение» не применимо, по крайней мере, наивно.

Pattern 0: Hack

Объедините маленькие ошибки в растровое изображение (как «рисовать контекст») и используйте его в качестве фона.Наложите это на изображения с пустым содержимым, которое будет обрабатывать обращения, контекстные меню, события и т. Д.

Преимущество состоит в том, что мы говорим здесь только о двух растровых изображениях: отображаемом в данный момент одном и том, который должен его заменить.,Это должно быть очень быстро.Тем не менее, мой многолетний опыт поднимает красный флаг опасности.Ваши комментарии?

Шаблон 1: Уменьшить размер изображения

Это не сложно, если вы заранее знаете размер изображения, к которому хотите изменить размер, и когда выготовы потерять детали (цвет) для производительности:

  1. Уменьшить размер растрового изображения с помощью BitmapImage.DecodePixelWidth
  2. Уменьшить информацию о цвете с помощью FormatConvertedBitmap.DestinationFormat
  3. Установить элементы управленияпараметр поведения масштабирования Image.Stretch - Stretch.None
  4. Установите для SetBitmapScalingMode для изображения значение LowQuality.
  5. Остановите буфер

См. код здесь .

Шаблон 2: Предварительная выборка фона

Этот шаблон применим, когда вы думаете, что можете воспользоваться преимуществом пользователя, смотрящего на изображения на экране,и подготовьте вперед следующие изображения для отображения.Недостатки вашего проекта, в дополнение к накладным расходам памяти, заключаются в том, что он должен поддерживать цель .Net Framework 4, а не только профиль клиента, поэтому он может привести к установке на клиентском компьютере.Вы сами должны будете испытывать боль при асинхронном программировании.

В этом шаблоне вы создаете точно необходимое количество элементов управления изображением.Когда необходимо добавить, переместить или удалить растровые изображения, вы изменяете только битмап-источники изображений.Задача BackgroundWorker отвечает за предварительную выборку BitmapSource (s) (возможно, с использованием шаблона «Уменьшить размер изображения» выше) и вставку их в MemoryCache.

Чтобы это работало, необходимо установить для CacheOption BitmapImage значение OnLoad, чтобы работа выгружалась в фоновый рабочий.

Шаблон 3: Контекст рисования

Это было предложенопод редакцией Шелдона Цзяо из службы поддержки Microsoft на форуме MSDN WPF здесь .См. Стр. 494, Глава 15 «2D-графика» в WPF 4 Адама Натана «Unleashed» для описания DrawingContext.Не могу сказать, что понимаю это.Согласно ответу здесь , я бы предположил, что это улучшило бы обработку чертежей геометрии, а не растровых изображений.Далее, я не думаю, что это поддержит требования к фокусу и событиям для изображений (мое плохо из-за того, что я не объясняю требования лучше на форуме). Кроме того, я обеспокоен кратким предложением книги: «Обратите внимание, что использование DrawingContextне меняет того факта, что вы работаете в системе с фиксированным режимом.Указанный рисунок не происходит сразу;Команды сохраняются в WPF до тех пор, пока они не понадобятся ». Это означает, что как только наш четный обработчик не сможет использовать преимущества параллелизма, как в« Фоновом предварительном извлечении ».

Шаблон 4: Доступный для записиБитовые карты

Документация MSDN здесь описывает его как систему с двойным буфером: ваш поток пользовательского интерфейса обновляет буфер;поток рендеринга WPF перемещает его в видеопамять.

Предполагаемое использование (см. здесь ) предназначено для растровых изображений, которые сильно изменяются, например, в видео-фильмах, таких как дисплей.Я не уверен, но возможно, что это может быть взломано и объединено с шаблоном предварительной выборки фона и использовано в сценарии сетки.

Шаблон 5: кэшированное растровое изображение

Не много информации о MSDN ( здесь ).В архиве форума WPF ( здесь ) объясняется, что «API BitmapCache предназначен для кэширования вашего контента (при рендеринге на аппаратном уровне) в видеопамяти, то есть он остается резидентным на вашем графическом процессоре.Это экономит затраты на повторный рендеринг этого контента при выводе его на экран ». Это кажется отличной идеей.Однако я не уверен, в чем заключаются подводные камни и как их использовать.

Pattern 6: RenderTargetBitmap

RenderTargetBitmap преобразует Visual в растровое изображение.Я не уверен, уместно ли это здесь.См. здесь .

Редактировать : Относительно вопроса Пола Хенеке: я написал, что «мое приложение должно отображать много растровых изображений».Я не упомянул, что мне нужно отображать около 800 изображений одновременно .

О проблемах производительности, связанных с моими вопросами SO * можно прочитать Производительность WPF Bitmap и Как сделать отображение изображений в WPF более «быстрым»?

Я изменил описание шаблона 1, чтобы подчеркнуть концепцию, что элементы управления изображениями не создаются и не удаляются (если мы не хотимотображать большую или меньшую сетку).Только их Источники настроены на разные, новые или нулевые BitmapSources.

Редактировать : Этот вопрос размещен на форуме поддержки WPF с некоторыми ответами от персонала MS.

1 Ответ

19 голосов
/ 27 февраля 2012

Я не могу найти конкретный вопрос в вашем сообщении, кроме как попросить комментарии к подходам ниже.Я не буду утверждать, что знаю все вышеизложенное, но я расскажу вам, что я знаю, работая некоторое время, разрабатывая высокопроизводительные пользовательские интерфейсы с использованием WPF и Silverlight.

Шаблон 0: Взлом.Объединяя все в одно изображение

Я бы избежал этого, если это возможно.Звучит так, будто вы хотите отобразить большую панель с тысячами маленьких изображений.Поэтому каждое изображение является миниатюрой (поскольку вы не можете одновременно отобразить 1000 больших изображений).В результате я бы рекомендовал кэширование / изменение размера по комбинации.

Шаблон 1: Уменьшить размер изображения

Если вы отображаете на экране 1000 изображений одновременно, рассмотрите доступную экранную недвижимость.Средний монитор составляет 1280x1024 пикселей или чуть более 1,3 Мпикселя.1000 изображений предполагают, что вы получите максимальный размер 1300 пикселей на изображение или 36 * 36.Допустим, ваши изображения размером 32 * 32.Вы определенно должны создать миниатюру этого размера изображения для отображения на экране, а затем при щелчке (или другом действии) отобразить полноразмерное изображение.

Также учитывайте не только издержки рендеринга при изменении размера большого изображения, но и отправку большого изображения в графический процессор для изменения размера.Эти данные требуют пропускной способности для отправки.Большое изображение может составлять несколько мегабайт, тогда как миниатюра размером 32 * 32 может составлять несколько килобайт.

Если вам требуется динамический размер, хорошо, но вам нужно будет поэкспериментировать с созданием нескольких миниатюр или генерацией их на лету.

Шаблон 2: Предварительная выборка фона

Это метод, о котором я не слышал, однако он кажется правдоподобным.Каковы накладные расходы в вашем приложении?обновляет ли оно свойство Image.Source или создает новое изображение, тесселяет, выполняет компоновку и отправляет информацию для ее рендеринга в графический процессор?

Все вышеперечисленное происходит на процессоре, за исключением окончательного рендеринга.Сокращая накладные расходы на стороне процессора и обновляя источник, вы можете быть к чему-то.Объедините это с WriteableBitmap в качестве источника, и вы сможете еще больше повысить производительность (см. Ниже).

Шаблон 3: Контекст рисования

Хорошо, все, что это делает, это позволяет вам ставить в очередь вызовы рисования в сохраненном режиме, используя синтаксис стиля "OnPaint", который не имеет ничего общего сстарый GDI OnPaint.По моему опыту, OnRender не улучшает производительность, но он обеспечивает гибкую гибкость по сравнению с тем, что рисуется и когда.OnRender предоставляет вам контекст, который имеет функцию DrawImage, позволяющую рисовать BitmapSource в конвейере рендеринга без необходимости использования элемента управления Image.Это хорошо, так как устраняет некоторые накладные расходы, однако создает проблемы, аналогичные тем, которые наблюдаются в Pattern0 (вы потеряете макет и вам придется вычислять положение всех ваших изображений).Если вы сделаете это, вы можете использовать шаблон 0, против которого я рекомендовал.

Схема 4: Доступные для записи растровые изображения

WriteableBitmaps - немного используемая и чрезвычайно мощная подсистема в WPF.Я эффективно использую их для создания компонента диаграмм, способного отображать большие объемы данных в режиме реального времени.Я хотел бы предложить проверить кодексный проект WriteableBitmapEx Раскрытие, я однажды внес свой вклад в это и посмотреть, сможете ли вы объединить его с другими шаблонами.В частности, функция Blit, которая позволит вам записывать кэшированное растровое изображение в источник растрового изображения на изображении.

Например, хорошей техникой может быть Шаблон 1 + 2 + 4.

Вы можете иметь сетку из N элементов управления изображением на экране в определенных местах в элементе управления сеткой.Каждый из них является статическим и не прокручивается из поля зрения, поэтому никаких творений / удалений не происходит.Теперь, в дополнение к этому, измените размер вашего изображения и запишите в WriteableBitmap, который установлен как свойство Source для каждого изображения.При прокрутке получите следующие / предыдущие эскизы и обновите источники, используя WriteableBitmapEx.Blit.Pow!виртуализированная, кэшированная, многопоточная обработка изображений.

Шаблон 5: Кэшированное растровое изображение

Это попытка Microsoft сделать 1 + 2 + 4, как я обсуждал выше.То, что он пытается сделать, это после компоновки (сторона ЦП), тесселяции (сторона ЦП), отправки инструкций рендеринга в сохраненном режиме в ГП (сторона ЦП) и рендеринга (сторона ГП), он кэширует растровое изображение визуализированного элемента, которыйиспользуется на следующем проходе рендеринга.Да, малоизвестный факт о WPF заключается в том, что замечательный движок с графическим процессором работает ужасно медленно, так как большую часть своей работы выполняет на процессоре: P

Я бы поэкспериментировал с BitmapCache и посмотрел, как он работает.Есть предостережения, и они заключаются в том, что когда вы обновляете ваш UIElement, он должен воссоздать кеш, чтобы статические элементы работали намного лучше, чем динамические.Кроме того, я не видел значительного улучшения производительности от использования этого, тогда как методы стиля WriteableBitmap могут дать порядок улучшения.

Шаблон 6: RenderTargetBitmap

Этот последний метод позволяет вам визуализировать UIElement в растровое изображение - вы знаете, что - но интересно то, что это может выполнить беднякгенератор миниатюр (или изменение размера).Например, установите Image с BitmapSource вашего полноразмерного изображения.Теперь установите размер элемента управления изображения на 32 * 32 и рендеринга в растровое изображение.Вуаля!У вас есть миниатюра BitmapSource для использования в сочетании с некоторой заменой (Pattern 2) и / или записываемыми растровыми изображениями.

Хорошо, наконец, просто хотел сказать, что требование, которое у вас есть, подтолкнет WPF к своим пределам, однако есть способы заставить его выполнить.Как я уже сказал, у меня есть системы сборки, которые отображают тысячи или миллионы элементов на экране одновременно, используя замечательный обходной путь - WriteableBitmap.Переход по стандартному маршруту WPF приведет к адской производительности, поэтому вам придется сделать что-то экзотическое, чтобы решить эту проблему.

Как я уже сказал, моя рекомендация - 1 + 2 + 4.Вы должны изменить размер миниатюры, в этом я не сомневаюсь.Идея иметь статическую сетку элементов управления изображением и обновлять источники очень хороша.Идея использования WriteableBitmap (в частности, блитовая функция WriteableBitmapEx) для обновления источников также заслуживает изучения.

Удачи!

...