Page 1 of 2

wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 9:03 am
by rybert2
Hello,
I'm trying to write a simple graphics app and I have a problem with drawing to a wxBitmap with wxBufferedDC and alpha channel.
Here's some code:

Code: Select all

 //canvas.cpp 
 #include "canvas.h"

BEGIN_EVENT_TABLE(qCanvas, wxPanel)
    EVT_PAINT(qCanvas::onPaint)
    EVT_LEFT_DOWN(qCanvas::onLeftDown)
    EVT_LEFT_UP(qCanvas::onLeftUp)
    EVT_MOTION(qCanvas::onMotion)
    EVT_LEAVE_WINDOW(qCanvas::onLeave)
    EVT_ERASE_BACKGROUND(qCanvas::eraseBackground)
END_EVENT_TABLE()

qCanvas::qCanvas(wxFrame* parent)
    :   wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize() ),
    	initialPoint( 0, 0 )
        
{
    wxSystemOptions::SetOption("msw.window.no-clip-children", 1);
    drawBuffer = new wxBitmap( wxSize( 3000, 2000 ), 32 );
    drawBuffer->UseAlpha();
    drawing = new wxBitmap( wxSize( 3000, 2000 ), 32 );
    drawing->UseAlpha();
    wxMemoryDC dc( *drawBuffer );
    dc.SetBackground( *wxWHITE_BRUSH );
    dc.Clear();
    dc.SelectObject( *drawing );
    dc.Clear();
    dc.SelectObject( wxNullBitmap );

    this->SetBackgroundStyle( wxBG_STYLE_CUSTOM );
    wxPanel::SetBackgroundColour( wxColor(255, 255, 255) );
}

qCanvas::~qCanvas()
{
    delete drawBuffer;
    delete drawing;
}

void qCanvas::onPaint(wxPaintEvent &event)
{
    wxPaintDC dc( this );
    render( dc );
}

void qCanvas::onLeftDown(wxMouseEvent &event)
{
    initialPoint = event.GetPosition();
    wxClientDC dc( this );
    render( dc );
}

void qCanvas::onLeftUp(wxMouseEvent &event)
{
    wxClientDC dc( this );
    dc.DrawBitmap( drawing, 0, 0, true );
}

void qCanvas::onMotion(wxMouseEvent &event)
{
    wxClientDC dc( this );
    if( event.LeftIsDown() )
    {
        wxBufferedDC* bdc = new wxBufferedDC( &dc, *drawBuffer );
        bdc->SetBackground( *wxTRANSPARENT_BRUSH );
        bdc->Clear();
        bdc->SetPen( wxPen( wxColor(0, 255, 0, 255), 4, wxPENSTYLE_SOLID ) );
        bdc->DrawLine( initialPoint, event.GetPosition() );
        delete bdc;
        render( dc );
    }
    wxBufferedDC ldc( &dc, drawing );
    ldc.SetBackground( *wxTRANSPARENT_BRUSH );
    wxPoint pt = event.GetPosition();
    wxSize sz = this->GetSize();
    ldc.Clear();
    ldc.SetPen( wxPen( wxColor( 0, 255, 0, 255 ), 1, wxPENSTYLE_SHORT_DASH ) );
    ldc.DrawLine( wxPoint( 0, pt.y ), wxPoint( sz.GetWidth(), pt.y ) );
    ldc.DrawLine( wxPoint( pt.x, 0 ), wxPoint( pt.x, sz.GetHeight() ) );
    render( dc );
}

void qCanvas::onLeave(wxMouseEvent &event)
{

}

void qCanvas::render( wxDC& dc )
{
    dc.DrawBitmap( *drawBuffer, 0, 0, true );
    dc.DrawBitmap( drawing, 0, 0, true );
}

void qCanvas::eraseBackground(wxEraseEvent &event) { ; }

void qCanvas::onCaptureLost(wxMouseCaptureLostEvent &event) { ; }

 

Code: Select all

//canvas.h
#ifndef CANVAS_H_INCLUDED
#define CANVAS_H_INCLUDED
#include <wx/wx.h>
#include <wx/dcbuffer.h>
#include <wx/graphics.h>
#include <wx/dcgraph.h>
#include <wx/sysopt.h>

