Buffered images

BlitzMax Forums/Brucey's Modules/Buffered images

Vertex(Posted 2008) [#1]
Hi!
I want to draw an oscilloscope dynamically. I think it is better to use double buffered images to avoid flickering.

My idea:
A timer check all x milliseconds if a new audio signal is avariable. If it avraible then the oscilloscope is draw in a "Backbuffer Memory DC" and a OnPaint event is proceed manually.

On the OnPaint event the "Backbuffer Memory DC" is drawn in a Picturebox(wxStaticBitmap or wxImage?)

So how can I do that? Do I need a wxStaticBitmap or wxImage to draw a dynamic image? How can I create a memory DC and than show in the Picturebox?

That you can imagine what I want to do:


cu olli

Edit:


This is my first test. The DrawPanel should redefine its backgroundcolor every 100ms. But how can I release a OnPaint event manually?

And the DrawPanel is not fully redrawn oO


Brucey(Posted 2008) [#2]
instead of

Frame.Panel.OnPaint(XYZ)

try

Frame.Panel.Refresh()

In your timer notify. Refresh sets the panel to a "dirty" state, which is then set for repainting.

Also, you might be interested in replacing your wxPaintDC with wxAutoBufferedPaintDC which is a double-buffered version of it.
You simply use it as usual, and it sorts out the buffering. (or so they say!)

your create line would look like this :
DC        = New wxAutoBufferedPaintDC.CreatePaintDC(DrawPanel)



Vertex(Posted 2008) [#3]
Thank you!

With Refresh it works fine...

But however there are flickering:


Use space to switch between slow and quick mode. Or should I set some style attributes in Create call of the DrawPanel?

cu olli


Nigel Brown(Posted 2008) [#4]
no flicker here Leopard 10.5

edit: do get a crash after quitting? Only happens after you
have pressed space.


Brucey(Posted 2008) [#5]
For the GLcanvas I had to implement this to stop flicker on Win32:
void MaxGLCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
{
    // Do nothing, to avoid flashing on MSW
}

I wonder if this is the issue for your panel... Perhaps we need to override that method by default - where the default will be to call the superclass implementation (which would redraw the background). Overriding it you could possibly choose to do nothing....


Nigel Brown(Posted 2008) [#6]
Just re-tested this on PC and there is visible tearing that you don’t see on the MAC. And the code doesn’t error after exit. So that must be MAC specific?


Brucey(Posted 2008) [#7]
And the code doesn’t error after exit. So that must be MAC specific?

You need to Stop() the timer on Mac before you quit, otherwise the timer's parent object could be GC'd while the event itself is still live.


Brucey(Posted 2008) [#8]
Okay.. actually reading the docs on wxAutoBufferedDC reveals this snippet of useful information :

"This wxDC derivative can be used inside of an OnPaint() event handler to achieve double-buffered drawing. Just create an object of this class instead of wxPaintDC and make sure wxWindow::SetBackgroundStyle is called with wxBG_STYLE_CUSTOM somewhere in the class initialization code, and that's all you have to do to (mostly) avoid flicker.
"

Add this to the OnInit() of your custom panel.
SetBackgroundStyle(wxBG_STYLE_CUSTOM)


Seems to help a bit.


Nigel Brown(Posted 2008) [#9]
A large improvement here just some vsync issues. Looked on wxForum for solution and it only mentions wxGLCanvas in relation to vsync.


Brucey(Posted 2008) [#10]
Yeah, I don't think the vsync will be noticeable in Vertex's original example image as there won't be a lot of full-frame colour changing involved.


Nigel Brown(Posted 2008) [#11]
you might be interested in this?

http://wxcode.sourceforge.net/showcomp.php?name=wxPlotCtrl


Vertex(Posted 2008) [#12]
Thanks a lot!

I actually solve the problem so:
Type TOscilloscope Extends wxPanel
	Field Samples  : Int[]
	Field Bitmap   : wxBitmap
	Field Width    : Int
	Field Height   : Int

	Method OnInit()
		GetClientSize(Width, Height)
		Bitmap = New wxBitmap.CreateEmpty(Width, Height)
		
		ConnectAny(wxEVT_SIZE,  OnSize)
		ConnectAny(wxEVT_PAINT, OnPaint)
	End Method
	
	Method Draw()
		Local DC    : wxMemoryDC, ..
		      Brush : wxBrush, ..
		      Pen   : wxPen
		
		Local IndexFactor  : Float, ..
		      SampleFactor : Float, ..
		      Index        : Int, ..
		      X            : Int, ..
		      Y            : Int
		
		' Draw into bitmap
		DC = New wxMemoryDC.Create()
		DC.SelectObject(Self.Bitmap)
		
		Brush = New wxBrush.CreateFromColour(New wxColour.Create(188, 188, 188))
		DC.SetBackground(Brush)
		DC.Clear()

		Pen = New wxPen.CreateFromColour(New wxColour.Create(200, 50, 50))
		DC.SetPen(Pen)
		DC.DrawLine(0, -TRecorder.Threshold*Float(Height/2) + Height/2, ..
		            Width, -TRecorder.Threshold*Float(Height/2) + Height/2)
		
		IndexFactor  = Float(TRecorder.NumSamples)/Float(Width)
		SampleFactor = Float(Height)/Float($8000)
		
		Pen = New wxPen.CreateFromColour(New wxColour.Create(50, 50, 200))
		DC.SetPen(Pen)
		If Self.Samples Then
			For X = 1 Until Width
				Index = X*IndexFactor
				If Index >= TRecorder.NumSamples Then Index = TRecorder.NumSamples - 1

				Y = Int(Samples[Index]*SampleFactor) + Height/2
				If Y < 0       Then Y = 0
				If Y >= Height Then Y = Height - 1
				
				DC.DrawLine(X, Height/2, X, Y)
			Next
		Else
			DC.DrawLine(0, Height/2, Width, Height/2)
		EndIf
		
		DC.SelectObject(wxNullBitmap)
		DC.Free()
		
		Refresh()
	End Method
	
	Function OnSize(E:wxEvent)
		Local Event : wxSizeEvent, ..
		      _Self : TOscilloscope
		
		Event = wxSizeEvent(E)
		_Self = TOscilloscope(Event.Parent)
		
		' Resize bitmap and draw into
		_Self.GetClientSize(_Self.Width, _Self.Height)
		_Self.Bitmap = New wxBitmap.CreateEmpty(_Self.Width, _Self.Height)
		_Self.Draw()
	End Function
	
	Function OnPaint(E:wxEvent)
		Local Event  : wxPaintEvent, ..
		      _Self  : TOscilloscope, ..
		      DC     : wxPaintDC
		
		Event = wxPaintEvent(E)
		_Self = TOscilloscope(Event.Parent)
		
		DC = New wxPaintDC.Create(_Self)
		_Self.PrepareDC(DC)
		DC.DrawBitmap(_Self.Bitmap, 0, 0)
		DC.Free()
	End Function
End Type


I use a empty bitmap as backbuffer and draw into via wxMemoryDC. The bitmap is resized if the panel is resized.
On the paint event the complete backbuffer is drawn.

Also I have an extern timer event, that fill the oscilloscope with new sample data and than call Oscilloscope.Refresh() if there anough sample recordet.

Seem to be flicker free in combination with SetBackgroundStyle(wxBG_STYLE_CUSTOM)

(I must say I haven't realize the method with wxAutoBufferedPaintDC ^^)

The wxPlotCtrl looks good but I think it is easyer for me to program my own control. But thank you!

cu olli


Brucey(Posted 2008) [#13]
Vertex, that's very nice :-)

Are you finding non-3D graphics context drawing fast enough?

In general, I'm quite happy with GDI output for a lot of things, although I do miss anti-aliasing on Win32.


Vertex(Posted 2008) [#14]
For my purpose it is fast enough. The Oscilloscope and Autocorrelogram is drawn in fullscreen(~1600 x 1050) in realtime. For things like CAD(e.g. AutoCAD) or typeface editors(e.g. Fontlab) I would use Cairo or OpenVG that supports antialiasing.

But a idea to antialiasing:
Using supersampling. You render the GDI output in a double sized buffer and on the paint event, you use a bilinear filter to fit the buffer in the original size. On things that should not filtered, like images, you can use a mask for the buffer and the regions that not have set a bit would filtered by pointsampling. Also GDI supports a user zoom for very handy zooming.

To flickering:
I also have flickering :( The complete graphic is drawn in a backbuffer but the backbuffer is drawn pixel by pixel in the gadget. To avoid this I must use BufferedDC or AutoBufferedDC.

Here is the result


I love your wxWidget module, it is sooo cool :)

cu olli