wxMemoryDC on wxBitmap uses button background/foreground 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.
Post Reply
rando
Earned a small fee
Earned a small fee
Posts: 24
Joined: Fri Nov 09, 2018 9:11 pm

wxMemoryDC on wxBitmap uses button background/foreground

Post by rando » Tue Jan 15, 2019 8:08 pm

I have two wxButtons, both are identical in setup except that one has a background wxColour(192,192,192), call this button A, while the other has a background wxColour(0,0,0), call this button B. Both button are on the same wxPanel which has background wxColour(192,192,192). Text is drawn onto the buttons using the following code (a new bitmap has been assigned to the button prior to the function call).

Code: Select all

bool DrawLabelOnBitmapButton( wxButton* button, const wxString& label, const wxFont& font, const wxColour font_color )
{
	wxString local_text;
	wxArrayString text_lines;
	wxBitmap bitmap = button->GetBitmap();
	wxMemoryDC mem_dc( bitmap );
	wxSize text_area;
	if( !( bitmap.IsOk() && mem_dc.IsOk() && label.Len() > 0 ) ) {
		return false;
	}
	mem_dc.SetTextForeground( font_color ); //*wxBLACK in this case
	//mem_dc.SetTextBackground( font_color );
	// TODO use GetTextExtnet() and GetMultilineTextExtent() to compute text_area
	text_area = mem_dc.GetSize() - ( mem_dc.GetSize() * ( BUTTON_BORDER_PERCENT / 100 ) );
	text_lines = SplitStringNearCenter( label );
	local_text = ( text_lines.Count() > 1 ) ? text_lines[0] + '\n' + text_lines[1] : text_lines[0];
	mem_dc.SetFont( font );
	mem_dc.DrawLabel( local_text, text_area, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL );
	mem_dc.DestroyClippingRegion();
	button->SetBitmap( bitmap );
	return true;
}
That function is used to draw all button text in the application.

The problem is that button B renders the text as expected (solid black letters) which button A renders the text with a white outline and inner color the same as the button background when using wxBLACK. If I simply switch button A background to (0,0,0) then the text is as expected. I have tried with other colors such as wxColour(255,0,255)) which results in the color being visible but as a thin line with the white outline.

The original problem I am trying to solve is that the buttons have a bitmap with a radiused look to one end which is accomplished by applying transparency. The transparent portion of the bitmap shows through the black color of the button but the panel the button is on is grey so the look is less than ideal.

The buttons A and B
Image

Manolo
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 653
Joined: Mon Apr 30, 2012 11:07 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by Manolo » Tue Jan 15, 2019 8:29 pm

Why you use mem_dc.DestroyClippingRegion();?
You better replace it with mem_dc.SelectObject(wxNullBitmap);

rando
Earned a small fee
Earned a small fee
Posts: 24
Joined: Fri Nov 09, 2018 9:11 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by rando » Tue Jan 15, 2019 9:18 pm

Manolo wrote:Why you use mem_dc.DestroyClippingRegion();?
You better replace it with mem_dc.SelectObject(wxNullBitmap);
Thanks for the tip. Unfortunately, that does not make any difference in the appearance of the buttons.

ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 2928
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by ONEEYEMAN » Tue Jan 15, 2019 9:58 pm

Hi,
Are you using a default theme?
Thank you.

rando
Earned a small fee
Earned a small fee
Posts: 24
Joined: Fri Nov 09, 2018 9:11 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by rando » Tue Jan 15, 2019 10:04 pm

ONEEYEMAN wrote:Hi,
Are you using a default theme?
Thank you.
Just locally manipulated through the provided interface, I have not downloaded any themes. I just added a slideshow for my pictures and changed the color to Blue.
Windows 10 Pro, all up to date.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 1834
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by PB » Tue Jan 15, 2019 10:25 pm

wxWidgets version and platform?

I do not observe any such issues (the buttons having regular outline is not the focus of this topic) with code below, except wxMemoryDC.Clear() not filling the bitmap with the current brush? MS Windows, wxWidgets 3.1.2.
memorydc text.png
memorydc text.png (4.48 KiB) Viewed 292 times
Obviously one has to properly set the DC attributes and take care of transparency.

Code: Select all

#include <wx/wx.h>

