如何写入(位图?)图像缓冲区以获得更快的 GDI+ 显示?

发布于 2024-07-25 14:47:56 字数 894 浏览 7 评论 0原文

使用 C++ 和 .net,我有一个数据流,我想将其显示为滚动图像。 每次获得一些新数据时,我都想将其添加为新行(128x1 像素)并将之前的内容滚动到一侧。

我对这个问题的第一次尝试涉及每次获得新行时渲染整个数据集。 这有效,但太慢了,所以我认为写入某种缓冲区(也许是位图?)可能更有意义。 问题是我不知道如何做到这一点; Graphic 对象允许您非常愉快地绘制,但我看不到一种明显的方法来告诉我的控件使用 Bitmap 对象作为缓冲区? 同样,我看不到一种在位图上绘制然后可以写入屏幕的方法。

这一定是可能的,但到目前为止我的 google-foo 已经让我失败了...

[Edit1] 只是澄清一下,数据是一个频谱图。 下图显示了我试图实现的目标:

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

我正在绘制的数据来自在浮点数数组中。 没有什么可以限制我得到的数量,所以我只想忘记数据,因为它从图的一侧掉落。

我目前正在继承 System::Windows::Forms::UserControl,但如果有更好的替代方案,可以切换到其他内容吗?

Using C++ and .net I have a stream of data I want to display as a scrolling image. Each time I get some new data I want to add it as a new row (128x1 pixels) and scroll the previous contents to one side.

My first stab at the problem involved rendering the entire data set each time I got a new row. This worked, but was far too slow, so I'm thinking it might make more sense to write to a buffer of some sort (a bitmap maybe?). The problem is that I can't see how I can do that; Graphic objects allow you to draw quite happily, but I can't see an obvious way to tell my control to use a Bitmap object as it's buffer? Similarly, I can't see a way to draw on a bitmap which I could then write to the screen.

This must be possible, but my google-foo has failed me thus far...

[Edit1] Just to clarify, the data is a spectrogram. The following image shows the sort of thing I was trying to achieve:

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

The data I'm plotting comes in arrays of floats. There's nothing to limit how many I'll get, so I just want to forget data as it drops off the side of the plot.

I'm currently inheriting from a System::Windows::Forms::UserControl, but could switch to something else if there's a better alterative?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

混浊又暗下来 2024-08-01 14:47:56

查看 ScrollWindow win32 方法。 您可以滚动屏幕上的现有数据,然后仅绘制新数据。 这非常快。

Take a look at the ScrollWindow win32 method. You can scroll the existing data on the screen and then draw only the new data. This is very fast.

黎歌 2024-08-01 14:47:56

一种可能的策略:

  • 从左到右绘制后备缓冲区,到达末尾时会环绕。 仅在绘制到屏幕时(以某些指定的帧速率)执行滚动逻辑。 使用带有源矩形和目标矩形的 DrawImage 来实现此目的。

  • 使用bitmap.LockBits(...)和bitmap.UnlockBits(...)方法修改原始Bitmap数据。 请小心,仅锁定要修改的矩形,因为这些函数实际上将位图数据从非托管内存复制到托管内存。 这里描述了如何执行此操作的示例 位图 ..::.LockBits 方法(矩形、ImageLockMode、PixelFormat)

  • LockBits 的替代方法是在位图上使用 SetPixel。 但众所周知,SetPixel 速度很慢。

  • 将图像传输到屏幕时,请确保 Graphics 实例上的 CompositingMode 设置为 bmpg.CompositingMode = CompositingMode.SourceCopy,并且后台缓冲区的像素格式为 PixelFormat.Format32bppPArgb。

A possible strategy:

  • Draw on a backbuffer from left to right which wraps around when reaching the end. Perform scrolling logic only when painting to screen (at some specified framerate). Use DrawImage with source rectangle and destination rectangle to achieve this.

  • Use bitmap.LockBits(...) and bitmap.UnlockBits(...) method to modify the raw Bitmap data. Be careful to only lock the rectangle you are going to modify, since these functions actually makes copies of the bitmap data from unmanaged to managed memory. An example on how to do this is described here Bitmap..::.LockBits Method (Rectangle, ImageLockMode, PixelFormat).

  • An alternative to LockBits is to use SetPixel on the bitmap. But SetPixel is known to be slow.

  • When blitting images to the screen make sure that CompositingMode on Graphics instance is set to bmpg.CompositingMode = CompositingMode.SourceCopy, and that pixel format of the back buffer is PixelFormat.Format32bppPArgb.
