1

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

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

这一定是可能的,但到目前为止,我的 google-foo 让我失望了……

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

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

我正在绘制的数据来自浮点数组。没有什么可以限制我会得到多少,所以我只想忘记数据,因为它会从情节的一侧掉下来。

我目前从 a 继承System::Windows::Forms::UserControl,但如果有更好的选择,可以切换到其他东西吗?

4

5 回答 5

5

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

于 2009-06-28T22:34:54.563 回答
1

一个可能的策略:

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

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

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

  • 将图像传送到屏幕时,请确保 Graphics 实例上的 CompositingMode 设置为 bmpg.CompositingMode = CompositingMode.SourceCopy,并且后台缓冲区的像素格式为 PixelFormat.Format32bppPArgb。
于 2009-08-07T12:08:06.667 回答
1

位图 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()

于 2009-07-28T09:25:44.423 回答
0

我不太清楚您要绘制的确切内容(对话框上的某种控制?)但猜测它应该像这样工作:

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 个像素行可能效率不高。你最好画一个大的区域,只根据需要显示它的一部分——尽管这显然取决于你的数据是如何进入的。

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

于 2009-06-28T22:50:59.150 回答
0

尝试以下操作:

  • 启动一个新的 VC++ WinForms 应用程序。
  • 将名为“Spectrogram”的用户控件添加到项目中
  • 将计时器控件添加到“频谱图”用户控件并将“启用”属性设置为 true
  • 将以下私有变量添加到“频谱图”用户控件
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);    
  • 将以下代码添加到“频谱图”计时器滴答事件

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

这应该让您大致了解位图缓冲控件。这里的关键是构造函数中的“SetStyle”调用和绘制事件中位图的偏移量-1。

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

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

于 2009-06-29T02:04:58.803 回答