wxBufferedDC and Alpha Channel Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

wxBufferedDC and Alpha Channel

Post 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.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post by doublemax »

Too much code. Please try to strip it down to the absolute minimum to demonstrate the problem.
Use the source, Luke!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxBufferedDC and Alpha Channel

Post 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
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

Re: wxBufferedDC and Alpha Channel

Post 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.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post 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.
Use the source, Luke!
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

Re: wxBufferedDC and Alpha Channel

Post 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.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post 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.
Use the source, Luke!
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

Re: wxBufferedDC and Alpha Channel

Post 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?
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post by doublemax »

I think for drawing operations like DrawLine() with alpha you need wxGraphicsContext or wxGCDC under Windows.
Use the source, Luke!
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

Re: wxBufferedDC and Alpha Channel

Post 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?
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post 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.
Use the source, Luke!
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

Re: wxBufferedDC and Alpha Channel

Post 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?
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post 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.
Use the source, Luke!
rybert2
In need of some credit
In need of some credit
Posts: 7
Joined: Thu May 17, 2018 8:47 am

Re: wxBufferedDC and Alpha Channel

Post 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?
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBufferedDC and Alpha Channel

Post by doublemax »

Can you try to build a (as small as possible) compilable sample?
Use the source, Luke!
Post Reply