class MyDialog : public wxDialog
{
public:
    MyDialog () : wxDialog(NULL, wxID_ANY, "Test", wxDefaultPosition, wxSize(600, 400), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
    {                                                     
        m_backgroundBitmap = CreateBackgroundBitmap(wxSize(256, 48));

        wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        AddButton(mainSizer, "Button A", *wxBLACK);
        AddButton(mainSizer, "Button B", *wxRED);
        AddButton(mainSizer, "Button C", *wxBLUE);
        AddButton(mainSizer, "Button D", *wxBLACK);

        SetSizer(mainSizer);                 
    }   
private:  
    wxBitmap m_backgroundBitmap;
    
    void AddButton(wxSizer* sizer, const wxString& labelText, const wxColour& labelColour)
    {
        wxButton* button = new wxButton(this, wxID_ANY);
        button->SetBitmap(AddLabelToBackgroundBitmap(m_backgroundBitmap, labelText, labelColour));
        sizer->Add(button, wxSizerFlags().Border().Expand());    
    }

    static wxBitmap AddLabelToBackgroundBitmap(const wxBitmap& backgroundBitmap, const wxString& labelText, const wxColour& labelColour)
    {
        wxBitmap bitmap(backgroundBitmap.GetSize());
        wxMemoryDC memoryDC(bitmap);
        wxRect rect(memoryDC.GetSize());
        wxFont font(*wxNORMAL_FONT);

        font.MakeLarger().MakeBold();
        
        wxDCFontChanger fontChanger(memoryDC, font);
        wxDCPenChanger penChanger(memoryDC, *wxYELLOW_PEN);
        wxDCBrushChanger brushChanger(memoryDC, *wxYELLOW_BRUSH);
        wxDCTextColourChanger textColourChanger(memoryDC, labelColour);
    
        memoryDC.DrawRectangle(rect);
        memoryDC.DrawBitmap(backgroundBitmap, 0, 0, true);
        memoryDC.DrawLabel(labelText, rect, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL);
        memoryDC.SelectObject(wxNullBitmap);
        
        wxMask* mask = new wxMask();

        mask->Create(bitmap, *wxYELLOW);
        bitmap.SetMask(mask);

        return bitmap;
    }

    static wxBitmap CreateBackgroundBitmap(const wxSize& size)
    {
        wxBitmap bitmap(size);
        wxMemoryDC memoryDC(bitmap);        
        wxDCPenChanger penChanger(memoryDC, *wxBLACK_PEN);
        wxDCBrushChanger brushChangerBlack(memoryDC, *wxBLACK_BRUSH);        
        wxRect rect(memoryDC.GetSize());

        memoryDC.DrawRectangle(rect);

        wxDCBrushChanger brushChangerGreen(memoryDC, *wxGREEN_BRUSH);        
        
        memoryDC.DrawRoundedRectangle(rect, -1.);
        memoryDC.SelectObject(wxNullBitmap);

        // Make the area outside rounded rectangle filled in black
        // transparent
        wxMask* mask = new wxMask();

        mask->Create(bitmap, *wxBLACK);
        bitmap.SetMask(mask);        

        return bitmap;
    }    
};

class MyApp : public wxApp
{
public:   
    bool OnInit()
    {
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);

rando
Earned a small fee
Earned a small fee
Posts: 24
Joined: Fri Nov 09, 2018 9:11 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by rando » Wed Jan 23, 2019 9:37 pm

Finally getting back to this. First a little relevant background. I have been using wxWidgets since version 2.6. I develop industrial and laboratory interfaces for our test equipment. Those interfaces are as simple as possible, all the magic occurs on the back-end and the major work is in communication and data storage/manipulation. I have used wxStaticBitmap control to display an application header image (logo) but have never used wxDC, or any of the image related functionality. I have never worked with images and had no understanding of image formats. I have managed, with the help of the well above average wxWidgets documentation, to figure out everything I need for this application except for this button text problem.

@PB I use a similar method to draw some button bitmaps, although I used the gradient drawing functions, and then use my DrawLabelOnBitmapButton() algorithm to draw text on those bitmaps and assign to a button. It is two stage (create the bitmap, pass it to DrawLabelOnBitmapbutton()) and works as expected regardless of the background color of the button. Those are the buttons on the left of the attached image.
I added code to set a mask to wxCYAN. which does not occur in the image, to try and ensure that nothing was masked off.

The green/gray buttons start out as png images on disk using alpha channel for transparency.
Those images are loaded into memory at application startup into a wxVecotr<wxBitmap*> bitmaps by calling the following function:

Code: Select all

wxBitmap* GetChoiceStripBitmap(ChoiceStripBitmap csbm)
{
	wxBitmap* bitmap = nullptr;
	switch (csbm)
	{
	case CSBM_LEFT_UNSELECTED:
		bitmap = new wxBitmap("resource\\images\\choice_strip\\choice_strip_left_unselected.png", wxBITMAP_TYPE_PNG);
		break;
	case CSBM_LEFT_SELECTED:
		bitmap = new wxBitmap("resource\\images\\choice_strip\\choice_strip_left_selected.png", wxBITMAP_TYPE_PNG);
		break;
	case CSBM_RIGHT_UNSELECTED:
		bitmap = new wxBitmap("resource\\images\\choice_strip\\choice_strip_right_unselected.png", wxBITMAP_TYPE_PNG);
		break;
	case CSBM_RIGHT_SELECTED:
		bitmap = new wxBitmap("resource\\images\\choice_strip\\choice_strip_right_selected.png", wxBITMAP_TYPE_PNG);
		break;
	case CSBM_INSIDE_UNSELECTED:
		bitmap = new wxBitmap("resource\\images\\choice_strip\\choice_strip_inside_unselected.png", wxBITMAP_TYPE_PNG);
		break;
	case CSBM_INSIDE_SELECTED:
		bitmap = new wxBitmap("resource\\images\\choice_strip\\choice_strip_inside_selected.png", wxBITMAP_TYPE_PNG);
		break;
	case CSBM_INVALID:
		; // do nothing, bitmap is alrady nullptr
	}
	return bitmap;
}
then copy the bitmap as needed using CopyBitmap(*bitmaps[target_index]);

The main difference between the buttons which have correct colored text and buttons which have text the same color as the button background is the way the wxBitmap is obtained.

I can change the color of the affected buttons text on the fly by changing the button background color so I am assuming that the text is being drawn transparent somehow.

Attached is an image of how the buttons look and one of the choice strip bitmaps used. The yellow button text was produced simply by changing the background color of that button. The buttons on the left have background set to yellow and cyan but that does not show through the text.


Any ideas to point me in a the direction of figuring this out are appreciated. I have tried to trace though the code but confess that my understanding of the window drawing/painting process is sub-par at best and I usually end up getting lost.
tsr_image.png
tsr_image.png (25.73 KiB) Viewed 225 times
Attachments
choice_strip_left_selected.png
A Choice Strip Button
choice_strip_left_selected.png (4.91 KiB) Viewed 225 times

PB
Part Of The Furniture
Part Of The Furniture
Posts: 1834
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by PB » Thu Jan 24, 2019 1:04 pm

(I am certainly no expert either, so take anything I say with a reasonably large grain of salt).


TBH, I do not really understand the issue and how you change the text of the colour via changing the button background? It still seems to work with the modified code from my previous post even when using the bitmap you provided. As the bitmap seemed to have some issues with blended partially transparent pixels on its left side, I used another method to make it transparent when combined with text. However, it seems that this method makes the edges somewhat jagged; I haven't looked into it any further.

I also do not understand how you can have regular wxButtons on MS Windows without them having the rectangular button background and border?

The layout on the screenshot below has two panels layout horizontally, the left one has a default background while the right one is black. In each panel there are vertically stacked pairs of wxButton and wxStaticBitmap with the same label.
memdctransparency.png
memdctransparency.png (33.27 KiB) Viewed 183 times
Code which generated the screenshot above, perhaps it can be of some use to you, particularly the code converting the alpha to mask in the dialog ctor and AddLabelToBackgroundBitmap() method.

Code: Select all

#include <wx/wx.h>

class MyDialog : public wxDialog
{
public:
    MyDialog () : wxDialog(NULL, wxID_ANY, "Test")
    {                                                                     
        m_maskColour = wxColour(255, 0, 255); // magenta
        
        wxImage image;

        if ( !image.LoadFile("choice_strip_left_selected.png") )
            return;

        if ( image.HasAlpha() )
            image.ConvertAlphaToMask(m_maskColour.Red(), m_maskColour.Green(), m_maskColour.Blue()); 
        m_backgroundBitmap = wxBitmap(image);
                
        wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);

