Gradient Text

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
MallocThenFree
In need of some credit
In need of some credit
Posts: 4
Joined: Thu Apr 29, 2021 3:29 pm

Gradient Text

Post by MallocThenFree » Thu Apr 29, 2021 8:44 pm

Hello,

I'm working with wxWidgets 3.1.5 and would like to have some gradient text in my app similar to:

Image

I'm certain there's a way to do this, but I could use some pointers in the right direction. I'm not exactly sure what pieces in the library to put together to accomplish this, so any tips or insight would be greatly appreciated. Apologies if this has been asked before and I've missed the post. Thanks!

User avatar
doublemax
Moderator
Moderator
Posts: 16014
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Gradient Text

Post by doublemax » Fri Apr 30, 2021 12:01 am

This could (and should) be very easy, unfortunately the option to set a brush for a font was "forgotten" in wxGraphicsContext.
The documentation for wxGraphicsContext::DrawText() is wrong, the brush you can pass here is not used to fill the text, it's used to fill the background.

So i only came up with this complicated way: You first draw the text into a wxBitmap, then you create a wxRegion from that bitmap, which you can then use as a clipping mask for any drawing operation.

Code: Select all

#include "wx/graphics.h"
#include "wx/dcgraph.h"
#include "wx/dcbuffer.h"

class MyPanel : public wxPanel
{
public:
  MyPanel( wxWindow *parent, wxWindowID id)
    : wxPanel(parent, id)
  {
    SetBackgroundStyle(wxBG_STYLE_PAINT);
    Bind(wxEVT_PAINT, &MyPanel::OnPaint, this);
  }

  void OnPaint( wxPaintEvent &event )
  {
    wxBufferedPaintDC dc(this);

    const wxSize &size = GetClientSize();
    wxGraphicsContext *gc = wxGraphicsContext::Create(dc);

    // clear background of the window with black
    gc->SetBrush(*wxBLACK_BRUSH);
    gc->DrawRectangle( 0, 0, size.x, size.y);
    
    // create a mask by drawing the text into a wxBitmap and then creating a wxRegion from that wxBitmap 
    wxFont font(48, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Tahoma");

    wxString s("SOME TEXT");
    dc.SetFont( font );
    // calculate size needed for text
    wxSize textSize = dc.GetTextExtent( s );
    wxBitmap bmp(textSize.x, textSize.y);
    {
      wxMemoryDC mdc(bmp);
      mdc.SetBrush(*wxWHITE_BRUSH);
      mdc.Clear();

      mdc.SetTextForeground(*wxBLACK);
      mdc.SetFont( font );
      mdc.DrawText( s, 0, 0 );
    }

    // bitmap now contains the outline of the text
    // use it as clipping region
    wxRegion rgn(bmp, *wxWHITE );
    gc->Clip(rgn);

    wxGraphicsBrush brush = gc->CreateLinearGradientBrush(0, 0, textSize.x, 0, wxColor(255,0,0), wxColor(0,255,0) );
    gc->SetBrush( brush );

    // draw rectangle using the gradient brush
    gc->DrawRectangle(0, 0, textSize.x, textSize.y);

    delete gc;
  }
};
Use the source, Luke!

MallocThenFree
In need of some credit
In need of some credit
Posts: 4
Joined: Thu Apr 29, 2021 3:29 pm

Re: Gradient Text

Post by MallocThenFree » Fri Apr 30, 2021 1:18 am

doublemax wrote:
Fri Apr 30, 2021 12:01 am
This could (and should) be very easy, unfortunately the option to set a brush for a font was "forgotten" in wxGraphicsContext.
The documentation for wxGraphicsContext::DrawText() is wrong, the brush you can pass here is not used to fill the text, it's used to fill the background.

So i only came up with this complicated way: You first draw the text into a wxBitmap, then you create a wxRegion from that bitmap, which you can then use as a clipping mask for any drawing operation.
Thank you! This is great. I was thinking along the same lines, but didn't know what pieces I needed. I'll give this a try. Thanks again!

MallocThenFree
In need of some credit
In need of some credit
Posts: 4
Joined: Thu Apr 29, 2021 3:29 pm

Re: Gradient Text

Post by MallocThenFree » Mon May 03, 2021 10:04 pm

Thank you again for the code snippet. It worked perfectly. There does appear to be some aliasing on the drawn text, which isn't a huge deal, but wondering if there's anything that can be done about it. Here's a snap of what I'm getting:
wxMemDCHelloWorld.png
wxMemDCHelloWorld.png (36.9 KiB) Viewed 783 times
Any ideas?

BTW, I'm on MacOS 10.15.7, wxWidgets 3.1.5.

User avatar
doublemax
Moderator
Moderator
Posts: 16014
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Gradient Text

Post by doublemax » Mon May 03, 2021 10:27 pm

As the region is effectively just a 1 bit mask, you can't get antialiasing. It's a little strange though that the edges on the image are 2x2 pixels. I guess it's somehow related to Retina displays, but i don't understand how.

Another idea:
draw the text into a bitmap (black on white background)
draw the gradient into another bitmap of the same size.
do the compositing yourself, pixel-by-pixel (the greyvalue of the first bitmap will be the blending factor)

The last step would be slow, but still reasonably fast when using wxPixelData:
https://docs.wxwidgets.org/trunk/classw ... _data.html

Or, you could make a feature request at https://groups.google.com/g/wx-users
I would suspect that all underlying native APIs for wxGraphicsContext support brushes for fonts. At least the GDI+ renderer already supports it internally, it's just not exposed through the public API. I haven't checked the renderers for GTK and OSX.
Use the source, Luke!

MallocThenFree
In need of some credit
In need of some credit
Posts: 4
Joined: Thu Apr 29, 2021 3:29 pm

Re: Gradient Text

Post by MallocThenFree » Tue May 04, 2021 8:57 am

Thanks again doublemax! I appreciate your help!

Post Reply