Как записать в буфер изображений (битмап?) для более быстрого отображения GDI+?

Используя С++ и .net, у меня есть поток данных, который я хочу отобразить в виде прокручиваемого изображения. Каждый раз, когда я получаю новые данные, я хочу добавить их в виде новой строки (128x1 пикселей) и прокрутить предыдущее содержимое в одну сторону.

Моя первая попытка решить эту проблему заключалась в рендеринге всего набора данных каждый раз, когда я получал новую строку. Это сработало, но было слишком медленно, поэтому я думаю, что было бы разумнее записывать в какой-то буфер (может быть, в растровое изображение?). Проблема в том, что я не понимаю, как я могу это сделать; Объекты Graphic позволяют вам довольно хорошо рисовать, но я не вижу очевидного способа указать моему элементу управления использовать объект Bitmap в качестве буфера? Точно так же я не вижу способа рисовать на растровом изображении, которое я мог бы затем вывести на экран.

Это должно быть возможно, но мой google-foo пока не подвел меня...

[Edit1] Чтобы уточнить, данные представляют собой спектрограмму. На следующем изображении показано, чего я пытался достичь:

http://www.geekops.co.uk/photos/0000-00-02%20(Forum%20images)/ScrollingGraphicsAlgorithmExplanation.png

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

В настоящее время я наследую от System::Windows::Forms::UserControl, но могу ли я переключиться на что-то другое, если есть лучшая альтернатива?


person Jon Cage    schedule 28.06.2009    source источник


Ответы (5)


Взгляните на метод Win32 ScrollWindow. Вы можете прокручивать существующие данные на экране, а затем рисовать только новые данные. Это очень быстро.

person Paul Alexander    schedule 28.06.2009
comment
Означает ли это, что изображение должно увеличиваться каждый раз, когда я добавляю новые данные? - person Jon Cage; 29.06.2009
comment
Нет... ScrollWindow делает именно то, что вы хотите. Он прокручивает существующий контент, оставленный в области отсечения, например график ЦП в диспетчере задач. - person Paul Alexander; 29.06.2009
comment
Это звучит как изящный способ сделать то, что я хочу, но мой производный класс System::Windows::Forms::UserControl не имеет функции ScrollWindow. Нужно ли добавлять к нему полосу прокрутки или использовать другой базовый класс? - person Jon Cage; 29.06.2009
comment
Это метод Win32. Вы должны использовать PInvoke. - person Paul Alexander; 29.06.2009

Растровое изображение bmpImage = новое растровое изображение (512 512);

for (int iRow = 0; iRow ‹ 512; iRow++)

{

  for (int iCol = 0; iCol <512; iCol++)
                        {
                            Color clr;
                            bmpImage.SetPixel(iCol, iRow, clr);
                        }

}

(Изображение)bmpImage.save()

person Community    schedule 28.07.2009

Возможная стратегия:

  • Нарисуйте задний буфер слева направо, который закручивается по мере достижения конца. Выполнять логику прокрутки только при рисовании на экране (с определенной частотой кадров). Для этого используйте DrawImage с исходным прямоугольником и прямоугольником назначения.

  • Используйте методы bitmap.LockBits(...) и bitmap.UnlockBits(...) для изменения необработанных данных Bitmap. Будьте осторожны, чтобы заблокировать только прямоугольник, который вы собираетесь изменить, поскольку эти функции фактически делают копии данных растрового изображения из неуправляемой в управляемую память. Пример того, как это сделать, описан здесь Bitmap..::.LockBits Method (Прямоугольник, ImageLockMode, PixelFormat).

  • Альтернативой LockBits является использование SetPixel на растровом изображении. Но известно, что SetPixel работает медленно.

  • При переносе изображений на экран убедитесь, что для параметра CompositingMode в экземпляре Graphics установлено значение bmpg.CompositingMode = CompositingMode.SourceCopy, а формат пикселей заднего буфера — PixelFormat.Format32bppPArgb.
person BeeWarloc    schedule 07.08.2009