class qCanvas : public wxPanel
{
private:
    wxBitmap* drawing;
    wxBitmap* drawBuffer;
    wxPoint initialPoint;
    void render( wxDC& dc );
public:
    qCanvas(wxFrame* parent);
    ~qCanvas();
    //Event Handlers:
    void onPaint(wxPaintEvent &event);
    void onLeftDown(wxMouseEvent &event);
    void onLeftUp(wxMouseEvent &event);
    void onRightDown(wxMouseEvent &event);
    void onRightUp(wxMouseEvent &event);
    void onMotion(wxMouseEvent &event);
    void onLeave(wxMouseEvent &event);
    void eraseBackground(wxEraseEvent &event);
    void onCaptureLost(wxMouseCaptureLostEvent &event);
    DECLARE_EVENT_TABLE();
};

#endif // CANVAS_H_INCLUDED

I'm using wxWidgets3.1.1 on Windows 10.

What I'm trying to achieve for this example is to draw lines on mouse position and on LeftDown event start to draw line from initialPoint to current mouse position. But when I try to do so the screen stays blank.
What may the reason of this?
PS. When I drawed a line with wxColor( 0, 255, 0, 255 ) which should be green, the line was yellow.

Re: wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 9:22 am
by doublemax
Too much code. Please try to strip it down to the absolute minimum to demonstrate the problem.

Re: wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 10:20 am
by PB
I believe the code posted has several issues. Many of them are shared with another recent thread, perhaps reading at least this post may somewhat help: viewtopic.php?f=1&t=44596&start=15#p184333

Re: wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 10:24 am
by rybert2

Code: Select all

//canvas.h
#include <wx/wx.h>
#include <wx/dcbuffer.h>
#include <wx/graphics.h>
#include <wx/dcgraph.h>
#include <wx/sysopt.h>

class qCanvas : public wxPanel
{
private:
    wxBitmap* drawing;
    wxBitmap* drawBuffer;
    wxPoint initialPoint;
    void render( wxDC& dc );
public:
    qCanvas(wxFrame* parent);
    ~qCanvas();
    //Event Handlers:
    void onPaint(wxPaintEvent &event);
    void onLeftDown(wxMouseEvent &event);
    void onLeftUp(wxMouseEvent &event);
    void onMotion(wxMouseEvent &event);
    void eraseBackground(wxEraseEvent &event);
    DECLARE_EVENT_TABLE();
};

Code: Select all

//canvas.cpp
#include "canvas.h"

BEGIN_EVENT_TABLE(qCanvas, wxPanel)
    EVT_PAINT(qCanvas::onPaint)
    EVT_LEFT_DOWN(qCanvas::onLeftDown)
    EVT_LEFT_UP(qCanvas::onLeftUp)
    EVT_MOTION(qCanvas::onMotion)
    EVT_ERASE_BACKGROUND(qCanvas::eraseBackground)
END_EVENT_TABLE()

qCanvas::qCanvas(wxFrame* parent)
    :   wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize() )
{
    wxSystemOptions::SetOption("msw.window.no-clip-children", 1);
    drawBuffer = new wxBitmap( wxSize( 3000, 2000 ), 32 );
    drawBuffer->UseAlpha();
    drawing = new wxBitmap( wxSize(3000, 2000), 32 );
    drawing->UseAlpha();
    wxMemoryDC dc( *drawBuffer ); //Fill bitmaps with empty white color
    dc.SetBackground( *wxWHITE_BRUSH );
    dc.Clear();
    dc.SelectObject( *drawing );
    dc.Clear();
    dc.SelectObject( wxNullBitmap ); 

    this->SetBackgroundStyle( wxBG_STYLE_CUSTOM );
    wxPanel::SetBackgroundColour( wxColor(255, 255, 255) );
    initialPoint = wxPoint(0, 0);
}

qCanvas::~qCanvas()
{
    delete drawBuffer;
    delete drawing;
}

void qCanvas::onPaint(wxPaintEvent &event)
{
    wxPaintDC dc( this );
    render( dc );
}

void qCanvas::onLeftDown(wxMouseEvent &event)
{
    initialPoint = event.GetPosition();
    wxClientDC dc( this );
    render( dc );
}

void qCanvas::render( wxDC& dc )
{
    dc.DrawBitmap( *drawBuffer, 0, 0, true );
    dc.DrawBitmap( *drawing, 0, 0, true );
}

