Repository: FreeCAD/FreeCAD
Pull request #20594: Gui: make BitmapFactory::pixmapFromSvg dpi aware
Author: login: captain0xff
Labels: name: Mod: Core
description: Issue or PR touches core sections (App, Gui, Base) of FreeCAD
Created: 2025-04-02T15:35:44Z | Merged: 2025-04-04T22:32:22Z

Partially fixes https://github.com/FreeCAD/FreeCAD/issues/17602#issuecomment-2721420581

![image](https://github.com/user-attachments/assets/ee774283-095d-4eb8-89e9-6933631821bc)
These arrow icons appear to be PNGs so this PR doesn't fix their scaling issue.


Diff:
diff --git a/src/Gui/BitmapFactory.cpp b/src/Gui/BitmapFactory.cpp
index 5c70844bb196..0bb4f6c5ebbb 100644
--- a/src/Gui/BitmapFactory.cpp
+++ b/src/Gui/BitmapFactory.cpp
@@ -32,6 +32,7 @@
 # include <QImageReader>
 # include <QPainter>
 # include <QPalette>
+# include <QScreen>
 # include <QString>
 # include <QSvgRenderer>
 # include <QStyleOption>
@@ -277,6 +278,8 @@ QPixmap BitmapFactoryInst::pixmap(const char* name) const
 QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
                                          const ColorMap& colorMapping) const
 {
+    static qreal dpr = getMaximumDPR();
+    
     // If an absolute path is given
     QPixmap icon;
     QString iconPath;
@@ -304,21 +307,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, size * dpr, colorMapping);
         }
     }
 
-    return icon;
-}
+    if (!icon.isNull()) {
+        icon.setDevicePixelRatio(dpr);
+    }
 
-QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size, qreal dpr,
-                                         const ColorMap& colorMapping) const
-{
-    qreal width = size.width() * dpr;
-    qreal height = size.height() * dpr;
-    QPixmap px(pixmapFromSvg(name, QSizeF(width, height), colorMapping));
-    px.setDevicePixelRatio(dpr);
-    return px;
+    return icon;
 }
 
 QPixmap BitmapFactoryInst::pixmapFromSvg(const QByteArray& originalContents, const QSizeF& size,
@@ -677,3 +674,14 @@ QIcon BitmapFactoryInst::mergePixmap (const QIcon &base, const QPixmap &px, Gui:
 
     return overlayedIcon;
 }
+
+qreal BitmapFactoryInst::getMaximumDPR()
+{
+    qreal dpr = 1.0F;
+
+    for (QScreen* screen: QGuiApplication::screens()) {
+        dpr = std::max(screen->devicePixelRatio(), dpr);
+    }
+
+    return dpr;
+}
diff --git a/src/Gui/BitmapFactory.h b/src/Gui/BitmapFactory.h
index 23288589afbc..22c79251aeb1 100644
--- a/src/Gui/BitmapFactory.h
+++ b/src/Gui/BitmapFactory.h
@@ -87,14 +87,6 @@ class GuiExport BitmapFactoryInst : public Base::Factory
      */
     QPixmap pixmapFromSvg(const char* name, const QSizeF& size,
                           const ColorMap& colorMapping = ColorMap()) const;
-    /** Retrieves a pixmap by name and size created by an
-     * scalable vector graphics (SVG) and a device pixel ratio
-     *
-     * @param colorMapping - a dictionary of substitute colors.
-     * Can be used to customize icon color scheme, e.g. crosshair color
-     */
-    QPixmap pixmapFromSvg(const char* name, const QSizeF& size, qreal dpr,
-                          const ColorMap& colorMapping = ColorMap()) const;
     /** This method is provided for convenience and does the same
      * as the method above except that it creates the pixmap from
      * a byte array.
@@ -149,6 +141,8 @@ class GuiExport BitmapFactoryInst : public Base::Factory
     /// Helper method to merge a pixmap into one corner of a QIcon
     static QIcon mergePixmap (const QIcon &base, const QPixmap &px, Gui::BitmapFactoryInst::Position position);
 
+    static qreal getMaximumDPR();
+
 private:
     bool loadPixmap(const QString& path, QPixmap&) const;
     void restoreCustomPaths();
diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp
index 2583de287d85..4af26bb8cf21 100644
--- a/src/Gui/CommandView.cpp
+++ b/src/Gui/CommandView.cpp
@@ -2697,10 +2697,7 @@ class SelectionCallbackHandler {
             hotYF *= pRatio;
         }
 #endif
-        qreal cursorWidth = size.width() * pRatio;
-        qreal cursorHeight = size.height() * pRatio;
-        QPixmap px(Gui::BitmapFactory().pixmapFromSvg(svgFile, QSizeF(cursorWidth, cursorHeight)));
-        px.setDevicePixelRatio(pRatio);
+        QPixmap px(Gui::BitmapFactory().pixmapFromSvg(svgFile, size));
         return QCursor(px, hotXF, hotYF);
     }
 };
diff --git a/src/Gui/ToolHandler.cpp b/src/Gui/ToolHandler.cpp
index 606e2527be4d..182fa8c6d21f 100644
--- a/src/Gui/ToolHandler.cpp
+++ b/src/Gui/ToolHandler.cpp
@@ -120,7 +120,7 @@ void ToolHandler::setSvgCursor(const QString& cursorName,
     //
     qreal pRatio = devicePixelRatio();
     bool isRatioOne = (pRatio == 1.0);
-    qreal defaultCursorSize = isRatioOne ? 64 : 32;
+    qreal cursorSize = isRatioOne ? 64 : 32;
     qreal hotX = x;
     qreal hotY = y;
 #if !defined(Q_OS_WIN32) && !defined(Q_OS_MACOS)
@@ -129,15 +129,13 @@ void ToolHandler::setSvgCursor(const QString& cursorName,
         hotY *= pRatio;
     }
 #endif
-    qreal cursorSize = defaultCursorSize * pRatio;
 
     QPixmap pointer = Gui::BitmapFactory().pixmapFromSvg(cursorName.toStdString().c_str(),
-                                                         QSizeF(cursorSize, cursorSize),
+                                                         QSizeF{cursorSize, cursorSize},
                                                          colorMapping);
     if (isRatioOne) {
         pointer = pointer.scaled(32, 32);
     }
-    pointer.setDevicePixelRatio(pRatio);
     setCursor(pointer, hotX, hotY, false);
 }
 
diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp
index 77adeb35ee8a..ccb23dc5be76 100644
--- a/src/Gui/Tree.cpp
+++ b/src/Gui/Tree.cpp
@@ -5488,7 +5488,7 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon& icon1, QIcon& icon2
 
         if (currentStatus & Status::External) {
             static QPixmap pxExternal;
-            int px = 12 * getMainWindow()->devicePixelRatioF();
+            constexpr int px = 12;
             if (pxExternal.isNull()) {
                 pxExternal = Gui::BitmapFactory().pixmapFromSvg("LinkOverlay",
                                                               QSize(px, px));
diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp
index 9338ff37add1..e278d5a2cf8a 100644
--- a/src/Gui/ViewProviderLink.cpp
+++ b/src/Gui/ViewProviderLink.cpp
@@ -1730,7 +1730,7 @@ QIcon ViewProviderLink::getIcon() const {
 
 QPixmap ViewProviderLink::getOverlayPixmap() const {
     auto ext = getLinkExtension();
-    int px = 12 * getMainWindow()->devicePixelRatioF();
+    constexpr int px = 12;
     if(ext && ext->getLinkedObjectProperty() && ext->_getElementCountValue())
         return BitmapFactory().pixmapFromSvg("LinkArrayOverlay", QSizeF(px,px));
     else if(hasSubElement)
diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp
index 4ad49813841b..090ea98dc23a 100644
--- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp
+++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp
@@ -1129,8 +1129,8 @@ class DrawSketchHandlerGenConstraint: public DrawSketchHandler
         auto colorMapping = std::map<unsigned long, unsigned long>();
         colorMapping[defaultCrosshairColor] = color;
 
-        qreal fullIconWidth = 32 * pixelRatio;
-        qreal iconWidth = 16 * pixelRatio;
+        constexpr qreal fullIconWidth = 32;
+        constexpr qreal iconWidth = 16;
         QPixmap cursorPixmap =
                     Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair",
                                                        QSizeF(fullIconWidth, fullIconWidth),
@@ -1143,7 +1143,6 @@ class DrawSketchHandlerGenConstraint: public DrawSketchHandler
         cursorPainter.end();
         int hotX = 8;
         int hotY = 8;
-        cursorPixmap.setDevicePixelRatio(pixelRatio);
         // only X11 needs hot point coordinates to be scaled
         if (qGuiApp->platformName() == QLatin1String("xcb")) {
             hotX *= pixelRatio;
@@ -1422,17 +1421,16 @@ class DrawSketchHandlerDimension : public DrawSketchHandler
         auto colorMapping = std::map<unsigned long, unsigned long>();
         colorMapping[defaultCrosshairColor] = color;
 
-        qreal fullIconWidth = 32 * pixelRatio;
-        qreal iconWidth = 16 * pixelRatio;
-        QPixmap cursorPixmap = Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair", QSizeF(fullIconWidth, fullIconWidth), colorMapping),
-            icon = Gui::BitmapFactory().pixmapFromSvg("Constraint_Dimension", QSizeF(iconWidth, iconWidth));
+        constexpr qreal fullIconWidth = 32;
+        constexpr qreal iconWidth = 16;
+        QPixmap cursorPixmap = Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair", QSizeF(fullIconWidth, fullIconWidth), colorMapping);
+        QPixmap icon = Gui::BitmapFactory().pixmapFromSvg("Constraint_Dimension", QSizeF(iconWidth, iconWidth));
         QPainter cursorPainter;
         cursorPainter.begin(&cursorPixmap);
         cursorPainter.drawPixmap(16 * pixelRatio, 16 * pixelRatio, icon);
         cursorPainter.end();
         int hotX = 8;
         int hotY = 8;
-        cursorPixmap.setDevicePixelRatio(pixelRatio);
         // only X11 needs hot point coordinates to be scaled
         if (qGuiApp->platformName() == QLatin1String("xcb")) {
             hotX *= pixelRatio;
diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
index b6ff76dfd816..efbfee9143c5 100644
--- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
+++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
@@ -406,12 +406,7 @@ DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& sugg
                 break;
         }
         if (!iconType.isEmpty()) {
-            qreal pixelRatio = 1;
-            Gui::View3DInventorViewer* viewer = getViewer();
-            if (viewer) {
-                pixelRatio = viewer->devicePixelRatio();
-            }
-            int iconWidth = 16 * pixelRatio;
+            constexpr int iconWidth = 16;
             QPixmap icon = Gui::BitmapFactory().pixmapFromSvg(iconType.toStdString().c_str(),
                                                               QSize(iconWidth, iconWidth));
             pixmaps.push_back(icon);
diff --git a/src/Mod/TechDraw/Gui/CommandCreateDims.cpp b/src/Mod/TechDraw/Gui/CommandCreateDims.cpp
index 983de413a236..706fb80cd536 100644
--- a/src/Mod/TechDraw/Gui/CommandCreateDims.cpp
+++ b/src/Mod/TechDraw/Gui/CommandCreateDims.cpp
@@ -1364,12 +1364,7 @@ class TDHandlerDimension : public TechDrawHandler,
 
     QPixmap icon(std::string name)
     {
-        qreal pixelRatio = 1;
-        Gui::View3DInventorViewer* viewer = getViewer();
-        if (viewer) {
-            pixelRatio = viewer->devicePixelRatio();
-        }
-        int width = 16 * pixelRatio;
+        constexpr int width = 16;
         return Gui::BitmapFactory().pixmapFromSvg(name.c_str(), QSize(width, width));
     }
 
diff --git a/src/Mod/TechDraw/Gui/QGVPage.cpp b/src/Mod/TechDraw/Gui/QGVPage.cpp
index 893d077767c0..8bd105dfd388 100644
--- a/src/Mod/TechDraw/Gui/QGVPage.cpp
+++ b/src/Mod/TechDraw/Gui/QGVPage.cpp
@@ -557,29 +557,14 @@ QColor QGVPage::getBackgroundColor()
     return fcColor.asValue<QColor>();
 }
 
-double QGVPage::getDevicePixelRatio() const
-{
-    for (Gui::MDIView* view : m_vpPage->getDocument()->getMDIViews()) {
-        if (view->isDerivedFrom<Gui::View3DInventor>()) {
-            return static_cast<Gui::View3DInventor*>(view)->getViewer()->devicePixelRatio();
-        }
-    }
-
-    return 1.0;
-}
-
 QPixmap QGVPage::prepareCursorPixmap(const char* iconName, QPoint& hotspot)
 {
 
     QPointF floatHotspot(hotspot);
-    double pixelRatio = getDevicePixelRatio();
 
-    // Due to impossibility to query cursor size via Qt API, we stick to (32x32)*device_pixel_ratio
+    // Due to impossibility to query cursor size via Qt API, we stick to (32x32)
     // as FreeCAD Wiki suggests - see https://wiki.freecad.org/HiDPI_support#Custom_cursor_size
-    double cursorSize = 32.0 * pixelRatio;
-
-    QPixmap pixmap = Gui::BitmapFactory().pixmapFromSvg(iconName, QSizeF(cursorSize, cursorSize));
-    pixmap.setDevicePixelRatio(pixelRatio);
+    QPixmap pixmap = Gui::BitmapFactory().pixmapFromSvg(iconName, QSizeF(32, 32));
 
     // The default (and here expected) SVG cursor graphics size is 64x64 pixels, thus we must adjust
     // the 64x64 based hotspot position for our 32x32 based cursor pixmaps accordingly
@@ -590,7 +575,7 @@ QPixmap QGVPage::prepareCursorPixmap(const char* iconName, QPoint& hotspot)
     // therefore we must take care of the transformation ourselves...
     // Refer to QTBUG-68571 - https://bugreports.qt.io/browse/QTBUG-68571
     if (qGuiApp->platformName() == QLatin1String("xcb")) {
-        floatHotspot *= pixelRatio;
+        floatHotspot *= Gui::BitmapFactoryInst::getMaximumDPR();
     }
 #endif
 
diff --git a/src/Mod/TechDraw/Gui/QGVPage.h b/src/Mod/TechDraw/Gui/QGVPage.h
index 7a5b524574b4..332576871a7d 100644
--- a/src/Mod/TechDraw/Gui/QGVPage.h
+++ b/src/Mod/TechDraw/Gui/QGVPage.h
@@ -152,7 +152,6 @@ public Q_SLOTS:
 
     QColor getBackgroundColor();
 
-    double getDevicePixelRatio() const;
     QPixmap prepareCursorPixmap(const char* iconName, QPoint& hotspot);
 
     void drawForeground(QPainter* painter, const QRectF& rect) override;


Reviews:
user: login: benj5378
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:40:30Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:44:32Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:47:46Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:48:11Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:50:21Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T16:06:02Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T16:21:27Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T19:57:24Z
user: login: kadet1090
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T21:23:46Z
user: login: benj5378
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T12:58:06Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T13:40:30Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T14:13:51Z
user: login: PaddleStroke
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-08-15T11:03:13Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-15T11:08:12Z
user: login: PaddleStroke
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-08-15T12:12:11Z
user: login: kadet1090
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-15T12:36:46Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-15T16:14:44Z
user: login: PaddleStroke
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-08-15T16:38:58Z
user: login: captain0xff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-15T16:51:17Z
user: login: PaddleStroke
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-08-16T05:35:33Z

Review comments:
diff_hunk: @@ -149,6 +149,8 @@ class GuiExport BitmapFactoryInst : public Base::Factory
     /// Helper method to merge a pixmap into one corner of a QIcon
     static QIcon mergePixmap (const QIcon &base, const QPixmap &px, Gui::BitmapFactoryInst::Position position);
 
+    static qreal getMaximumDPR();
path: src/Gui/BitmapFactory.h
user: login: benj5378
body: ```suggestion
    static qreal getMaximumDPR() const;
```

created_at: 2025-04-02T15:39:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -304,21 +311,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);
         }
     }
 
-    return icon;
-}
+    if (!icon.isNull()) {
+        icon.setDevicePixelRatio(dpr);
path: src/Gui/BitmapFactory.cpp
user: login: benj5378
body: This doesn't need to be before render?
created_at: 2025-04-02T15:40:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -149,6 +149,8 @@ class GuiExport BitmapFactoryInst : public Base::Factory
     /// Helper method to merge a pixmap into one corner of a QIcon
     static QIcon mergePixmap (const QIcon &base, const QPixmap &px, Gui::BitmapFactoryInst::Position position);
 
+    static qreal getMaximumDPR();
path: src/Gui/BitmapFactory.h
user: login: captain0xff
body: static member functions can't be marked const.
created_at: 2025-04-02T15:44:32Z
author_association: MEMBER
diff_hunk: @@ -304,21 +311,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);
         }
     }
 
-    return icon;
-}
+    if (!icon.isNull()) {
+        icon.setDevicePixelRatio(dpr);
path: src/Gui/BitmapFactory.cpp
user: login: captain0xff
body: Wdym by render?
If I put it before
```icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);``` 
then I think it will probably be overwritten right?
created_at: 2025-04-02T15:47:46Z
author_association: MEMBER
diff_hunk: @@ -304,21 +311,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);
         }
     }
 
-    return icon;
-}
+    if (!icon.isNull()) {
+        icon.setDevicePixelRatio(dpr);
path: src/Gui/BitmapFactory.cpp
user: login: captain0xff
body: The docs don't mention anything about this.
created_at: 2025-04-02T15:48:11Z
author_association: MEMBER
diff_hunk: @@ -304,21 +311,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);
         }
     }
 
-    return icon;
-}
+    if (!icon.isNull()) {
+        icon.setDevicePixelRatio(dpr);
path: src/Gui/BitmapFactory.cpp
user: login: captain0xff
body: Verified. Moving that line before the assignment doesn't work.
created_at: 2025-04-02T15:50:21Z
author_association: MEMBER
diff_hunk: @@ -304,21 +311,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);
path: src/Gui/BitmapFactory.cpp
user: login: kadet1090
body: IIRC QSize does implement `operator*` with qreal: https://doc.qt.io/qt-6/qsize.html#operator-2a-1 so it should be as simple as:
```suggestion
            icon = pixmapFromSvg(content, size * dpr, colorMapping);
```

Wouldn't it work?
created_at: 2025-04-02T16:06:01Z
author_association: MEMBER
diff_hunk: @@ -304,21 +311,15 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
         QFile file(iconPath);
         if (file.open(QFile::ReadOnly | QFile::Text)) {
             QByteArray content = file.readAll();
-            icon = pixmapFromSvg(content, size, colorMapping);
+            icon = pixmapFromSvg(content, QSizeF{size.width() * dpr, size.height() * dpr}, colorMapping);
path: src/Gui/BitmapFactory.cpp
user: login: captain0xff
body: Yes it works. I wasn't aware of it. Thanks.
created_at: 2025-04-02T16:21:27Z
author_association: MEMBER
diff_hunk: @@ -677,3 +673,14 @@ QIcon BitmapFactoryInst::mergePixmap (const QIcon &base, const QPixmap &px, Gui:
 
     return overlayedIcon;
 }
+
+qreal BitmapFactoryInst::getMaximumDPR() {
+    qreal dpr = 1.0f;
+
+    QList<QScreen*> screens = QGuiApplication::screens();
+    for (auto screen: screens) {
path: src/Gui/BitmapFactory.cpp
user: login: kadet1090
body: You don't need to create that variable: 

```suggestion
    for (auto* screen : QGuiApplication::screens()) {
```
created_at: 2025-04-02T19:53:20Z
author_association: MEMBER
diff_hunk: @@ -280,6 +281,7 @@ QPixmap BitmapFactoryInst::pixmapFromSvg(const char* name, const QSizeF& size,
     // If an absolute path is given
     QPixmap icon;
     QString iconPath;
+    qreal dpr = getMaximumDPR();
path: src/Gui/BitmapFactory.cpp
user: login: kadet1090
body: This variable can be static, then it would be computed only once.
created_at: 2025-04-02T19:56:58Z
author_association: MEMBER
diff_hunk: @@ -590,7 +586,7 @@ QPixmap QGVPage::prepareCursorPixmap(const char* iconName, QPoint& hotspot)
     // therefore we must take care of the transformation ourselves...
     // Refer to QTBUG-68571 - https://bugreports.qt.io/browse/QTBUG-68571
     if (qGuiApp->platformName() == QLatin1String("xcb")) {
-        floatHotspot *= pixelRatio;
+        floatHotspot *= getDevicePixelRatio();
path: src/Mod/TechDraw/Gui/QGVPage.cpp
user: login: benj5378
body: Why do we use getDevicePixelRatio rather than the newly implemented getMaximumDPR?
created_at: 2025-04-03T12:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -590,7 +586,7 @@ QPixmap QGVPage::prepareCursorPixmap(const char* iconName, QPoint& hotspot)
     // therefore we must take care of the transformation ourselves...
     // Refer to QTBUG-68571 - https://bugreports.qt.io/browse/QTBUG-68571
     if (qGuiApp->platformName() == QLatin1String("xcb")) {
-        floatHotspot *= pixelRatio;
+        floatHotspot *= getDevicePixelRatio();
path: src/Mod/TechDraw/Gui/QGVPage.cpp
user: login: captain0xff
body: Oh right. I will replace all occurrences of these with the newly implemented function.
created_at: 2025-04-03T13:40:30Z
author_association: MEMBER
diff_hunk: @@ -590,7 +586,7 @@ QPixmap QGVPage::prepareCursorPixmap(const char* iconName, QPoint& hotspot)
     // therefore we must take care of the transformation ourselves...
     // Refer to QTBUG-68571 - https://bugreports.qt.io/browse/QTBUG-68571
     if (qGuiApp->platformName() == QLatin1String("xcb")) {
-        floatHotspot *= pixelRatio;
+        floatHotspot *= getDevicePixelRatio();
path: src/Mod/TechDraw/Gui/QGVPage.cpp
user: login: captain0xff
body: This was the only place that function was being used so I removed it
created_at: 2025-04-03T14:13:51Z
author_association: MEMBER
diff_hunk: @@ -406,12 +406,7 @@ DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& sugg
                 break;
         }
         if (!iconType.isEmpty()) {
-            qreal pixelRatio = 1;
-            Gui::View3DInventorViewer* viewer = getViewer();
-            if (viewer) {
-                pixelRatio = viewer->devicePixelRatio();
-            }
-            int iconWidth = 16 * pixelRatio;
+            constexpr int iconWidth = 16;
path: src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
user: login: PaddleStroke
body: Why? With 200% scaling the suggested icons are ridiculously small
created_at: 2025-08-15T11:03:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -406,12 +406,7 @@ DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& sugg
                 break;
         }
         if (!iconType.isEmpty()) {
-            qreal pixelRatio = 1;
-            Gui::View3DInventorViewer* viewer = getViewer();
-            if (viewer) {
-                pixelRatio = viewer->devicePixelRatio();
-            }
-            int iconWidth = 16 * pixelRatio;
+            constexpr int iconWidth = 16;
path: src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
user: login: kadet1090
body: `pixmapFromSvg` already contains compensation for DPI and the width is internally multiplied by the proper factor. The only correct way to deal with DPI is to have it done in a way that you don't need to deal with DPI at higher level of abstractions.
created_at: 2025-08-15T11:08:12Z
author_association: MEMBER
diff_hunk: @@ -406,12 +406,7 @@ DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& sugg
                 break;
         }
         if (!iconType.isEmpty()) {
-            qreal pixelRatio = 1;
-            Gui::View3DInventorViewer* viewer = getViewer();
-            if (viewer) {
-                pixelRatio = viewer->devicePixelRatio();
-            }
-            int iconWidth = 16 * pixelRatio;
+            constexpr int iconWidth = 16;
path: src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
user: login: PaddleStroke
body: Yes I understood that later. However it does not work in this case. The suggested constraints are not scaled. So on my win11 200% scaling laptop the icons are very small.
created_at: 2025-08-15T12:12:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -406,12 +406,7 @@ DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& sugg
                 break;
         }
         if (!iconType.isEmpty()) {
-            qreal pixelRatio = 1;
-            Gui::View3DInventorViewer* viewer = getViewer();
-            if (viewer) {
-                pixelRatio = viewer->devicePixelRatio();
-            }
-            int iconWidth = 16 * pixelRatio;
+            constexpr int iconWidth = 16;
path: src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
user: login: kadet1090
body: Should be fixed then somehow, we can restore that to fix the regression now, but we really must avoid having DPI awareness on higher levels of abstraction.
created_at: 2025-08-15T12:36:46Z
author_association: MEMBER
diff_hunk: @@ -677,3 +674,14 @@ QIcon BitmapFactoryInst::mergePixmap (const QIcon &base, const QPixmap &px, Gui:
 
     return overlayedIcon;
 }
+
+qreal BitmapFactoryInst::getMaximumDPR()
+{
+    qreal dpr = 1.0F;
+
+    for (QScreen* screen: QGuiApplication::screens()) {
+        dpr = std::max(screen->devicePixelRatio(), dpr);
+    }
+
+    return dpr;
path: src/Gui/BitmapFactory.cpp
user: login: captain0xff
body: Can you tell me what is being returned from here for you? @PaddleStroke 
created_at: 2025-08-15T16:14:44Z
author_association: MEMBER
diff_hunk: @@ -677,3 +674,14 @@ QIcon BitmapFactoryInst::mergePixmap (const QIcon &base, const QPixmap &px, Gui:
 
     return overlayedIcon;
 }
+
+qreal BitmapFactoryInst::getMaximumDPR()
+{
+    qreal dpr = 1.0F;
+
+    for (QScreen* screen: QGuiApplication::screens()) {
+        dpr = std::max(screen->devicePixelRatio(), dpr);
+    }
+
+    return dpr;
path: src/Gui/BitmapFactory.cpp
user: login: PaddleStroke
body: I will check when I get a computer.
created_at: 2025-08-15T16:38:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -677,3 +674,14 @@ QIcon BitmapFactoryInst::mergePixmap (const QIcon &base, const QPixmap &px, Gui:
 
     return overlayedIcon;
 }
+
+qreal BitmapFactoryInst::getMaximumDPR()
+{
+    qreal dpr = 1.0F;
+
+    for (QScreen* screen: QGuiApplication::screens()) {
+        dpr = std::max(screen->devicePixelRatio(), dpr);
+    }
+
+    return dpr;
path: src/Gui/BitmapFactory.cpp
user: login: captain0xff
body: For non-integer scaling the values seems to be dictated by https://doc.qt.io/qt-6/qguiapplication.html#setHighDpiScaleFactorRoundingPolicy
created_at: 2025-08-15T16:51:17Z
author_association: MEMBER
diff_hunk: @@ -406,12 +406,7 @@ DrawSketchHandler::suggestedConstraintsPixmaps(std::vector<AutoConstraint>& sugg
                 break;
         }
         if (!iconType.isEmpty()) {
-            qreal pixelRatio = 1;
-            Gui::View3DInventorViewer* viewer = getViewer();
-            if (viewer) {
-                pixelRatio = viewer->devicePixelRatio();
-            }
-            int iconWidth = 16 * pixelRatio;
+            constexpr int iconWidth = 16;
path: src/Mod/Sketcher/Gui/DrawSketchHandler.cpp
user: login: PaddleStroke
body: @captain0xff the second regression (the one that is not fixed by your PR) can be fixed just by reverting this change. Perhaps that will give you hint as to why that happens.
created_at: 2025-08-16T05:35:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: captain0xff
created_at: 2025-04-03T14:32:01Z
body: The recent force push just updates some formatting stuff because it appears that the code isn't automatically formatted for this module.
author_association: MEMBER
user: login: furgo16
created_at: 2025-04-04T06:50:04Z
body: It might be worth submitting a separate PR replacing the bitmap arrow icons with vector ones.  
author_association: CONTRIBUTOR
user: login: captain0xff
created_at: 2025-04-04T22:52:03Z
body: Thanks for the reviews :]
author_association: MEMBER
user: login: PaddleStroke
created_at: 2025-08-15T10:58:35Z
body: This PR generates issues : https://github.com/FreeCAD/FreeCAD/issues/21926
author_association: CONTRIBUTOR
user: login: PaddleStroke
created_at: 2025-08-15T10:59:18Z
body: Why did you changed the constraint cursor icon? What were you trying to fix?
author_association: CONTRIBUTOR
user: login: PaddleStroke
created_at: 2025-08-15T11:09:54Z
body: Ok so you have tried to move the logic of DPR awareness into BitmapFactory. But this made 2 regressions : 
- https://github.com/FreeCAD/FreeCAD/issues/21926 the cursor icons for sketcher constraints are cropped. Reverting your changes to CommandConstraints fix the crop issue, but then the curcor icon is scale twice.
- The suggested constraints are not DPR aware anymore, on 200% scale they are very small. Reverting the change in DrawSketchHandler fix the issue.

@captain0xff can you fix those regressions ?
author_association: CONTRIBUTOR
user: login: captain0xff
created_at: 2025-08-15T11:14:53Z
body: I will look into this later today.
author_association: MEMBER
user: login: PaddleStroke
created_at: 2025-08-15T12:35:17Z
body: @captain0xff There might be a third regression : https://github.com/FreeCAD/FreeCAD/issues/23128
Although for this one I am not sure it's related. It's just very similar to 21926
author_association: CONTRIBUTOR
user: login: captain0xff
created_at: 2025-08-15T15:11:20Z
body: Probably most of the high dpi regressions originate from this one. FreeCAD handles high dpi differently in different places so I knew that there might be potential regressions. We discussed this in the discord and I was advised to go through with this and then we can address any potential regressions one by one. It's been some time and no one reported anything so I assumed that we were good.

I even mentioned specifically about sketcher:
<img width="1102" height="125" alt="image" src="https://github.com/user-attachments/assets/338358bc-79e1-40af-b6f9-b10ba05c5264" />

Thank you for tracking them.
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T16:03:10Z
body: This is what I am getting one my machine with 200% scaling:
<img width="1864" height="936" alt="image" src="https://github.com/user-attachments/assets/aacfacab-6fb6-4a19-b149-4f5fda75d02f" />

And that seems to be correct right?
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T16:11:16Z
body: And regarding https://github.com/FreeCAD/FreeCAD/issues/23128
This is what I see in 100% scaling:
<img width="1589" height="944" alt="image" src="https://github.com/user-attachments/assets/777b8c76-57c9-4d20-9b47-44107ef5a587" />

And this is what I see on 200%:
<img width="1332" height="776" alt="image" src="https://github.com/user-attachments/assets/61e1e316-1edf-4116-aa2a-1490a889465e" />

author_association: MEMBER
user: login: PaddleStroke
created_at: 2025-08-15T16:31:49Z
body: Thanks for looking into it.

On your first screenshot I am not sure what to look at.

On your second messages, I see you can replicate the issue of the cropping of the constraint. Your equal constraint is completely gone. To note there was an other PR that made some changes to make sketcher things DPR aware. But that PR combined to your changes made the constrain icons scaled twice. And so they currently appear very big when OS has scale. I opened a PR to fix this problem : https://github.com/FreeCAD/FreeCAD/pull/22940

I think you should know since it's closely related
author_association: CONTRIBUTOR
user: login: PaddleStroke
created_at: 2025-08-15T16:38:23Z
body: So what I can tell is that : 
- in suggested constraints the icons are too small so the DPR is somehow not applied. Maybe the icons are loaded with another function?

- in the cursor icons, the contraint size looks correct. What does not look correct is the relative position between the cursor cross and the constraint icon. Because the cursor icon is build from the cross + constraint icon. And I guess the calculation of pixel between each is hardcoded and doesn't get the DPR adjustment 

- for the multiple constraint on one edge it's the same. In current main the icons are scaled twice (which is fixed by my PR above) and the relative position between them is buggy same as for the cursor.
author_association: CONTRIBUTOR
user: login: captain0xff
created_at: 2025-08-15T16:52:42Z
body: > On your first screenshot I am not sure what to look at.

I was just curious if the size of the tool indicator or whatever is called is correct.
<img width="1280" height="642" alt="image" src="https://github.com/user-attachments/assets/aaf4dd6a-5a4d-4fe4-8d15-851ac3aebde8" />

This is with 200% scaling.
author_association: MEMBER
user: login: PaddleStroke
created_at: 2025-08-15T16:58:07Z
body: This is what I call the tool cursor.

The geometry tools cursors are OK
The one that are problematic are the constraint tools cursors. Try dimension constraint for example.
author_association: CONTRIBUTOR
user: login: captain0xff
created_at: 2025-08-15T17:11:07Z
body: <img width="628" height="508" alt="image" src="https://github.com/user-attachments/assets/11ba9e15-ddd9-4c53-93c6-8e0196829bf6" />

<img width="1012" height="323" alt="image" src="https://github.com/user-attachments/assets/caefbd99-f99f-42f2-8f90-f0ca1a7cf4fb" />

Setting the devicePixelRatio to 1.0 instead of the correct dpr value here fixes the issue. So, something is probably scaling the pixmap twice. Can you point me to the code responsible for drawing them? The issue surely lies there.
author_association: MEMBER
user: login: PaddleStroke
created_at: 2025-08-15T17:28:30Z
body: It's in commandconstraints.cpp, it's the code that you modified in this PR.

The activated functions of the 2 drawsketchhandler of this file.
author_association: CONTRIBUTOR
user: login: captain0xff
created_at: 2025-08-15T17:39:48Z
body: Ok, so I narrowed it down to this: https://github.com/FreeCAD/FreeCAD/blob/af83b6883eb0afae7d82aaaefc6a3991429c0a70/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp#L2758
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T17:41:13Z
body: If you set the icon's dpr with setDevicePixelRatio to 1.0 before this line and then restore it later then it behaves as expected. So, basically the call to toImage crops the icon.
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T17:44:06Z
body: I am wondering if it makes sense to set the icon dpr to 1.0 here:
<img width="784" height="207" alt="image" src="https://github.com/user-attachments/assets/df1cedd9-e194-4a05-a737-8bd65bca3284" />

Because we have already scaled it correctly while loading. 
I am checking if it fixes the other regressions.
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T17:52:26Z
body: And yes it also fixes https://github.com/FreeCAD/FreeCAD/issues/21926
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T17:53:08Z
body: But it might create new regressions so I actually need to test this properly.
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T17:54:45Z
body: @PaddleStroke can you kindly confirm if setting the dpr to 1.0 in https://github.com/FreeCAD/FreeCAD/blob/af83b6883eb0afae7d82aaaefc6a3991429c0a70/src/Gui/BitmapFactory.cpp#L315 solves all the regressions that you mentioned?
author_association: MEMBER
user: login: kadet1090
created_at: 2025-08-15T18:18:56Z
body: > @PaddleStroke can you kindly confirm if setting the dpr to 1.0 in https://github.com/FreeCAD/FreeCAD/blob/af83b6883eb0afae7d82aaaefc6a3991429c0a70/src/Gui/BitmapFactory.cpp#L315 solves all the regressions that you mentioned?

Such change would need to be very thoroughly tested. Lack of that line broken painting over icons as the painter was not aware about scaling. It can trade one regressions for others. If we neee to disable it for things in sketcher it should be done on case by case basis.
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T18:35:30Z
body: Yep, I saw that. The reason is that the merge function utilizes the dpr from the pixmaps. Firstly why does it do so? Why will be the dpr between two pixmaps be different?
author_association: MEMBER
user: login: captain0xff
created_at: 2025-08-15T19:18:45Z
body: I created https://github.com/FreeCAD/FreeCAD/pull/23132 which should fix the first and the third regressions. I am still not sure about how to reproduce the second one. @PaddleStroke can you share a screenshot of the regression?
author_association: MEMBER
user: login: PaddleStroke
created_at: 2025-08-16T04:51:57Z
body: When you use a geometry tool, the suggested auto constraints are shown near
the mouse cursor. For instance start slot tool, start a slot on origin then
move the mouse along X axis. You will have a point-on-object and horizontal
auto constraints suggested. Their icons will appear on your mouse cursors.
Those icons are too small.

I will post a picture tomorrow

On Fri, Aug 15, 2025, 20:19 Kacper Donat ***@***.***> wrote:

> *kadet1090* left a comment (FreeCAD/FreeCAD#20594)
> <https://github.com/FreeCAD/FreeCAD/pull/20594#issuecomment-3192364115>
>
> @PaddleStroke <https://github.com/PaddleStroke> can you kindly confirm if
> setting the dpr to 1.0 in
> https://github.com/FreeCAD/FreeCAD/blob/af83b6883eb0afae7d82aaaefc6a3991429c0a70/src/Gui/BitmapFactory.cpp#L315
> solves all the regressions that you mentioned?
>
> Such change would need to be very thoroughly tested. Lack of that line
> broken painting over icons as the painter was not aware about scaling. It
> can trade one regressions for others. If we neee to disable it for things
> in sketcher it should be done on case by case basis.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/FreeCAD/FreeCAD/pull/20594#issuecomment-3192364115>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AEYO6MJLRG354YOUBTXBBBL3NYQCLAVCNFSM6AAAAAB2J45EZGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTCOJSGM3DIMJRGU>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

author_association: CONTRIBUTOR
user: login: PaddleStroke
created_at: 2025-08-16T05:22:27Z
body: <img width="800" height="692" alt="image" src="https://github.com/user-attachments/assets/bf57d33d-aac8-4c0a-9bfc-6afad3b609d0" />

Here is a picture
author_association: CONTRIBUTOR