wxWidgets / wxWidgets

Cross-Platform C++ GUI Library
https://www.wxwidgets.org/
6.04k stars 1.76k forks source link

On MSW platform, the text drawn on a wxBitmap using wxMemoryDC, doesn't apply the antialliasing mode. #23586

Open rossanoparis opened 1 year ago

rossanoparis commented 1 year ago

Description

On MSW platform, the text drawn on a wxBitmap using wxMemoryDC, doesn't apply the antialliasing mode. Other primitives drawn on the bitmap, such as circles and lines are painted correctly. The bitmap used has got a transparent background.

In my example I draw entities on the bitmap first and then I paint the bitmap on a wxFrame, the same reported issue can be observed saving such bitmap in a png file.

To Reproduce

it is necessary to compile these files src.zip

Platform and version information

Windows platform (which is affected) image image

Linux platform (which is OK) image image

vadz commented 1 year ago

It would be nice to have a small patch to the drawing sample reproducing the issue instead of a ZIP, but in any case, we don't have much control over how wxDC::DrawText() behaves. Consider using wxGraphicsContext instead.

rossanoparis commented 1 year ago

Excuse me @vadz inside the zip there are the files to compile in order to get the executable. Im not able to prepare a patch, I'm sorry could you explay ho should I do ....

Anyway I use wxGraphicsContext ... Should I post the code ?

vadz commented 1 year ago

Yes, to be clear, it's much simpler for me to see what the code does by looking at a (probably very small) diff to the sample rather than dealing with the ZIP.

The link in the comment above explains how to make a patch.

rossanoparis commented 1 year ago

I'll try to us a sample among those provided, in the meantime let me post the code which is very simple, perhaps I'm doing something wrong, even though using linux it works.

void CMainFrame::OnPaint(wxPaintEvent& event)
{
    PaintWindow();
    event.Skip();
}
void CMainFrame::PaintWindow()
{
    wxBitmap bmp(this->GetClientSize(), 32);
    bmp.UseAlpha();

    if (bmp.IsOk()) {
        wxMemoryDC memdc(bmp);
        wxGCDC gcdc(memdc);

        // Init GC from bitmap
        wxGraphicsContext* pGC = wxGraphicsContext::Create(memdc);
        gcdc.SetBackground(*wxTRANSPARENT_BRUSH);
        gcdc.SetGraphicsContext(pGC);
        gcdc.Clear();

        // Valid GC context
        if (gcdc.IsOk()) {
            // Draw widgets on bitmap
            Draw(gcdc.GetGraphicsContext());

            // Release bitmap
            memdc.SelectObject(wxNullBitmap);

            // Draw bitmap on window
            wxClientDC dc(this);
            dc.DrawBitmap(bmp, 0, 0, false);
        }
    }
}
void CMainFrame::Draw(wxGraphicsContext* pGC)
{
    // Set graphics context attributes
    pGC->SetInterpolationQuality(wxINTERPOLATION_BEST);
    pGC->SetAntialiasMode(wxANTIALIAS_DEFAULT);

    //Draw line
    wxPoint2DDouble points[2] = { wxPoint2DDouble(400, 200), wxPoint2DDouble(50, 400) };
    pGC->SetBrush(*wxTRANSPARENT_BRUSH);
    pGC->SetPen(wxPen(wxColour(125, 125, 125), 2, wxPENSTYLE_SOLID));
    pGC->StrokeLines(2, points);

    //Draw circle
    pGC->SetPen(wxPen(wxColour(255, 215, 0), 2, wxPENSTYLE_SOLID));
    pGC->SetBrush(wxColour(255, 0, 0, 125));
    pGC->DrawEllipse(50, 50, 200, 200);

    //Draw rounded rectangle
    pGC->SetPen(wxPen(wxColour(255, 0, 0), 2, wxPENSTYLE_SOLID));
    pGC->SetBrush(wxColour(0, 255, 0, 125));
    pGC->DrawRoundedRectangle(150, 150, 200, 250, 25);

    //Draw text
    wxFont fnt = this->GetFont();
    fnt.SetPointSize(28);

    pGC->SetBrush(*wxTRANSPARENT_BRUSH);
    pGC->SetFont(fnt, wxColour(0, 255, 0));
    pGC->DrawText("1) My TEXT to draw", 10, 10, 0.0);
    pGC->DrawText("2) My TEXT to draw", 10, 350, 45);
    pGC->DrawText("3) My TEXT to draw", 300, 50, -45);
    pGC->DrawText("4) My TEXT to draw", 100, 400, 0.0);
}
oneeyeman1 commented 1 year ago