        mainSizer->Add(CreatePanel(), wxSizerFlags().Expand());
        mainSizer->Add(CreatePanel(wxBLACK), wxSizerFlags().Expand());        
        SetSizerAndFit(mainSizer);                 
    }   
private: 
    wxColour m_maskColour;
    wxBitmap m_backgroundBitmap;

    wxPanel* CreatePanel(const wxColour* colour = NULL)
    {
        wxPanel* panel = new wxPanel(this);
        wxBoxSizer* panelSizer = new wxBoxSizer(wxVERTICAL);

        if ( colour )
            panel->SetBackgroundColour(*colour);

        AddButtonAndStaticBitmap(panel, panelSizer, "Button A", *wxBLACK);
        AddButtonAndStaticBitmap(panel, panelSizer, "Button B", *wxRED);
        AddButtonAndStaticBitmap(panel, panelSizer, "Button C", *wxBLUE);
        AddButtonAndStaticBitmap(panel, panelSizer, "Button D", *wxYELLOW);

        panel->SetSizer(panelSizer);

        return panel;
    }
   
    void AddButtonAndStaticBitmap(wxWindow* parent, wxSizer* sizer, 
                                  const wxString& labelText, const wxColour& labelColour)
    {
        wxBitmap bitmapWithLabel = AddLabelToBackgroundBitmap(m_backgroundBitmap, labelText, labelColour);
        
        wxButton* button = new wxButton(parent, wxID_ANY);
        button->SetBitmap(bitmapWithLabel);
        sizer->Add(button, wxSizerFlags().Border().Expand());   

        wxStaticBitmap* staticBitmap = new wxStaticBitmap(parent, wxID_ANY, bitmapWithLabel);
        sizer->Add(staticBitmap, wxSizerFlags().Border().Expand());   
    }

