Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iconloader scaling enhancemets #275

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft

Conversation

palinek
Copy link
Contributor

@palinek palinek commented Mar 3, 2022

While debugging the lxqt/libfm-qt#788 I dived "deeply" into some aspects of the iconloader logic and here are probable enhancements.

Use map to not traverse and evaluate the existing entries each time when
info/icon is needed.
@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

@tsujan Can you, please, have a look into this commits?

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

I assure you that the problem isn't in libqtxdg. LXQt's icon engine works self-consistently and without problem with scale factors; any change could damage that.

Anyway, this patch causes a crash with a scale factor:

#0  0x00007f9f8bef267a in QIconEngine::isNull() const () from /usr/lib/libQt5Gui.so.5
#1  0x00007f9f8c6e7586 in QToolButton::initStyleOption(QStyleOptionToolButton*) const ()
   from /usr/lib/libQt5Widgets.so.5
#2  0x00007f9f8c6ea3d0 in QToolButton::sizeHint() const () from /usr/lib/libQt5Widgets.so.5
#3  0x00007f9f8c52705b in qSmartMaxSize(QWidgetItem const*, QFlags<Qt::AlignmentFlag>) ()
   from /usr/lib/libQt5Widgets.so.5
#4  0x00007f9f8c52751e in QWidgetItem::maximumSize() const () from /usr/lib/libQt5Widgets.so.5
#5  0x00007f9f8c6df3cb in ?? () from /usr/lib/libQt5Widgets.so.5
#6  0x00007f9f8c6e201d in ?? () from /usr/lib/libQt5Widgets.so.5
#7  0x00007f9f8c5221ce in QLayout::totalMinimumSize() const () from /usr/lib/libQt5Widgets.so.5
#8  0x00007f9f8c526e45 in qSmartMinSize(QWidgetItem const*) () from /usr/lib/libQt5Widgets.so.5
#9  0x00007f9f8c6dd3b0 in ?? () from /usr/lib/libQt5Widgets.so.5
#10 0x00007f9f8c6dd548 in ?? () from /usr/lib/libQt5Widgets.so.5
#11 0x00007f9f8c655f94 in ?? () from /usr/lib/libQt5Widgets.so.5
#12 0x00007f9f8c656071 in ?? () from /usr/lib/libQt5Widgets.so.5
#13 0x00007f9f8c5221ce in QLayout::totalMinimumSize() const () from /usr/lib/libQt5Widgets.so.5
#14 0x00007f9f8c5231c8 in QLayout::activate() () from /usr/lib/libQt5Widgets.so.5
#15 0x00007f9f8c53e7aa in QWidgetPrivate::setVisible(bool) () from /usr/lib/libQt5Widgets.so.5
...

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