Я не совсем понимаю, что именно вы пытаетесь нарисовать (какой-то элемент управления в диалоговом окне?), но, по-видимому, это должно работать примерно так:

class Foo {
    ...
    Gdiplus::Bitmap* m_pBitmap;
};

void Foo::DrawItem(LPDRAWITEMSTRUCT lpDraw) {

   // update bitmap if needed
   if(some_condition_requiring_bitmap_redraw) {

       // do expensive drawing into bitmap
       Gdiplus::Graphics graphics(m_pBitmap);
   }


   // create a graphics object to draw the control from the bitmap
   Gdiplus::Graphics graphics(lpDraw->hDC);
   graphics.DrawImage(m_pBitmap, ...);
}

В любом случае, это очень грубое предположение. Вызов DrawItem может выглядеть совершенно по-другому, если вы используете .NET (я не знаком с ним...), но основная логика должна быть примерно такой же.

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

Вам также может понадобиться обновить растровое изображение, чтобы «прокрутить» его содержимое. Оставлю это на ваше усмотрение :-)

person Peter    schedule 28.06.2009
comment
Да, это будет настраиваемый элемент управления в диалоговом окне. - person Jon Cage; 29.06.2009

Попробуйте следующее:

  • Запустите новое приложение VC++ WinForms.
  • Добавьте в проект пользовательский элемент управления Spectrogram.
  • Добавьте элемент управления таймером в пользовательский элемент управления «Спектрограмма» и установите для свойства «Включено» значение true.
  • Добавьте следующие частные переменные в пользовательский элемент управления «Спектрограмма».
private:
Graphics ^m_gfxBuffer;
Graphics ^m_gfxOriginal;
Bitmap ^m_bmpBuffer;
Bitmap ^m_bmpOriginal;
  • Добавьте следующий код в конструктор «Спектрограмма»:

m_bmpBuffer = gcnew Bitmap(this->ClientSize.Width, this->ClientSize.Height);
m_gfxBuffer = Graphics::FromImage(m_bmpBuffer);
m_bmpOriginal = gcnew Bitmap(this->ClientSize.Width, this->ClientSize.Height);
m_gfxOriginal = Graphics::FromImage(m_bmpOriginal);
this->SetStyle(::ControlStyles::AllPaintingInWmPaint | ::ControlStyles::DoubleBuffer | ::ControlStyles::UserPaint | ::ControlStyles::OptimizedDoubleBuffer, true);
this->UpdateStyles();
  • Добавьте следующий код в событие рисования «Спектрограмма»:

array<unsigned char, 1> ^bytes = gcnew array<unsigned char, 1>(m_bmpBuffer->Height * 3);
Random ^r = gcnew Random();
r->NextBytes(bytes);

m_gfxOriginal->DrawImage(m_bmpBuffer, -1, 0);

int y = 0;
for (int i = 0; i < m_bmpOriginal->Height * 3; i += 3)
{
  m_bmpOriginal->SetPixel(m_bmpOriginal->Width - 1, y++, ::Drawing::Color::FromArgb(255, bytes[i], bytes[i + 1], bytes[i + 2]));
}

m_gfxBuffer->DrawImage(m_bmpOriginal, 0, 0);
e->Graphics->DrawImage(m_bmpOriginal, 0, 0);    
  • Добавьте следующий код в событие тика таймера «Спектрограмма»

this->Invalidate(false);
  • Сохраните свой проект
  • Очистить и восстановить
  • Запустить проект
  • Закрыть запущенную форму
  • Пользовательский элемент управления Spectrogram теперь должен находиться на панели инструментов.
  • Перетащите его из «Панель инструментов» в форму, и вы должны увидеть прокручивающуюся случайную цветную спектрограмму.

Это должно дать вам общее представление об элементе управления с буферизацией растрового изображения. Ключевым моментом здесь является вызов SetStyle в конструкторе и смещение растрового изображения на -1 в событии рисования.

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

Надеюсь это поможет. Дайте мне знать, как это происходит.

person Community    schedule 29.06.2009