Page 1 of 1

How to use wxDCOverlay?

Posted: Tue Jan 14, 2020 12:07 pm
by Big Muscle
Our application has a canvas that draws some plots on X-Y axis. We would like to draw pointer that shows the measurement time in the plot. The current approach is to do everything in wxEVT_PAINT event, but it is not good because changing the time pointer redraws whole canvas that can be slow.

Our ideas is to draw time pointer into some kind of layer that is put over the plot and only this layer is redrawn when time changes. There is some wxDCOverlay in wxWidgets. Is it suitable for this purpose? Application will run on Win and GTK+3.

I declare "wxOverlay m_overlay" in the main class and call the following code when time changes:

Code: Select all

CAnalysisPanel contructor:
	m_pPlot = new wxPanel(this);
	m_pPlot->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
	m_pPlot->Bind(wxEVT_PAINT, &CAnalysisPanel::OnPaint, this);

void CAnalysisPanel::SetCurrentFrame(unsigned int i) {
        wxClientDC dc(m_pPlot) ; // m_pCanvas is wxPanel
        wxDCOverlay overlaydc( m_overlay, &dc );
        overlaydc.Clear();
        dc.DrawLine(...);	// code to draw the pointer
}
But it does not work. Although the time pointer is drawn, the wxEVT_PAINT event seems to be called immediately after, so it gets repainted.
Is something wrong with the code? Did I forget something? Why wxEVT_PAINT is called?

Re: How to use wxDCOverlay?

Posted: Tue Jan 14, 2020 2:21 pm
by New Pagodi
I think a better approach would be to cache the plots into a bitmap. The paint handler would first draw the bitmap and then draw any extra lines over the bitmap. To generate the cached bitmap of the plot, you can use wxMemoryDC.

Re: How to use wxDCOverlay?

Posted: Tue Jan 14, 2020 4:15 pm
by doublemax
I've never used wxDCOverlay, but it's used in the "drawing" sample to draw a rubber-band on the panel. Check it out.

Re: How to use wxDCOverlay?

Posted: Wed Jan 15, 2020 9:32 am
by Big Muscle
New Pagodi: yes, this seems the classical approach and I probably stay with it, it also allows me to cache separate bitmap for more plots and then just compose them when needed. Although the usage seems much more complicated, e.g. how to plot the lines into the transparent wxMemoryDC so I can compose them directly?

Code: Select all

	wxMemoryDC dc;
	dc.SelectObject(m_bmpCachedPlot); // m_bmpCachedPlot is 32-bit bitmap

	wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
	if (gc) {
		// this does nothing !!! why it does not clear the background???
		gc->ClearRectangle(rc.GetLeft(), rc.GetTop(), rc.GetWidth(), rc.GetHeight()); 
		...
		delete gc;
	}
I just wanted to know whether there is some native way in wxWidgets and I found wxDCOverlay. Sample works correctly, but my code does not although I just copied it from the sample and there is not much information in the documentation. I don't understand why wxEVT_PAINT handler is invoked in my application but it is not in the sample. Also, it seems weird that using wxDCOverlay behaves exactly in the same way like when it is not used, i.e.:

Code: Select all

void CAnalysisPanel::SetCurrentFrame(unsigned int i) {
        wxClientDC dc(m_pPlot) ; // m_pPlot is wxPanel
        wxDCOverlay overlaydc( m_overlay, &dc );
        overlaydc.Clear();
        dc.DrawLine(...);	// code to draw the pointer
}
behaves exactly like

Code: Select all

void CAnalysisPanel::SetCurrentFrame(unsigned int i) {
        wxClientDC dc(m_pPlot) ; // m_pPlot is wxPanel
        dc.DrawLine(...);	// code to draw the pointer
}
When drawing anything in "dc" then wxEVT_PAINT is invoked.

Re: How to use wxDCOverlay?

Posted: Wed Jan 15, 2020 11:06 am
by New Pagodi
Big Muscle wrote:
Wed Jan 15, 2020 9:32 am
New Pagodi: yes, this seems the classical approach and I probably stay with it, it also allows me to cache separate bitmap for more plots and then just compose them when needed. Although the usage seems much more complicated, e.g. how to plot the lines into the transparent wxMemoryDC so I can compose them directly?