    wxBitmap AddLabelToBackgroundBitmap(const wxBitmap& backgroundBitmap, 
                                        const wxString& labelText, const wxColour& labelColour)
    {
        wxBitmap bitmap(backgroundBitmap.GetSize());                        
        wxMemoryDC memoryDC(bitmap);        
        wxFont font(*wxNORMAL_FONT);

        font.MakeLarger().MakeBold();
       
        wxDCFontChanger fontChanger(memoryDC, font);        
        wxDCTextColourChanger textColourChanger(memoryDC, labelColour);
        wxRect rect(memoryDC.GetSize());
   
        memoryDC.SetBackground(wxBrush(m_maskColour));
        memoryDC.Clear();
        memoryDC.DrawBitmap(backgroundBitmap, 0, 0, true);
        memoryDC.DrawLabel(labelText, rect, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL);
        memoryDC.SelectObject(wxNullBitmap);        
       
        wxMask* mask = new wxMask();        
        mask->Create(bitmap, m_maskColour);
        bitmap.SetMask(mask);

        return bitmap;
    }        
};

class MyApp : public wxApp
{
public:   
    bool OnInit()
    {
        wxInitAllImageHandlers();
        
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);

rando
Earned a small fee
Earned a small fee
Posts: 24
Joined: Fri Nov 09, 2018 9:11 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by rando » Thu Jan 24, 2019 1:28 pm

Sorry! I should have shown my button creation code

Code: Select all

wxButton( collapsible_pane_panel, wxID_ANY, _("UNIFORM"), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE|wxBU_NOTEXT);
wxBORDER_NONE removes the border then I set the button background to the same as the parent (which is usually black but I have collapsible panes that are grayish and those are where this problem is evident because the text cannot be the same color as the gray background).

Code: Select all

SetBackgroundColour( wxColour( 192, 192, 192 ) );

PB
Part Of The Furniture
Part Of The Furniture
Posts: 1834
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by PB » Thu Jan 24, 2019 2:07 pm

I may be missing something but TBH, I would probably not bother with a complicated maintaining of transparency and rendered the button bitmaps on the fly, with background being the same as their parents, assuming the background is a solid color.
memdcbutton.png
memdcbutton.png (26.22 KiB) Viewed 172 times

Code: Select all

##include <wx/wx.h>

class MyDialog : public wxDialog
{
public:
    MyDialog () : wxDialog(NULL, wxID_ANY, "Test")
    {                                                                     
        if ( !m_backgroundBitmap.LoadFile("choice_strip_left_selected.png", wxBITMAP_TYPE_PNG) )
            return;                
        wxBoxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);