void qCanvas::eraseBackground(wxEraseEvent &event) { ; }

But I think this is most important part: when I move the mouse. I want to draw lines through the current position and if mouse is down draw the line from initialPoint which is a point where mouse was clicked to the current mouse position.

Code: Select all

void qCanvas::onMotion(wxMouseEvent &event)
{
    wxClientDC dc( this );
    if( event.LeftIsDown() )
    {
        wxBufferedDC* bdc = new wxBufferedDC( &dc, *drawBuffer );
        bdc->SetBackground( *wxTRANSPARENT_BRUSH );
        bdc->Clear();
        bdc->SetPen( wxPen( wxColor(0, 255, 0, 255), 4, wxPENSTYLE_SOLID ) );
        bdc->DrawLine( initialPoint, event.GetPosition() );
        delete bdc;
        render( dc );
    }
    wxBufferedDC ldc( &dc, *drawing );
    ldc.SetBackground( *wxTRANSPARENT_BRUSH );
    wxPoint pt = event.GetPosition();
    wxSize sz = this->GetSize();;
    ldc.Clear();
    ldc.SetPen( wxPen( wxColor( 0, 255, 0, 255 ), 1, wxPENSTYLE_SHORT_DASH ) );
    ldc.DrawLine( wxPoint( 0, pt.y ), wxPoint( sz.GetWidth(), pt.y ) );
    ldc.DrawLine( wxPoint( pt.x, 0 ), wxPoint( pt.x, sz.GetHeight() ) );
    render( dc );
}
I use wxBufferedDC to avoid flicker. Whetever I do - the canvas remains blank.

Re: wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 10:59 am
by doublemax

Code: Select all

        wxBufferedDC* bdc = new wxBufferedDC( &dc, *drawBuffer );
        bdc->SetBackground( *wxTRANSPARENT_BRUSH );
        bdc->Clear();
What you're probably trying to do here is to make the whole DC transparent so that only the line will be blitted to the original DC. But you can't "draw" transparency. Drawing with a transparent brush is a NOP.

I would suggest to stop using wxClientDC altogether. Just change the model state and redraw everything completely in the paint event handler. If this turns out to be too slow, you can look into optimizing things by caching etc.

Re: wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 11:29 am
by rybert2
So i should use wxMemoryDC to draw into a wxBitmap and then redraw whole canvas trigering PaintEvent with Refresh()?
Also I would like to make it possible to draw on multiple layers. Should I redraw e. g. some vector with bitmap every time I do any change?
Would be problematic since I redraw on every move.

Re: wxBufferedDC and Alpha Channel

Posted: Thu May 17, 2018 11:56 am
by doublemax
In the paint event handler you must be able to draw the whole content in any case. How you do (and optimize) that, is up to you. IMHO optimization comes last, first you should check if it's really too slow.

You can also look into wxOverlay http://docs.wxwidgets.org/trunk/classwx_overlay.html

The "drawing" sample uses it to draw a rubber band selection, check its source code.

Re: wxBufferedDC and Alpha Channel

Posted: Fri May 18, 2018 9:24 am
by rybert2
I have done some changes in my code and finally - it works, but not properly. Now I have problem with color. When I draw my bitmaps even with transparent brush the bg color is black. As a solution I made another bitmap (bgBmp), Clear() it with white color and draw on it - works, background is now white but I can't see my lines! So I changed the bg color to wxColour(128, 128, 128). Now the lines appear but in not natural way. They look like the alpha channel wasn't wxALPHA_OPAQUE but something in the middle.

Code: Select all

//canvas.h
// includes: wx.h, sysopt.h, dcbuffer.h, graphics.h, dcgraph.h overlay.h

class qCanvas : public wxPanel
{
private:
    void render( wxDC& );
    wxBitmap* buffer;
    wxVector<wxBitmap*> bitmaps;
    wxPoint initialPoint;
    wxOverlay over;
    wxBitmap bgBmp;
public:
    qCanvas(wxFrame* parent);
    ~qCanvas();
    //Event Handlers:
    void onPaint(wxPaintEvent &event);
    void onLeftDown(wxMouseEvent &event);
    void onLeftUp(wxMouseEvent &event);
    void onMotion(wxMouseEvent &event);
    void eraseBackground(wxEraseEvent &event);
    DECLARE_EVENT_TABLE();
};

Code: Select all

