Как быстро генерировать изображения с помощью .NET

#c# #.net #gdi #gdi

#c# #.net #gdi #gdi

Вопрос:

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

При написании этих строк мне просто пришла в голову мысль использовать библиотеку задач в .Net 4, чтобы выполнять БОЛЬШЕ работы, даже если каждая задача генерации не будет быстрее.

В любом случае, мысли и комментарии приветствуются.

Спасибо!

Комментарии:

1. Есть ли какой-то конкретный код, с которым у вас проблемы с производительностью, который вы хотели бы показать, чтобы у нас была основа для обсуждения?

Ответ №1:

Если вам нужна скорость raw, лучший вариант — использовать DirectX. Вероятно, следующим лучшим вариантом будет использовать GDI из C и предоставить управляемый интерфейс для его вызова. Затем, вероятно, использовать p / invoke для GDI непосредственно с C #, и, наконец, использовать GDI в C #. Но в зависимости от того, что вы делаете, вы можете не заметить большой разницы. Многопоточность может не помочь вам, если вы ограничены скоростью, с которой видеокарта может работать с помощью GDI , но может быть полезна, если вы ограничены процессором при разработке «что рисовать». Если вы печатаете много изображений последовательно, вы можете выиграть, запустив предварительный расчет, рендеринг и «фазы» печати в отдельных потоках.

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

Как вы можете минимизировать объем, который вы рисуете?

Во-первых, устраните ненужную работу. Подумайте о каждом элементе, который вы рисуете — действительно ли это необходимо? Часто более простой дизайн на самом деле может выглядеть лучше, экономя при этом много усилий по рендерингу. Подумайте, можно ли заменить градиентную заливку плоской заливкой или закругленный прямоугольник будет выглядеть приемлемо как обычный прямоугольник (и проверьте, дает ли это вообще какое-либо преимущество в скорости на вашем оборудовании, прежде чем выбрасывать его!)

Оспорьте свои предположения — например, требование «высокого разрешения» — часто, если вы печатаете на чем-то вроде принтера с заменой краски (этот процесс приводит к некоторому искажению цвета) или CMYK-принтера, который использует любую форму сглаживания для смешивания цветов (практическое разрешение которого намного ниже, чем шаг точек, который может разрешить принтер), изображение со сглаживанием с относительно низким разрешением часто может выдавать такой же хороший результат, как и изображение со сверхвысоким разрешением. Если вы выводите на черно-белый принтер с разрешением 2400 точек на дюйм, вы все равно можете обнаружить, что 1200 точек на дюйм или даже 600 точек на дюйм приемлемы (вы получаете постоянно уменьшающуюся отдачу по мере увеличения разрешения, и большинство людей не заметят разницы между 600 и 2400 dpi). Просто распечатайте несколько типичных примеров, используя разные исходные разрешения, чтобы увидеть, на что похожи результаты. Если вы сможете уменьшить разрешение вдвое, вы потенциально сможете выполнять рендеринг в 4 раза быстрее.

Обычно старайтесь избегать перерисовки одной и той же области — если вы хотите нарисовать черную рамку вокруг области, вы могли бы нарисовать белый прямоугольник внутри черного прямоугольника, но это означает, что все пиксели в середине заполняются дважды. Вы можете повысить производительность, нарисовав 4 черных прямоугольника снаружи, чтобы точно нарисовать рамку. И наоборот, если у вас много примитивов для рисования, можете ли вы уменьшить количество рисуемых примитивов? например Если вы рисуете много полос, вы можете нарисовать чередующиеся прямоугольники разных цветов (= 2n прямоугольников), или вы можете залить весь фон одним цветом, а затем нарисовать прямоугольники только для второго цвета (= n 1 прямоугольник). Сокращение числа отдельных вызовов методов GDI часто может обеспечить значительный выигрыш, особенно если у вас быстрое графическое оборудование.

Если вы рисуете какую-либо часть изображения более одного раза, рассмотрите возможность ее кэширования (визуализируйте ее в виде растрового изображения, а затем при необходимости добавляйте к вашему конечному изображению). Чем сложнее этот вложенный образ, тем больше вероятность, что его кэширование окупится. Например, если у вас есть повторяющийся шаблон, подобный линейке, не рисуйте каждую разметку линейки отдельной линией — визуализируйте повторяющийся участок линейки (например, 10 строк или 50), а затем прокручивайте этот предварительный просмотр всего несколько раз, чтобы нарисовать окончательную линейку.