落在眉间の轻吻 2024-08-01 14:47:56

位图 bmpImage = new Bitmap(512,512);

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

{

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

}

(Image)bmpImage.save()

Bitmap bmpImage = new Bitmap(512,512);

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

{

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

}

(Image)bmpImage.save()

找回味觉 2024-08-01 14:47:56

我不太清楚你到底想画什么(对话框上的某种控制?),但猜测它应该像这样工作:

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, ...);
}

无论如何,这是一个非常粗略的猜测。 如果您使用 .NET(我不熟悉它...),DrawItem 调用可能看起来完全不同,但基本逻辑应该大致相同。

根据您的数据的具体内容,一次绘制 1 个像素行可能效率不高。 您可能最好绘制一个大区域并仅根据需要显示其中的一些部分 - 尽管这显然取决于数据的输入方式。

您可能还需要对位图进行某种更新以“滚动”其内容。 我把这个留给你:-)

I'm not quite clear on exactly what you're trying to draw (some kind of control on a dialog?) but at a guess it should work something like:

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, ...);
}

That's a very rough guess at it anyway. The DrawItem call might look quite different if you're using .NET (I'm not familiar with it...), but the basic logic should be approximately the same.

Depending on what exactly your data is, it may not be efficient to draw 1 pixel rows at a time. You may be better off drawing a large area and only showing bits of it as required - although that will obviously depend on how your data comes in.

You also might need to do some kind of update to your bitmap to "scroll" its contents. I'll leave that up to you :-)

冷月断魂刀 2024-08-01 14:47:56

请尝试以下操作:

  • 启动新的 VC++ WinForms 应用程序。
  • 将名为“Spectrogram”的用户控件添加到项目中
  • 将计时器控件添加到“Spectrogram”用户控件并将“Enabled”属性设置为 true
  • 将以下私有变量添加到“Spectrogram”用户控件
private:
Graphics ^m_gfxBuffer;
Graphics ^m_gfxOriginal;
Bitmap ^m_bmpBuffer;
Bitmap ^m_bmpOriginal;
  • 将以下代码添加到“ Spectrogram' 构造函数:

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();
  • 将以下代码添加到“Spectrogram”绘制事件:

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);    
  • 将以下代码添加到“Spectrogram”计时器刻度事件

this->Invalidate(false);
  • 保存项目
  • 清理并重建
  • 运行项目
  • 关闭正在运行的窗体
  • Spectrogram 用户控件现在应位于“ 将其从“工具箱
  • ”拖到表单中,您应该会看到一个滚动的随机彩色频谱图。

这应该让您对位图缓冲控件有一个总体了解。 这里的关键是构造函数中的“SetStyle”调用以及绘制事件中位图的偏移量-1。

您必须正确处理图形和位图对象,并在调整大小事件中处理销毁和重建它们。

希望这可以帮助。 让我知道事情的后续。

Try the following:

  • Start a new VC++ WinForms application.
  • Add a user control named 'Spectrogram' to the project
  • Add a timer control to the 'Spectrogram' user control and set the 'Enabled' property to true
  • Add the following private variables to the 'Spectrogram' user control
private:
Graphics ^m_gfxBuffer;
Graphics ^m_gfxOriginal;
Bitmap ^m_bmpBuffer;
Bitmap ^m_bmpOriginal;
  • Add the following code to the 'Spectrogram' constructor:

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();
  • Add the following code to the 'Spectrogram' paint event:

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);    
  • Add the following code the 'Spectrogram' timers tick event

this->Invalidate(false);
  • Save your project
  • Clean and Rebuild
  • Run the project
  • Close the running Form
  • The Spectrogram user control should now be in the 'Toolbox'
  • Drag it from the 'Toolbox' to the form and you should see a scrolling random colored Spectrogram of sorts.

This should give you a general idea of a bitmap buffered control. The key here is the "SetStyle" call in the constructor and the offset of the bitmap by -1 in the paint event.

You will have to properly dispose of the graphics and bitmap objects as well as handle destroying and rebuilding them in the resize event.

Hope this helps. Let me know how it goes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文