//canvas.cpp
#include "canvas.h"

BEGIN_EVENT_TABLE(qCanvas, wxPanel)
    EVT_PAINT(qCanvas::onPaint)
    EVT_LEFT_DOWN(qCanvas::onLeftDown)
    EVT_LEFT_UP(qCanvas::onLeftUp)
    EVT_MOTION(qCanvas::onMotion)
    EVT_ERASE_BACKGROUND(qCanvas::eraseBackground)
END_EVENT_TABLE()

qCanvas::qCanvas(wxFrame* parent)
    :   wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetSize() ),
        initialPoint(0, 0),
        over(),
        bgBmp( wxSize(800, 600), 32 ),
{
    wxSystemOptions::SetOption("msw.window.no-clip-children", 1);
    this->SetBackgroundStyle( wxBG_STYLE_PAINT );
    wxPanel::SetOwnBackgroundColour( wxColor(255, 255, 255) );
    wxMemoryDC mdc( bgBmp );
    mdc.SetBackground( wxBrush( wxColour( 128, 128, 128, 255), wxBRUSHSTYLE_SOLID ) );
    mdc.Clear();
    mdc.SelectObject( wxNullBitmap );
}

void qCanvas::onPaint(wxPaintEvent &event)
{
    wxAutoBufferedPaintDC dc( this );
    over.Reset();
    render( dc );
}

void qCanvas::onLeftDown(wxMouseEvent &event)
{
    initialPoint = event.GetPosition();
    buffer = new wxBitmap( wxSize(800, 600), 32 );
    buffer->UseAlpha();
    wxClientDC dc( this );
}

void qCanvas::onLeftUp(wxMouseEvent &event)
{
    wxClientDC dc( this );
    bitmaps.push_back( buffer );
    Refresh();
    Update();
}

void qCanvas::onMotion(wxMouseEvent &event)
{
    wxClientDC dc( this );
    dc.SetPen( wxPen( wxColor(0, 0, 0), 1, wxPENSTYLE_LONG_DASH ) );

    wxDCOverlay dcOver( over, &dc );
    dcOver.Clear();

    if( event.LeftIsDown() )
    {
        wxBufferedDC bdc( &dc, *buffer );
        bdc.SetPen( wxPen( wxColour(255, 0, 0), 10 ) );
        bdc.SetBackground( *wxTRANSPARENT_BRUSH );
        bdc.Clear();
        bdc.DrawLine( initialPoint, event.GetPosition() );
    }

    wxPoint pt = event.GetPosition();
    wxSize sz = this->GetSize();
    dc.DrawLine( wxPoint(0, pt.y), wxPoint(sz.GetWidth(), pt.y) );
    dc.DrawLine( wxPoint(pt.x, 0), wxPoint(pt.x, sz.GetHeight() ) );
}

void qCanvas::render( wxDC& dc )
{
    dc.DrawBitmap( bgBmp, 0, 0 );
    for( wxBitmap* bmp : bitmaps )
    {
        dc.DrawBitmap( *bmp, wxPoint(0, 0), true );
    }
}

void qCanvas::eraseBackground(wxEraseEvent &event) { ; }
What can be wrong here that the alpha channel doesn't work properly?

Re: wxBufferedDC and Alpha Channel

Posted: Fri May 18, 2018 10:19 am
by doublemax
I think for drawing operations like DrawLine() with alpha you need wxGraphicsContext or wxGCDC under Windows.

Re: wxBufferedDC and Alpha Channel

Posted: Fri May 18, 2018 11:21 am
by rybert2
Ok, I have changed my code for something like this:

Code: Select all

void qCanvas::onMotion(wxMouseEvent &event)
{
    wxClientDC dc( this );
    dc.SetPen( wxPen( wxColor(0, 0, 0), 1, wxPENSTYLE_LONG_DASH ) );

    wxDCOverlay dcOver( over, &dc );
    dcOver.Clear();

    if( event.LeftIsDown() )
    {
        wxMemoryDC mdc( *buffer );
        mdc.SetBackground( *wxTRANSPARENT_BRUSH );
        mdc.Clear();
        wxGCDC gc( mdc );
        gc.SetPen( wxPen( wxColor( 255, 0, 0), 4 ) );
        gc.DrawLine( initialPoint, event.GetPosition() );
        mdc.SelectObject( wxNullBitmap );
        dc.DrawBitmap( *buffer, 0, 0, true );
    }

    wxPoint pt = event.GetPosition();
    wxSize sz = this->GetSize();
    dc.DrawLine( wxPoint(0, pt.y), wxPoint(sz.GetWidth(), pt.y) );
    dc.DrawLine( wxPoint(pt.x, 0), wxPoint(pt.x, sz.GetHeight() ) );
}
and it works perfectly! This code is temporary - just to see if wxGCDC works and it does! I'm thinking of changing wxBitmap to wxImage and work on wxGraphicsContext and wxGCDC. Is it a good solution or should I keep using wxGCDC only in operations like this one?

