Reputation: 11
When I was using Qt, I applied a blur effect to a QWidget that is a window. The code looks like this:
#include <QGraphicsBlurEffect>
#include <QApplication>
#include <QWidget>
int main(int argc, char** argv) {
QApplication app(argc,argv);
QWidget window;
auto blur = new QGraphicsBlurEffect;
window.setGraphicsEffect(blur);
window.show();
return app.exec();
}
However, the result was that the window's background turned completely black:
I have gone through the official documentation, but I still don't understand why this happened. The official documentation doesn't mention that it can't be used on such a QWidget.
I thought it might be a platform issue, so I cross-compiled the program and had my friend run it. However, this did not change the result.
Note: My system is Ubuntu ARM64 with X11, while my friend's is Windows x86.
I've tried using the shadow effect instead of the blur, and the result was that only the blur effect was abnormal.
The issue remains the same even when I tried using the latest version 6.8.2. My current Qt version is 6.5.4.
Upvotes: 1
Views: 82
Reputation: 48434
Just add the following line before showing:
window::setAutoFillBackground(true);
This is technically a bug, caused by a simplification of the QWidget painting system probably done to let the developer assume that the painting behavior of a top level widget is identical to that of a child one.
Your doubts about the QWidget specificity are also mislead: all widgets in Qt derive from QWidget, so there is actually no difference in applying a graphics effect to any other widget type, as they all are QWidgets.
The difference, in reality, is in the fact that your window
is a top level widget: a window.
When a QWidget is being drawn, its paintEvent()
creates as QPainter object that, most of the times, draws over an existing painting surface, which normally is the parent, and that parent can also be the window.
The concept is similar to physical painting, where you always draw above something: a canvas, or anything else that was previously drawn on it. Similarly, child painting works by drawing above the background.
By default, widgets are always transparent and eventually draw on the background their contents.
The problem comes when drawing a widget that is also a top level one: a window.
In that case, Qt needs to draw a background, and eventually tell that widget to draw on that. For top level widgets, it therefore queries the widget style and palette and draws that background on its own, then calls the widget's paintEvent()
.
Also remember that Qt painting is a relatively high abstraction API: the generic QPainter class allows us to virtually draw on anything, even though we're actually drawing onto very different surface types: functions like QPainter::drawRect()
make our lives easy, but what those functions do is often very different depending on "what" we're drawing onto.
This is why using a Qt Style Sheet that sets basic painting rules for widgets only works for top level windows but not for child widgets (which would require setting the Qt::WA_StyledBackground
attribute for the instance, or overriding the paintEvent()
of the class as the docs suggest).
The same widget will therefore be painted (possibly) differently whether it's a top level one or a child of another, and the autoFillBackground
property is actually important on that matter, as it may be implied for top level widgets.
Similarly to the physical drawing explained above, the "palette" is exactly as a physical palette; it is just a start reference, what and how it's used is up to the widget or its usage of QStyle, exactly as an actual painter would do: the fact that they have a palette made of black and white doesn't prevent them to blend those color, possibly never using neither white or black at all.
By default, the "background" of a widget is never drawn: just like other palette colors, it's just a "color rule". The behavior is similar to that of QWidget subclasses when using QSS.
Each widget (or style) may use that color for specific parts of the widget, or even completely ignore it.
That said, if the widget is a top level, Qt then must set a background no matter what.
The first step is to clear the window background (actually, the paint buffer) to a completely black color, or, to be precise, to a "black transparent" color. The assumption is that the the final background of the window will eventually completely draw upon that color (if the window is opaque) or eventually composite-draw on a partially transparent window.
Then, Qt will assume the widget's background as the window background, even though it's not expected to draw its background. If the window is not actually transparent, that "black transparent" will just become a full opaque black.
Some Qt graphics effects actually involve an alpha channel that blends the contents of the target with the background. This is specifically the case of QGraphicsBlurEffect, which must be able to blend its blurred contents with the background. This is achieved by capturing the widget contents, using the render()
function of QWidget, onto a theoretically transparent QPixmap.
Now, since the effect draws on a previously set background (the "full opaque black" above assumed for the top level widget) the resulting effect blends the blurred contents using that color as reference, thus resulting in a completely black background, because the widget does not auto fill its own background in the render()
function.
To clarify the above, you can see a quite similar effect by setting the Window
role of the QApplication palette()
with a color having an alpha value less than 255 and showing a simple QWidget:
...
QPalette pal = QApplication::palette();
pal.setColor(QPalette::Window, QColor(255, 0, 0, 127));
QApplication::setPalette(pal)
QWidget window;
window.show();
...
Note: I'm not a C++ developer, so the above syntax is probably wrong; still, the code logic should be clear enough.
The above will show a window with a dark red background, because the 127
(half) transparency of the full red is blended on top of the black background originally cleared for the opaque window. On some styles it may even result in a gradient.
This means that the blur graphics effect will always try to blend the output of the widget's paintEvent()
(and/or its background palette, also considering the style) with the black background.
Considering all the above, as long as the color for the background role of the widget is opaque, using setAutoFillBackground(true);
should be enough to ensure that the top level widget will have an appropriate color, and the blur effect will eventually work with that.
Note that using QSS that affect the top level widget may also have unexpected results on graphics effects. In that case, especially if the QSS is applied on widgets and not on the whole QApplication (or the parents), it may be necessary to explicitly call the effect's update()
call whenever repainting is required, which should be whenever any of the following events happen either on the target of the effect or its children (possibly using an event filter in that case):
QEvent::Resize
QEvent::StyleChange
QEvent::PaletteChange
QEvent::ParentChange
Finally, although "technically a bug", all the above shows that it's also partially by design, and working around it may be more (unnecessarily) difficult than required.
Due to the complexity of widget drawing, considering the cross-platform nature of Qt and everything involved with composite managers and different display aspects on *nix platforms, it may be better to report this as a suggestion for the documentation instead of an "actual" bug.
Upvotes: 0