Аналогичным образом, избегайте выполнения большого количества ненужной работы (например, многих вызовов MeasureString для значений, которые могут быть предварительно вычислены один раз или даже аппроксимированы. Или, если вы переходите по множеству значений Y, попробуйте сделать это, добавляя смещение на каждой итерации, а не повторно объединяя абсолютную позицию, используя mutliples каждый раз).

Попробуйте «пакетное» рисование, чтобы свести к минимуму количество необходимых изменений состояния и / или вызовов методов рисования — например, нарисуйте все элементы одного цвета / текстуры / кисти, прежде чем переходить к следующему цвету. Используйте вызовы «пакетной» визуализации (например, Нарисуйте примитив полилинии один раз, а не вызывайте DrawLine 100 раз).

Если вы выполняете какие-либо операции с каждым пикселем, то обычно намного быстрее захватить буфер необработанного изображения и манипулировать им напрямую, чем вызывать методы GetPixel / SetPixel.

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

И, конечно, посмотрите на код, с помощью которого вы визуализируете — профилируйте его и применяйте обычные оптимизации, чтобы помочь ему работать эффективно.

Наконец, наступает момент, когда вам следует подумать, может ли обновление оборудования быть дешевым и эффективным решением — если у вас медленный ПК и видеокарта низкого уровня, вы можете получить значительный выигрыш, просто купив новый компьютер с лучшей видеокартой в нем. Или, если изображения огромны, вы можете обнаружить, что увеличение объема оперативной памяти на пару ГБ устраняет накладные расходы на подкачку виртуальной памяти. Это может показаться дорогим, но наступает момент, когда соотношение затрат и выгод от нового оборудования оказывается лучше, чем тратить больше денег на дополнительную работу по оптимизации (и их постоянно снижающуюся отдачу).

Комментарии:

1. Несколько хороших предложений. В частности, я не думал о кэшировании, так сказать, повторно нарисованных областей. Работа с этим связана с получением образов с диска, поэтому накладные расходы на ввод-вывод довольно высоки. Я могу сократить это, «наблюдая» за повторными вызовами одного и того же изображения и сохраняя его в памяти некоторое время. Еще раз спасибо!

Ответ №2:

У меня есть несколько идей:

  1. Посмотрите на код в Paint.net . Это программа Paint с открытым исходным кодом, написанная на C #. Это может натолкнуть вас на несколько хороших идей. Вы, безусловно, могли бы сделать это в сочетании с идеями 2 и 3.

  2. Если задания нужно выполнить «немедленно», вы могли бы использовать что-то асинхронное для создания изображений. В зависимости от области применения в целом, вы можете даже использовать что-то вроде NServiceBus для постановки задачи в очередь компонента обработки изображений. Как только задача будет выполнена, отправляющий компонент получит уведомление посредством подписки на сообщение, опубликованное по завершении.

  3. Решение на основе задач подходит для отложенной обработки. Вы могли бы создавать изображения в пакетном режиме и использовать либо подход Task, либо что-то, называемое Quartz.net (http://quartznet.sourceforge.net ). Это планировщик заданий с открытым исходным кодом, который я использую для всех своих временных заданий.

Ответ №3:

Вы можете создать новое растровое изображение и выполнить блокировки (…), указав нужный формат пикселей. Как только у вас будут нужные биты, если вы хотите использовать неуправляемый код для рисования в нем, внесите библиотеку, закрепите данные в памяти и используйте эту библиотеку против него. Я думаю, вы можете использовать GDI для обработки необработанных пиксельных данных, но у меня сложилось впечатление, что система.Рисование — это уже тонкий слой поверх GDI . В любом случае, ошибаюсь я или нет, с помощью LockBits у вас есть прямой доступ к пиксельным данным, которые могут выполняться так быстро или медленно, как вы их программируете.

Как только вы закончите с рисованием, вы можете разблокировать BITS и viola, и у вас будет новое изображение.

Комментарии:

1. На самом деле, если вы ищете графику быстрого качества печати, возможно, вы могли бы использовать методы векторного рисования вместо растрового.

2. Спасибо, я не знал об этом. Я бы предоставил код, но, по-видимому, он намного превышает 600 или около того символов, которые предоставляет это поле.