Code: Select all

	wxMemoryDC dc;
	dc.SelectObject(m_bmpCachedPlot); // m_bmpCachedPlot is 32-bit bitmap

	wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
	if (gc) {
		// this does nothing !!! why it does not clear the background???
		gc->ClearRectangle(rc.GetLeft(), rc.GetTop(), rc.GetWidth(), rc.GetHeight()); 
		...
		delete gc;
	}
Do you mean "gc->DrawRectangle"? I don't think there is a method named "ClearRectangle" in the graphics context class. If you do mean DrawRectangle, you need to set a pen first. ie something like:

Code: Select all

	if (gc) {
		gc->SetPen(*wxBLACK_PEN);
		gc->DrawRectangle(rc.GetLeft(), rc.GetTop(), rc.GetWidth(), rc.GetHeight()); 

Re: How to use wxDCOverlay?

Posted: Wed Jan 15, 2020 8:10 pm
by Big Muscle
There is ClearRectangle function in wxGraphicsContext but its implementation is empty so it does nothing. DrawRectangle is not suitable because it will draw rectangle together with the existing content.

My current multi-platform (GTK + MSW) solution that seems to be working is to re-create wxImage every time the plot needs to be repainted:

Code: Select all

wxImage img(rc.GetSize());
img.SetMaskColour(0, 0, 0);	// InitAlpha() initializes to opaque when mask colour is not set
img.InitAlpha();	// initializes image to be fully transparent

m_bmpCachedPlot = img;

wxMemoryDC dc;
dc.SelectObject(m_bmpCachedPlot);

wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
if (gc) {
	...
It is not cool, re-creating m_bmpCachedPlot only when window size changes would be better, but I have not found any other way to clear wxGraphicsContext with transparent background before painting.

Re: How to use wxDCOverlay?

Posted: Wed Jan 15, 2020 10:57 pm
by New Pagodi
If you want to draw with transparency with wxGraphicsContext while caching the result in a bitmap, I would try adding a function like this:

Code: Select all

void CAnalysisPanel::BuildPlot(int width, int height)
{
    wxImage img(width,height);

    // Create an alpha channel and set all pixels transparent.
    img.InitAlpha();
    memset(img.GetAlpha(), 0, width * height);

    // Create a graphics context from the image.
    wxGraphicsContext* gc = wxGraphicsContext::Create(img);

    if ( gc )
    {
        gc->SetPen(*wxBLACK_PEN);
        gc->StrokeLine(0,height-6,width-1,height-6);
        gc->StrokeLine(5,0,5,height-1);
        gc->StrokeLine(0,5,5,0);
        gc->StrokeLine(5,0,10,5);
        gc->StrokeLine(width-6,height-11,width-1,height-6);
        gc->StrokeLine(width-1,height-6,width-6,height-1);

        delete gc;
    }

    // Convert the image to a bitmap for drawing.
    m_bmpCachedPlot = wxBitmap(img);
}

Re: How to use wxDCOverlay?

Posted: Thu Jan 16, 2020 11:07 am
by Big Muscle
Thank you, it works!

I just modified it a but not to recreate wxImage everytime, wxBitmap m_bmpCachedPlot replaced with wxImage m_imgCachedPlot:

Code: Select all

	if (!m_imgCachedPlot.IsOk() || rc.GetSize() != m_imgCachedPlot.GetSize()) {
		m_imgCachedPlot.Create(rc.GetSize());
		m_imgCachedPlot.InitAlpha();
	}

	memset(m_imgCachedPlot.GetAlpha(), 0, rc.GetWidth() * rc.GetHeight());
	wxGraphicsContext* gc = wxGraphicsContext::Create(img);
	...
And in wxEVT_PAINT handler:

Code: Select all

	gc->DrawBitmap(gc->CreateBitmapFromImage(m_imgCachedPlot), 0, 0, rc.GetWidth(), rc.GetHeight());
I guess there is no way to use wxGraphicsBitmap directly to avoid image data copying as much as possible.