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?
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.
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()
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 :-)
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.