+2  A: 

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.

Paul Alexander
Does that mean the image would have to grow each time I add new data?
Jon Cage
No...the ScrollWindow does exactly what you want. It scrolls the existing content left within a clipping region - for example the CPU graph in the Task Manager.
Paul Alexander
This sounds like a neat wy to do what I want, but my System::Windows::Forms::UserControl derived class doesn't appear to have a ScrollWindow function. Do I need to add a scroll bar to it or use a different base class?
Jon Cage
It's a Win32 method. You have to use PInvoke.
Paul Alexander
A: 

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 :-)

Peter
Yes, this will be a custom control on a dialog.
Jon Cage
A: 

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.

A: 

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

+1  A: 

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.
BeeWarloc