BTW, what you see (with your eyes) partly depends on the widget style too (→ second paragraph of lxqt/libfm-qt#788 (comment)); don't trust your eyes too much ;)

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

Anyway, this patch causes a crash with a scale factor:

Did you rebuild the lxqt-qtplugin also?

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

Oh, you're right; I forgot that. But, still, I think it's important to stick with how Qt does it, in order to guarantee that our code is as standard as possible.

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

I think it's important to stick with how Qt does it

If we don't fix spotted bugs/problems, then there is no point for us to have our own implementation.

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

If we don't fix spotted bugs/problems, then there is no point for us to have our own implementation.

Yes, but I'm not sure to which bug you refer. lxqt/libfm-qt#792 isn't a bug in libqtxdg and the situation can become very confusing because of problems that are really elsewhere (like in the Breeze icon set: lxqt/libfm-qt#792 (comment)).

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

I'm not sure to which bug you refer

There are 3 commits here:

  • iconloader: Build/use map for finding entries
    ** [current] the resolved entries for one particular XdgIconLoaderEngine object are crawled and tried to find the best match every time some information is requested (available size/paint/pixmap/...)
    ** [new] by adding a map the best suited entry for particular size&scale is searched only the first time; each next time a "cached pointer" is used (which I believe is more performant)
  • iconloader: Handle non-integer scaling more precise
    ** [current] Qt (or any caller) calls the QIconEngine API requesting the <scaledSize> (<size> * <floating scale>); In XdgIconLoaderEngine for getting the most valid entry it uses <requestedSize> (<scaledSize> / qCeil(<floating scale>)), which is obviously not correct as the original <size> != <requestedSize> for non-integer scales
    ** [new] consider the <floating scale> as is for gathering the <requestedSize>, but do round the <floating scale> only for evaluation of the scaled (*@Sx) entries (also prefer the entries where scale == qCeil(<floating scale>))
  • iconloader: Consider scale in actualSize() & pixmap()
    ** [current] in actualSize() & pixmap() for searching the most suitable entry the scale is not considered at all (i.e. it is (wrongly) assumed to be 1.0)
    ** [new] use application wide scale (the same as it is done in Qt methods, when the window is not known)

Don't you think, it is worth addressing?

EDIT: forget the "iconloader: Handle non-integer scaling more precise" commit, I was reading the logic wrong and I've substituted it with more suitable one

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

Don't you think, it is worth addressing?

I'll need some time to read them.

Just one thing caught my eyes by a glance:

const qreal scale = qApp->devicePixelRatio();// Don't know which window to target

As far as I remember, we didn't have that elsewhere and I didn't find it in Qt's icon loader either. The problem is that a window of an app can be inside another screen with scaling. Therefore, qApp->devicePixelRatio() should be avoided, especially here.

My general point is that such details could easily get out of hand after a while if we don't stick with Qt's way of doing things as far as possible. Moreover, if our code gets too much different from that of Qt, we'll have trouble in importing Qt's fixes (like #225).

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

My general point is that such details could easily get out of hand after a while if we don't stick with Qt's way of doing things as far as possible.

I believe all these commits can be ported to Qt straight away. But this probably won't be backported to Qt v5.15 (I don't know if Qt would accept this binary incompatible change).

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

It is used in Qt for similar purposes also:

It's the last resort and only where really needed. The pixel ratio of window, pixmap or paint device has priority over it.

Just one possible scenario among who knows how many?

I have an app with a devicePixelRatio of 2 on an HDPI screen. One of its windows opens in an ordinary screen. QIcon::pixmap(QWindow *window,...) calls qt_effective_device_pixel_ratio(), sets devicePixelRatio to 1, and then calls d->engine->pixmap(). With your change, it may get a wrong pixmap. It's only in QIcon::pixmap(QWindow *window,...) that we can know about a window; we shouldn't deal with the pixel ratio prematurely in XdgIconLoaderEngine::pixmap().

palinek added 2 commits March 3, 2022 19:05
..and avoid unneded size multiplication/division.
These APIs don't provide the scaling information, we will guess it by
using the application's one (QIcon does it similarly in such cases).
@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

One of its windows opens in an ordinary screen. QIcon::pixmap(QWindow *window,...) calls qt_effective_device_pixel_ratio(), sets devicePixelRatio to 1, and then calls d->engine->pixmap(). With your change, it may get a wrong pixmap.

...and we can say the same for the former "HDPI windows" -> without the change the windows on all HDPI screens may get wrong pixmaps (because we assume the scale == 1).
There is no silver bullet for this, but I would say, that using the application wide scale will cover more situations correctly than assuming scale == 1.

@palinek palinek force-pushed the iconloader-enhancemets branch from 2e9bff0 to 5821616 Compare March 3, 2022 18:21
@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

...and we can say the same for the former "HDPI windows" -> without the change the windows on all HDPI screens may get wrong pixmaps (because we assume the scale == 1).

No. It's correctly handled by QIcon::pixmap(QWindow *window,...).

In old times, we relied on qApp's pixel ratio. Now things are different: qApp's pixel ratio shouldn't take priority and, especially, it shouldn't be considered prematurely.

One of our (or my) users warned me about this (don't remember where). My first reaction was like yours but then I saw its logic; made changes here (following Qt) as well as in Kvantum.

There is no silver bullet for this

Following Qt is the safest way.

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

...and we can say the same for the former "HDPI windows" -> without the change the windows on all HDPI screens may get wrong pixmaps (because we assume the scale == 1).

...and we can say the same for the former "HDPI windows" -> without the change the windows on all HDPI screens may get wrong pixmaps (because we assume the scale == 1).

No. It's correctly handled by QIcon::pixmap(QWindow *window,...).

Then this exact answer is for you initial issue. We're not using/guessing the scale in PixmapEntry/ScalableEntry... but in the XdgIconLoaderEngine. Here I don't consider the usage pre-mature.

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

Qt6 has gone further and has removed the scaling from QIconEngine::virtual_hook() too. Now the caller is responsible for providing QIcon::pixmap(const QSize &size, qreal devicePixelRatio, Mode mode, State state) with the correct dpr.

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

Qt6 has gone further and has removed the scaling from QIconEngine::virtual_hook() too.

It dropped the whole virtual_hook concept, but this doesn't make any difference to our discussed topic here.

@tsujan
Copy link
Member

tsujan commented Mar 3, 2022

QIconLoaderEngine::virtual_hook() has lost its role, QIconEngine::virtual_hook() might be removed at some point, and QIcon::pixmap(QWindow *window,...) has been deprecated. That's a big step further away from consulting qApp's dpr at the level of QIconLoaderEngine.

I've kept scaling of virtual_hook() in https://github.com/lxqt/libqtxdg/tree/wip_qt6 though, as it seemed harmless.

@palinek
Copy link
Contributor Author

palinek commented Mar 3, 2022

and QIcon::pixmap(QWindow *window,...) has been deprecated. That's a big step further away from consulting qApp's dpr at the level of QIconLoaderEngine.

There are still the QIcon::pixmap(size...) overloads, which just uses the QIcon::pixmap(size, -1, ...) and this handles the scaling:

  QPixmap QIcon::pixmap(const QSize &size, qreal devicePixelRatio, Mode mode, State state) const
  {
      if (!d)
          return QPixmap();
  
      // Use the global devicePixelRatio if the caller does not know the target dpr
      if (devicePixelRatio == -1)
          devicePixelRatio = qApp->devicePixelRatio();
  
      // Handle the simple normal-dpi case
      if (!(devicePixelRatio > 1.0)) {
          QPixmap pixmap = d->engine->pixmap(size, mode, state);
          pixmap.setDevicePixelRatio(1.0);
          return pixmap;
      }
  
      // Try get a pixmap that is big enough to be displayed at device pixel resolution.
      QPixmap pixmap = d->engine->scaledPixmap(size * devicePixelRatio, mode, state, devicePixelRatio);
      pixmap.setDevicePixelRatio(d->pixmapDevicePixelRatio(devicePixelRatio, size, pixmap.size()));
      return pixmap;
  }

But reading the code of Qt (5.15 & 6.dev) and how the QIcon calls engine->pixmap()... yes you're right the QIconEngine::pixmap(..) should no use scaling.

But the QIconEngine::actualSize(..) is not aware of the scaling at all. So I still think, we should check tha app wide scale there.

@tsujan
Copy link
Member

tsujan commented Mar 4, 2022

But the QIconEngine::actualSize(..) is not aware of the scaling at all.

In Qt5, because of the way QIcon::actualSize(QWindow *window,...) deals with it, QIconEngine::actualSize() shouldn't depend on qApp's dpr.

In Qt6, QIcon::actualSize(QWindow *window,...) is deprecated. Instead, QIcon::actualSize(size) takes qApp's dpr into account (and not that of window — there should be a good reason for it) and, again, because of that, QIconEngine::actualSize() shouldn't depend on qApp's dpr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants