Ответ 1
Да, это слишком медленно.
Я столкнулся с этой проблемой несколько лет назад при разработке Paint.NET(с самого начала, на самом деле, и это было довольно неприятно!). Производительность рендеринга была ужасной, так как она всегда была пропорциональна размеру растрового изображения, а не размеру области, которую ему было предложено перерисовать. То есть, частота кадров снизилась по мере увеличения размера растрового изображения, и частота кадров никогда не увеличивалась, так как размер области недействительной/перерисовывающей области снижался при реализации OnPaint() и вызывал Graphics.DrawImage(). Небольшое растровое изображение, скажем, 800x600, всегда работало нормально, но большие изображения (например, 2400x1800) были очень медленными. (В любом случае вы можете предположить, что в предыдущем абзаце ничего не происходило, например, масштабирование с помощью какого-то дорогого бикубического фильтра, что отрицательно сказалось бы на производительности.)
Можно заставить WinForms использовать GDI вместо GDI + и избежать даже создания объекта Graphics
за кулисами, после чего вы можете наложить на него еще один инструментарий рендеринга (например, Direct2D). Однако это не просто. Я делаю это в Paint.NET, и вы можете увидеть, что требуется, используя что-то вроде Reflector в классе с именем GdiPaintControl
в DLL SystemLayer, но за то, что вы делаете, я считаю его последним средством.
Однако размер растрового изображения, который вы используете (800x1200), должен по-прежнему работать достаточно хорошо в GDI +, не прибегая к передовому взаимодействию, если вы не нацеливаете что-то столь же низко, как Pentium II с частотой 300 МГц. Вот несколько советов, которые могут помочь:
- Если вы используете непрозрачный растровый рисунок (нет альфа/прозрачности) в вызове
Graphics.DrawImage()
, и особенно если это 32-битная растровая карта с альфа-каналом (но вы знаете, что это непрозрачно, или вам все равно), затем установитеGraphics.CompositingMode
вCompositingMode.SourceCopy
перед вызовомDrawImage()
(после этого обязательно верните его к исходному значению, иначе обычные примитивы рисования будут выглядеть очень уродливыми). Это пропускает много дополнительного смешивания по каждому пикселю. - Убедитесь, что
Graphics.InterpolationMode
не настроено на что-то вродеInterpolationMode.HighQualityBicubic
. ИспользованиеNearestNeighbor
будет самым быстрым, хотя, если есть растягивание, оно может выглядеть не очень хорошо (если оно не растягивается ровно в 2x, 3x, 4x и т.д.)Bilinear
обычно является хорошим компромиссом. Вы не должны использовать ничего, кромеNearestNeighbor
, если размер растрового изображения соответствует области, в которую вы рисуете, в пикселях. - Всегда рисуйте объект
Graphics
, указанный вам вOnPaint()
. - Всегда делайте рисунок в
OnPaint
. Если вам нужно перерисовать область, вызовитеInvalidate()
. Если вам нужно, чтобы рисунок появился прямо сейчас, вызовитеUpdate()
послеInvalidate()
. Это разумный подход, поскольку сообщения WM_PAINT (что приводит к вызовуOnPaint()
) являются сообщениями с низким приоритетом. Любая другая обработка оконным менеджером будет выполнена в первую очередь, и, таким образом, вы можете столкнуться с большим количеством пропусков кадров и сцепления в противном случае. - Использование
System.Windows.Forms.Timer
в качестве таймера частоты кадров/тика не будет работать очень хорошо. Они реализуются с использованием Win32SetTimer
и приводят к сообщениям WM_TIMER, которые затем приводят к возникновению событияTimer.Tick
, а WM_TIMER - другое сообщение с низким приоритетом, которое отправляется только тогда, когда очередь сообщений пуста. Вам лучше использоватьSystem.Threading.Timer
, а затем использоватьControl.Invoke()
(чтобы убедиться, что вы находитесь в правильном потоке!) И вызываетеControl.Update()
. - В общем, не используйте
Control.CreateGraphics()
. (следствие "всегда рисовать вOnPaint()
" и "всегда использоватьGraphics
, предоставленный вамOnPaint()
') - Я не рекомендую использовать обработчик событий Paint. Вместо этого внесите
OnPaint()
в класс, который вы пишете, который должен быть получен изControl
. Вывод из другого класса, например.PictureBox
илиUserControl
, либо не добавит вам никакого значения, либо добавит дополнительные служебные данные. (BTWPictureBox
часто неправильно понимается. Вероятно, вы почти никогда не захотите его использовать.)
Надеюсь, что это поможет.