Использование растрового изображения в прыгающих мячах

У меня есть WinForm приложение "Bouncing Balls", и мне нужно нарисовать мячи на bitmap и представить растровое изображение в этой форме.

У меня есть plusButton, который добавляет новый мяч, и я сохраняю каждый новый мяч в списке.

Теперь метод Form_Paint говорит каждому шару рисовать себя, он работает нормально, пока шаров не станет много и все приложение станет очень медленным.

Вот мой код:

Метод рисования кода формы:

 private void Form1_Paint(object sender, PaintEventArgs e)
 {
     ballsArray.drawImage(bmp,e, ClientRectangle);
 }

ПРИМЕЧАНИЕ: ballsArray относится к типу AllBalls, это класс, который обертывает методы мяча, внутри его c'tor я создаю список, в котором хранится каждый мяч. bmp создается при загрузке формы - по методу Form_Load().

DrawImage кода ballsArray:

 public void drawImage(Bitmap bmp,PaintEventArgs e, Rectangle r)
 {
     foreach (Ball b in allBalls)
     {
         b.drawImage(bmp,e, r);
     }
 }

drawImage кода мяча:

  public void drawImage(Bitmap bmp, PaintEventArgs e, Rectangle r)
  {
      using (Graphics g = Graphics.FromImage(bmp))
      {
          e.Graphics.FillEllipse(brush, ballLocation);
          g.DrawImage(bmp, 0, 0);
      }
  }

ПРИМЕЧАНИЕ: ballLocation – это прямоугольник, обозначающий положение мяча на каждом этапе движения.

Так что я делаю неправильно? Что заставляет приложение работать медленно?

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


person Elior    schedule 13.01.2013    source источник
comment
Вы получите гораздо лучшую производительность, передав объект Graphics события Form1_Paint в метод drawImage и нарисовав свой эллипс непосредственно на нем, вместо того, чтобы создавать новый Graphics для каждого шара.   -  person Rotem    schedule 13.01.2013
comment
@Rotem, хорошо, вы имеете в виду, что я должен переместить Graphics g = Graphics.FromImage(bmp) в Form1_Paint и отправить этот объект в метод drawImage?   -  person Elior    schedule 13.01.2013
comment
Не совсем. Снова глядя на ваш код, я не понимаю, почему вы рисуете на растровом изображении, а не непосредственно на объекте Graphics.   -  person Rotem    schedule 13.01.2013
comment
@Rotem это домашняя задача, и одним из ее ограничений является использование растрового изображения для рисования всех шаров ... так что я сделал приведенный выше код, но он становится медленным, когда я добавляю много шаров ..   -  person Elior    schedule 13.01.2013
comment
Для меня это не имеет смысла, вы просто рисуете растровое изображение на себя.   -  person Rotem    schedule 13.01.2013
comment
Я имею в виду, что каждый раз, когда вызывается Form1_Paint, мне нужно рисовать каждый шар на растровом изображении, а затем рисовать растровое изображение на форме.   -  person Elior    schedule 13.01.2013


Ответы (3)


Некоторые основные методы, чтобы сделать это быстро:

  • Не подвергайте себя двойной буферизации и особенно не делайте двойной буферизации дважды. Двойная буферизация, которую вы получаете, устанавливая для свойства DoubleBuffer формы значение true, превосходит любую двойную буферизацию, которую вы бы сделали сами. Буфер оптимизирован для эффективной работы с настройками вашего видеоадаптера. Так что полностью отбросьте вашу переменную bmp и нарисуйте e.Graphics, которую вы получили из аргумента обработчика события Paint.

  • Вы не используете переданный аргумент r. Возможно, предназначен для поддержки отсечения невидимых шаров. Тот, который вы хотите передать, - это e.ClipRectangle, вы можете пропустить рисование шаров, которые полностью находятся за пределами этого прямоугольника. Хотя это оптимизация, она не очень полезна, когда вы используете тему Aero, и вы получаете непостоянные скорости перерисовки, поэтому вы можете пропустить ее.

  • Не очень понятно, почему вы используете и Graphics.FillEllipse, и Graphics.DrawImage, когда рисуете мяч. Изображение должно перекрывать круг, поэтому просто удалите FillEllipse.

  • Обратите особое внимание на объект Bitmap, в котором хранится изображение мяча. Первое, что вы хотите убедиться, это то, что он нарисован с точным размером изображения, поэтому его не нужно масштабировать. Масштабирование очень дорогое. Хотя в вашем вызове DrawImage() нет масштабирования, вы все равно получите его, если разрешение растрового изображения не совпадает с разрешением вашего видеоадаптера. Следующий шаг решит эту проблему

  • Пиксельный формат растрового изображения мяча очень важен. Вам нужен тот, который позволяет копировать растровое изображение прямо в видеопамять без преобразования формата. На любой современной машине этот формат — PixelFormat.Format32bppPArgb. Разница огромна, он рисует в десять раз быстрее, чем любой другой. Вы не получите этот формат из добавленного ресурса изображения, вам придется создать это растровое изображение при запуске вашей программы. Проверьте этот ответ для получения необходимого кода.

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

person Hans Passant    schedule 13.01.2013
comment
Вы хотите сказать, что Format32bppPArgb быстрый, а Format32bppArgb нет? - person Rotem; 13.01.2013
comment
Да, Argb в десять раз медленнее на любой машине, на которой я тестировал. - person Hans Passant; 13.01.2013
comment
правда ли, что форма имеет свой битмап?! я имею в виду, когда форма загружается, он рисует себя.. - person Elior; 13.01.2013
comment
Когда вы устанавливаете для свойства DoubleBuffered значение true, то e.Graphics, которую вы получаете в событии Paint, относится к растровому изображению, а не к экрану. Это растровое изображение переносится на экран после завершения события Paint, благодаря чему достигается эффект двойной буферизации. - person Hans Passant; 13.01.2013

DrawImage на Paint (или, если уж на то пошло, на MouseMove) — очень плохой дизайн.

Graphics.DrawImage — дорогая операция, а для экрана — очень дорогая . Чтобы улучшить взаимодействие с пользователем (медленность), вы должны рисовать события MouseDown/MouseUp.

Кроме того, сначала нарисуйте MemoryBuffer в вашем методе drawImage, а после подготовки окончательного изображения нарисуйте его один раз в пользовательском интерфейсе. Этот метод известен как двойная буферизация.

Не мерцайте! Двойной буфер! - КодПрожект

Кроме того, вы также можете посмотреть BitBlit Собственный API для быстрой передачи цвета/изображения на экран.

Минималистичный пример C# приведен здесь.

person Tilak    schedule 13.01.2013
comment
спасибо, но я включил двойную буферизацию .. нужно делать то же самое, что вы предложили, нет? - person Elior; 13.01.2013
comment
Вам нужно сделать это вручную.e.Graphics.FillEllipse(brush, ballLocation); это строка, на которой вы должны сосредоточиться. - person Tilak; 13.01.2013

Включите двойную буферизацию в форме (DoubleBuffered = true).

person Brett Wolfington    schedule 13.01.2013