@hobbitdev , Please check the documentation at https://docs.wxwidgets.org/latest/classwx_paint_event.html#ad6d201c4b88ecf72ab454d174a7d607b.

In particular you suppose to create wxPai nDC in the handler and not create any other DC there.

Thank you.

rossanoparis commented 1 year ago

Thank you @oneeyeman1, you are right. I've forgotten that specification, I'm sorry for that.

In my application I need to draw on a transparent bitmap and after that overlay the window backgroung which has been painted with other stuff. Indeed I need to find another strategy regarding the DC able to draw on the bitmap, perhaps an object which is member of the class and created elsewhere not in wxPaintEvent functions chain.

rossanoparis commented 1 year ago

Kind @vadz and @oneeyeman1

I've implemented the painting of the bitmap into the wxFrame constructor, despite this the result didn't change.

I've implemented my code in minimal example (minimal.cpp) Due to my poor knowledge of git I couldn't prepare the patch asked; I beg your pardon for that. Anyway if you could accept the zipped minimal.cpp file [ minimal.zip ], I would be very grateful. My code is recognizable by the label #23586

BrianNixon commented 1 year ago

In the screenshots it looks like at least the horizontal text is being antialiased; it’s just not being blended properly. That may be a clash between GDI+ text rendering modes and the underlying GDI device context containing the bitmap. There’s some possibly-related discussion on SO here.

With GDI+, SetAntialiasMode(wxANTIALIAS_DEFAULT) calls SetTextRenderingHint(TextRenderingHintSystemDefault), which will have an effect dependent on the user’s settings for font smoothing. If that’s on, it seems GDI+ will use one of the GridFit modes, which appear not to work well (in this case, at least). Changing it to TextRenderingHintAntiAlias makes the output in this example appear as expected. But that is probably not a solution for the general case. (It’s not clear whether in wx “default” is supposed to mean “on”, regardless of the user’s other settings; note also that wxANTIALIAS_NONE does not yield the same output as wxANTIALIAS_DEFAULT with font smoothing disabled.)

(I don’t know how any of this affects the Direct2D case; that doesn’t work at all for me. On Windows 7 it throws an exception deep inside DirectX (in dxgi!​CD3D10Resource::​CGdiInterop::​CreateDeviceBitmap while trying to create the wxGCDC), which I haven’t investigated further – though the location of the fault is another hint that there’s a problem related to the GDI bitmap. On Windows 10 and 11 there’s no exception, but no output to the window either.)

rossanoparis commented 1 year ago

Thank you @BrianNixon

Is there a temporary work-around able to make my code working ? Could you please suggest it in case ? I tried to look at the link SetTextRenderingHint but I'm no able to find the HDC handle required by Graphic object

BrianNixon commented 1 year ago

In Draw() you can do:

#if defined(__WXMSW__) && wxUSE_GRAPHICS_GDIPLUS
    Graphics* g = static_cast<Graphics*>(pGC->GetNativeContext());
    g->SetTextRenderingHint(TextRenderingHintAntiAlias);
#endif

To access the GDI+ stuff you’ll also need in MainFrame.cpp:

#if defined(__WXMSW__) && wxUSE_GRAPHICS_GDIPLUS
#include "wx/msw/wrapgdip.h"
#endif
rossanoparis commented 1 year ago

Thank you @BrianNixon it was very kind of you

BrianNixon commented 1 year ago

Prego :-⁠)

Actually I might be wrong that the GridFit modes in general don’t work well; it may just be the ClearType mode. You could try TextRenderingHintAntiAliasGridFit as well and compare results.

rossanoparis commented 1 year ago

The results using both attributes look same at my eyes. Once again thank you.

TextRenderingHintAntiAlias image

TextRenderingHintAntiAliasGridFit image

BrianNixon commented 1 year ago

It is more noticeable on the horizontal text. Vertical stems are likely to be thicker/​blurrier without grid fitting.

TextRenderingHintAntiAlias: TextRenderingHintAntiAlias

TextRenderingHintAntiAliasGridFit: TextRenderingHintAntiAliasGridFit