        mainSizer->Add(CreatePanel(), wxSizerFlags().Expand());
        mainSizer->Add(CreatePanel(wxBLACK), wxSizerFlags().Expand());
        mainSizer->Add(CreatePanel(wxRED), wxSizerFlags().Expand());
        SetSizerAndFit(mainSizer);                 
    }   
private:     
    wxBitmap m_backgroundBitmap;

    wxPanel* CreatePanel(const wxColour* colour = NULL)
    {
        wxPanel* panel = new wxPanel(this);
        wxBoxSizer* panelSizer = new wxBoxSizer(wxVERTICAL);

        if ( colour )
            panel->SetBackgroundColour(*colour);

        AddButton(panel, panelSizer, "Button A", *wxBLACK);
        AddButton(panel, panelSizer, "Button B", *wxRED);
        AddButton(panel, panelSizer, "Button C", *wxBLUE);
        AddButton(panel, panelSizer, "Button D", *wxYELLOW);

        panel->SetSizer(panelSizer);

        return panel;
    }
   
    void AddButton(wxWindow* parent, wxSizer* sizer, 
                    const wxString& labelText, const wxColour& labelColour)
    {
        wxBitmap bitmapWithLabel = AddLabelToBackgroundBitmap(m_backgroundBitmap, 
                                    parent->GetBackgroundColour(), labelText, labelColour);
        
        wxButton* button = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxBU_NOTEXT);
        button->SetBitmap(bitmapWithLabel);
        sizer->Add(button, wxSizerFlags().Border().Expand());
    }

    wxBitmap AddLabelToBackgroundBitmap(const wxBitmap& backgroundBitmap, const wxColour& backgroundColour,
                                        const wxString& labelText, const wxColour& labelColour)
    {
        wxBitmap bitmap(backgroundBitmap.GetSize());                        
        wxMemoryDC memoryDC(bitmap);        
        wxFont font(*wxNORMAL_FONT);

        font.MakeLarger().MakeBold();
       
        wxDCFontChanger fontChanger(memoryDC, font);        
        wxDCTextColourChanger textColourChanger(memoryDC, labelColour);
        wxRect rect(memoryDC.GetSize());
   
        memoryDC.SetBackground(wxBrush(backgroundColour));
        memoryDC.Clear();
        memoryDC.DrawBitmap(backgroundBitmap, 0, 0, true);
        memoryDC.DrawLabel(labelText, rect, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL);
        memoryDC.SelectObject(wxNullBitmap);        
              
        return bitmap;
    }        
};

class MyApp : public wxApp
{
public:   
    bool OnInit()
    {
        wxInitAllImageHandlers();
        
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);

rando
Earned a small fee
Earned a small fee
Posts: 24
Joined: Fri Nov 09, 2018 9:11 pm

Re: wxMemoryDC on wxBitmap uses button background/foreground

Post by rando » Thu Jan 24, 2019 8:45 pm

Ok, I gave up on figuring out what is happening and instead just pasted PB's code into a function and then adapted it minimally to work with the flow I have designed. The following is what I came up with. Thanks for the help PB!

Code: Select all

bool DrawLabelOnBitmapButton( wxButton* button, const wxString& label, const wxFont& font, const wxColour font_color )
{
	wxBitmap background_bitmap = button->GetBitmap();
	wxBitmap bitmap(background_bitmap.GetSize());
	wxMemoryDC memory_dc(bitmap);
	wxDCFontChanger fontChanger(memory_dc, font);
	wxDCTextColourChanger textColourChanger(memory_dc, font_color);
	wxRect rect(memory_dc.GetSize());
	memory_dc.SetBackground(wxBrush(button->GetParent()->GetBackgroundColour()));
	memory_dc.Clear();
	memory_dc.DrawBitmap(background_bitmap, 0, 0, true);
	memory_dc.DrawLabel(label, rect, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL);
	memory_dc.SelectObject(wxNullBitmap);
	button->SetBitmap(bitmap);
	return true;
}

Post Reply