Re: wxBufferedDC and Alpha Channel

Posted: Fri May 18, 2018 11:44 am
by doublemax
wxGraphicsContext / wxGCDC is slower, because it uses anti-aliasing. But as long as you don't draw so much that it becomes an issue, you can use it everywhere.

Re: wxBufferedDC and Alpha Channel

Posted: Mon May 21, 2018 10:39 am
by rybert2
Another problem: when I use wxDCOverlay in this example it flickers:

Code: Select all

void qCanvas::onMotion(wxMouseEvent &event)
{
    wxClientDC dc( this );
    wxDCOverlay dcOver( over, &dc );
    dcOver.Clear();
    wxPoint pt = event.GetPosition();
    wxSize sz = this->GetSize();

    if( event.LeftIsDown() )
    {
        wxMemoryDC mdc( *buffer );
        mdc.SetBackground( *wxTRANSPARENT_BRUSH );
        mdc.Clear();
        wxGCDC gc( mdc );
        gc.SetPen( wxPen( wxColor( 255, 0, 0), 4 ) );
        gc.DrawLine( initialPoint, pt );
        mdc.SelectObject( wxNullBitmap );
        dc.SetPen( wxPen( wxColor( 255, 0, 0), 4 ) );
        dc.DrawLine( initialPoint, pt );
    }

    dc.SetPen( wxPen( wxColor(0, 0, 0), 1, wxPENSTYLE_LONG_DASH ) );
    dc.DrawLine( wxPoint(0, pt.y), wxPoint(sz.GetWidth(), pt.y) );
    dc.DrawLine( wxPoint(pt.x, 0), wxPoint(pt.x, sz.GetHeight() ) );
}
What can I do to avoid flicker?

Re: wxBufferedDC and Alpha Channel

Posted: Mon May 21, 2018 12:42 pm
by doublemax
Hard to tell, try buffering the wxClientDC, too.

In general, with wxClientDC you will always get messy code, it's much cleaner to (re-)draw everything in the paint event handler.

Re: wxBufferedDC and Alpha Channel

Posted: Tue May 22, 2018 7:10 am
by rybert2
Hm, that's why I use wxDCOverlay - to avoid repainting whole screen on every mouse move. This is very strange.
There is no reason to use wxMemoryDC without repainting it, wxClientDC causes flicker, wxBufferedDC doesn't work (I have a debugging check:
assert "bmpSrc.IsOk() && bmpSrc.HasAlpha()" failed in AlphaBlt(): AlphaBlt(): invalid bitmap,
even if I create wxBufferedDC with previously prepred bitmap "bmp")
The only solution that ALMOST worked was creating wxGCDC from wxBufferedDC, but now background is just black instead of transparent.

Code: Select all

void qCanvas::onMotion(wxMouseEvent &event)
{
    wxClientDC cdc( this );
    wxBufferedDC bdc( &cdc, *bmp );
    wxGCDC dc( bdc );
    dc.SetBackground( *wxTRANSPARENT_BRUSH );
    dc.Clear();
    wxDCOverlay dcOver( over, &dc );
    dcOver.Clear();
   [...]
}
// --- bmp creation:
bmp = new wxBitmap( some_wxSize, 32 /* depth */);
bmp->UseAlpha( true );
I tried clearing my buffer in various ways, by wxMemoryDC, by wxBufferedDC and ,as in example above, by wxGCDC - none worked.
Is there a way to use wxBufferedDC with wxDCOverlay? Or how can I make wxGCDC background transparent? Or should I use the paint event?

Re: wxBufferedDC and Alpha Channel

Posted: Tue May 22, 2018 7:59 am
by doublemax
Can you try to build a (as small as possible) compilable sample?