Font half size when using Direct2D with non-window based wxDC under HiDPI (Issue #22130)

97 views
Skip to first unread message

Blake-Madden

unread,
Feb 14, 2022, 9:28:10 PM2/14/22
to wx-...@googlegroups.com, Subscribed

When using the Direct2D renderer for wxDCs (under a HiPDI environment), font drawing is half the size it should be if using a non-window based wxDC. In other words, Direct2D with wxPaint looks OK, but if you use Direct2D with a wxMemoryDC then the font is tiny. Interestingly, it seems to only be a font issue--shape drawing is fine.

To Reproduce
On a HiDPI Windows system:

  1. Open the drawing sample and run it.
  2. Select Screen->Text screen
  3. Select Drawing->Use GDI+.
  4. Save it as a bitmap (Drawing->Save) and note that the bitmap looks like the screen.
  5. Select Drawing->Use Direct2D and save as a bitmap again. It should look the same.
  6. Now, change the sample to be DPI aware.
    • In Visual Studio, Project->drawing Properties->Manifest Tool->Input and Output. Set "DPI Aware" to "High DPI Aware" and rebuild.
  7. Repeat steps 2-5.

Note how the bitmap saved when Direct2D was in use has its text half the size as the GDI+ image. If you try this with other screens the problem doesn't happen with shapes and whatnot; it appears to be a font size issue.

Digging into the code, I think it has do with the wxGraphicsContext created from m_renderer->CreateContext(). If the wxDC passed into this has a window related to it (e.g., a wxPaintDC), then the wxGraphicsContext will be aware of the DPI. However, if a wxMemoryDC is passed, then it doesn't know what the DPI is and falls back to the default 96. I can see that the context created from a wxMemoryDC reports 96 when I call GetDPI(). However, I get 192 when I call GetDPI() with the context created from a wxPaintDC. I'm guessing this is the root cause.

Platform and version information

  • wxWidgets 3.1.6
  • Windows 10 (x64)
  • HiDPI display required (I'm using a Surface laptop)


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130@github.com>

Kumazuma

unread,
Feb 15, 2022, 8:29:11 AM2/15/22
to wx-...@googlegroups.com, Subscribed

creataion of wxGraphicsFont for d2d

// src/msw/graphicsd2d.cpp:5112
wxGraphicsFont wxD2DRenderer::CreateFont(
    double sizeInPixels, const wxString& facename,
    int flags,
    const wxColour& col)
{
    // Use the same DPI as wxFont will use in SetPixelSize, so these cancel
    // each other out and we are left with the actual pixel size.
    ScreenHDC hdc;
    wxRealPoint dpi(::GetDeviceCaps(hdc, LOGPIXELSX),
                    ::GetDeviceCaps(hdc, LOGPIXELSY));

    return CreateFontAtDPI(
        wxFontInfo(wxSize(sizeInPixels, sizeInPixels)).AllFlags(flags).FaceName(facename),
        dpi, col);
}

so... when dpi is not specific by user, dpi is set using screendc.

in same process on GDI+

// src/msw/graphics.cpp:2858
wxGraphicsFont
wxGDIPlusRenderer::CreateFont( const wxFont &font,
                               const wxColour &col )
{
    return CreateFontAtDPI(font, wxRealPoint(), col);
}

so I suggest

wxGraphicsFont wxD2DRenderer::CreateFont(
    double sizeInPixels, const wxString& facename,
    int flags,
    const wxColour& col)
{
    return CreateFontAtDPI(
        wxFontInfo(wxSize(sizeInPixels, sizeInPixels)).AllFlags(flags).FaceName(facename),
        wxRealPoint(), col);
}


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1040274347@github.com>

Blake-Madden

unread,
Feb 15, 2022, 4:03:47 PM2/15/22
to wx-...@googlegroups.com, Subscribed

No, didn't seem to help. wxGraphicsContext::CreateFont (graphicscmn.cpp, line 952) still relies on GetDPI, which incorrectly reports 96 (not 192). The lack of a window in the graphics context causes GetDPI to be problem.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1040790246@github.com>

Maarten

unread,
Feb 15, 2022, 4:11:28 PM2/15/22
to wx-...@googlegroups.com, Subscribed

As a workaround/fix, you can associate the window with the wxMemoryDC. In the drawing sample: mdc.GetImpl()->SetWindow(this);


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1040796601@github.com>

Blake-Madden

unread,
Feb 15, 2022, 4:18:07 PM2/15/22
to wx-...@googlegroups.com, Subscribed

mdc.GetImpl()->SetWindow(this);

That totally works! Thank you!


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1040802226@github.com>

VZ

unread,
Mar 25, 2022, 8:42:54 PM3/25/22
to wx-...@googlegroups.com, Subscribed

I don't see any differences in the text size between the 3 bitmaps saved while running with 200% DPI scaling, see:

  1. GDI:
    gdi
  2. GDI+:
    gdiplus
  3. Direct2D:
    d2d

I do see that the text is smaller than the much larger (and overlapping because of it...) text shown on screen. But normally I would say that this ought to be fixed with

diff --git a/samples/drawing/drawing.cpp b/samples/drawing/drawing.cpp
index 55e8e62c52..717f880560 100644
--- a/samples/drawing/drawing.cpp
+++ b/samples/drawing/drawing.cpp
@@ -2535,7 +2535,8 @@ void MyFrame::OnSave(wxCommandEvent& WXUNUSED(event))
         else
 #endif // wxUSE_POSTSCRIPT
         {
-            wxBitmap bmp(width, height);
+            wxBitmap bmp;
+            bmp.CreateWithDIPSize(wxSize(width, height), GetDPIScaleFactor());
             wxMemoryDC mdc(bmp);
             mdc.SetBackground(*wxWHITE_BRUSH);
             mdc.Clear();

(the use of DIP size is due to the fact that nothing is being scaled by DPI in this sample right now, so all pixels are actually DPI-independent in it under MSW) but this doesn't work neither right now because the bitmap scale factor is ignored by wxMemoryDC under MSW. I've tried fixing this in #22234 and with it the saved bitmap looks roughly like this in all 3 cases:

fixed

which is ugly but corresponds to the picture actually shown on the screen.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1079536488@github.com>

VZ

unread,
Mar 26, 2022, 10:35:16 AM3/26/22
to wx-...@googlegroups.com, Subscribed

I don't see any differences in the text size between the 3 bitmaps saved while running with 200% DPI scaling

Correction: as discussed in #22234, I do see the problem when actually running in 200% DPI and not just moving the window to a monitor using 200% DPI (while the main display still uses 100%).


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1079706512@github.com>

VZ

unread,
Mar 27, 2022, 7:15:41 PM3/27/22
to wx-...@googlegroups.com, Subscribed

Closed #22130 via 9e5c8a8.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issue/22130/issue_event/6314392140@github.com>

Blake-Madden

unread,
Apr 15, 2022, 11:19:50 AM4/15/22
to wx-...@googlegroups.com, Subscribed

Verified in my personal project as well. Thanks for the fix!


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/22130/1100171515@github.com>

Eran Ifrah

unread,
Apr 15, 2022, 12:26:13 PM4/15/22
to wx-...@googlegroups.com, Subscribed
- wxBitmap bmp(width, height);
> +            wxBitmap bmp;
> +            bmp.CreateWithDIPSize(wxSize(width, height), GetDPIScaleFactor());
Sorry to drop on this one, but if this is the solution, why not call it in the c-tor? (wxBitmap(width, height))
This single change (wrong font size using wxMemoryDC) caused me hours of work...
My only solution at the time was to drop wxMemoryDC and use wxClientDC.
Had I known this workaround, I would have used it...
Some background:

In my code, I used wxMemoryDC when outside of paint handler to calculate various sizes, and when in OnPaint, I used wxGCDC constructed from wxPaintDC
with the release of 3.1.6 all my custom controls drawings were "off" (overlapping text misaligned drawings etc)

--
You received this message because you are subscribed to the Google Groups "wx-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wx-dev+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/wx-dev/wxWidgets/wxWidgets/issues/22130/1100171515%40github.com.


--

Eran Ifrah
Author of CodeLite IDE https://codelite.org

Maarten Bent

unread,
Apr 15, 2022, 12:49:34 PM4/15/22
to wx-...@googlegroups.com
Hi,

The wxBitmap constructor has no access to the object (wxWindow, wxMemoryDC, ...) that is creating it,
so it cannot call GetDPIScaleFactor() on it.

Maybe CreateWithDIPSize can be mentioned in the changelog so it is easier to notice?

Maarten

Eran Ifrah

unread,
Apr 15, 2022, 12:55:27 PM4/15/22
to wx-...@googlegroups.com
Hi Maarten,

How about creating a hidden global control and use that as the window for getting the scale factor?
I know that this kind of trick is/was used in various places like wxSystemSettings to get colours etc (on wxGTK at least)

Change log is not a good solution as it's not visible enough - we need something more proactive to notify the users that an action should be taken

Maarten Bent

unread,
Apr 15, 2022, 1:04:00 PM4/15/22
to wx-...@googlegroups.com
On a setup with multiple monitors (with different DPI) this will not work. Since you don't know on which monitor to create the hidden control.

Vadim Zeitlin

unread,
Apr 16, 2022, 9:28:12 AM4/16/22
to wx-...@googlegroups.com
On Fri, 15 Apr 2022 19:25:59 +0300 Eran Ifrah wrote:

EI> Some background:
EI>
EI> In my code, I used wxMemoryDC when outside of paint handler to calculate
EI> various sizes, and when in OnPaint, I used wxGCDC constructed from wxPaintDC
EI> with the release of 3.1.6 all my custom controls drawings were "off"
EI> (overlapping text misaligned drawings etc)

Sorry, I still don't understand the problem, could you please explain it
in more details? AFAICS with 3.1.6 things should work correctly when using
DPI scaling in the scenario above, provided you use wxMemoryDC with a
bitmap using the same content scaling factor, because now wxMemoryDC uses
the same font size as wxGCDC used even before, whereas previously they
didn't work correctly because wxMemoryDC always used scaling factor of 1.

So how did this work for you before, did you do your own scaling?

Thanks,
VZ
Reply all
Reply to author
Forward
0 new messages