Painting shadow around a parentless QWidget
-
Hi,
Is QGraphicsDropShadowEffect what you are looking for ?
@SGaist
Hi.
No actually allQGraphicsEffect
require a parent or source to paint the effects! (it's useless in case of menus)
and what is needed is to paint the shadow inside the widget's area (faking the shadow).
Let's say we want to have5dp
shadow surrounding the widget like this:So what should be used inside
paintEvent
? -
@SGaist
There are two possibility in my mind :
1.Render shadow onQPixmap
(one pixmap per each edge.r.g right, top, bottomleft, ...) and then draw these cashed pixmaps on desired region of widget
2.UseQLinearGradient
to draw (corners are the problem)
There are no workaround out there! that's wierd -
Something's not completely clear. Can you show how you setup your widgets ?
-
@SGaist
There is no Widgets.
It's just one simple transparent widget that we want it to have shadow (there is only one widget in that image i have mentioned):#define WIDTH 5 class ShadowWidget : public QWidget { Q_OBJECT public: ShadowWidget(QWidget *parent = 0) :QWidget(parent) { setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground, true); } protected: void paintEvent(QPaintEvent *) override { QPainter p(this); /* draw the shadow here */ auto r = rect().marginsRemoved(QMargins(WIDTH, WIDTH, WIDTH, WIDTH)); p.setClipRect(r); p.setBrush(palette().background()); p.setPen(Qt::NoPen); p.drawRect(r); } };
-
@SGaist
There is no Widgets.
It's just one simple transparent widget that we want it to have shadow (there is only one widget in that image i have mentioned):#define WIDTH 5 class ShadowWidget : public QWidget { Q_OBJECT public: ShadowWidget(QWidget *parent = 0) :QWidget(parent) { setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground, true); } protected: void paintEvent(QPaintEvent *) override { QPainter p(this); /* draw the shadow here */ auto r = rect().marginsRemoved(QMargins(WIDTH, WIDTH, WIDTH, WIDTH)); p.setClipRect(r); p.setBrush(palette().background()); p.setPen(Qt::NoPen); p.drawRect(r); } };
hi, there's still a Widget that can a have a QGraphicsDropShadowEffect, doesn't it? In your case you can access it with
this
also I hade some similar issues in this thread
maybe it can help you. -
hi, there's still a Widget that can a have a QGraphicsDropShadowEffect, doesn't it? In your case you can access it with
this
also I hade some similar issues in this thread
maybe it can help you.@J.Hilk
Hi.hi, there's still a Widget that can a have a QGraphicsDropShadowEffect, doesn't it?
You mean we put a widget inside another (transparent) one and set
QGraphicsEffect
on first one ?
(If yes then we have overhead of two widget)
And about that thread, how exactlydrawShadow(..)
draw a shadow?
it didn't work -
There is a code snippet that works for a custom QGraphicsEffect (
QGraphicsEffect::draw(QPainter * painter)
) :if ((blurRadius() + distance()) <= 0) { drawSource(painter); return; } PixmapPadMode _mode = QGraphicsEffect::PadToEffectiveBoundingRect; QPoint _offset; QPixmap _pixmap = sourcePixmap(Qt::DeviceCoordinates, &_offset, _mode); if (_pixmap.isNull()) return; QTransform _transform = painter->worldTransform(); painter->setWorldTransform(QTransform()); QSize _backgroundSize = QSize(_pixmap.size().width() + 2 * distance(), _pixmap.size().height() + 2 * distance()); QImage _temp(_backgroundSize, QImage::Format_ARGB32_Premultiplied); QPixmap scaled = _pixmap.scaled(_backgroundSize); _temp.fill(0); QPainter _tempPainter(&_temp); _tempPainter.setCompositionMode(QPainter::CompositionMode_Source); _tempPainter.drawPixmap(QPointF(-distance(), -distance()), scaled); _tempPainter.end(); QImage blurred(_temp.size(), QImage::Format_ARGB32_Premultiplied); blurred.fill(0); QPainter blurPainter(&blurred); qt_blurImage(&blurPainter, _temp, blurRadius(), false, true); blurPainter.end(); _temp = blurred; _tempPainter.begin(&_temp); _tempPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); _tempPainter.fillRect(_temp.rect(), color()); _tempPainter.end(); painter->drawImage(_offset, _temp); painter->drawPixmap(_offset, _pixmap, QRectF()); painter->setWorldTransform(_transform);
But i can not use it for simply drawing inside a widget. don't know how to produce
sourcePixmap(..)
If you can understand it correctly please help me to figure this out.
It produce this nice result:
-
There is a code snippet that works for a custom QGraphicsEffect (
QGraphicsEffect::draw(QPainter * painter)
) :if ((blurRadius() + distance()) <= 0) { drawSource(painter); return; } PixmapPadMode _mode = QGraphicsEffect::PadToEffectiveBoundingRect; QPoint _offset; QPixmap _pixmap = sourcePixmap(Qt::DeviceCoordinates, &_offset, _mode); if (_pixmap.isNull()) return; QTransform _transform = painter->worldTransform(); painter->setWorldTransform(QTransform()); QSize _backgroundSize = QSize(_pixmap.size().width() + 2 * distance(), _pixmap.size().height() + 2 * distance()); QImage _temp(_backgroundSize, QImage::Format_ARGB32_Premultiplied); QPixmap scaled = _pixmap.scaled(_backgroundSize); _temp.fill(0); QPainter _tempPainter(&_temp); _tempPainter.setCompositionMode(QPainter::CompositionMode_Source); _tempPainter.drawPixmap(QPointF(-distance(), -distance()), scaled); _tempPainter.end(); QImage blurred(_temp.size(), QImage::Format_ARGB32_Premultiplied); blurred.fill(0); QPainter blurPainter(&blurred); qt_blurImage(&blurPainter, _temp, blurRadius(), false, true); blurPainter.end(); _temp = blurred; _tempPainter.begin(&_temp); _tempPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); _tempPainter.fillRect(_temp.rect(), color()); _tempPainter.end(); painter->drawImage(_offset, _temp); painter->drawPixmap(_offset, _pixmap, QRectF()); painter->setWorldTransform(_transform);
But i can not use it for simply drawing inside a widget. don't know how to produce
sourcePixmap(..)
If you can understand it correctly please help me to figure this out.
It produce this nice result:
-
@jsulm
Definitely i read that before :)
But the doc says :Calling this function with Qt::DeviceCoordinates outside of QGraphicsEffect::draw() will give undefined results, as there is no device context available.
Another thing is
sourcePixmap(..)
is a protected function and work if our widget has a parent widget while i need a static function like this (that operate on widgetpaintEvent()
) :
static void paintShadow(QPainter *, qreal radius, qreal distance,.....)
And i couldn't find the source code forsourcepixmap()
We just need to produce this kind of blur image (if we remove source painting):
I tried to get the pixmap via this function :
static QPixmap grab(QWidget *target, const QRect &rect) { auto result = QPixmap(rect.size()); result.fill(Qt::transparent); { QPainter p; p.begin(&result); target->render(&p, QPoint(rect.left(), rect.top()), rect, QWidget::DrawChildren | QWidget::IgnoreMask); p.end(); } return result; }
But if i put the result in place of
_pixmap
it doesn't draw the blur image -
Finally figured it out.
This will work for both parentlessQWidget
and normal Widget with parent.for parentless Widget use it insidepaintEvent()
:
shadow.h
:#pragma once #include <QtWidgets/qgraphicseffect.h> #include <QtGui/qpainter.h> #include <QtWidgets/qwidget.h> class Shadow : public QGraphicsEffect { public: enum Side { Left = 0x1, Right = 0x2, Bottom = 0x4, Top = 0x8, Around = Left | Top | Right | Bottom, }; Q_DECLARE_FLAGS(Sides, Side); Shadow(QObject *parent = 0); Shadow(const QColor &c, qreal distance, qreal radius, Sides sides = Side::Around, QObject *parent = 0); Sides sides() const { return _side; } void setSides(Sides s) { _side = s; updateBoundingRect(); } QColor color() const { return _color; } void setColor(const QColor &c) { _color = c; updateBoundingRect(); } qreal blurRadius() const { return _blurRadius; } void setBlurRadius(qreal br) { _blurRadius = br; updateBoundingRect(); } qreal distance() const { return _distance; } void setDistance(qreal d) { _distance = d; updateBoundingRect(); } QRectF boundingRectFor(const QRectF &) const override; static QPixmap grab(QWidget *target, const QRect &rect, int offset); // Return a pixmap with target painted into it with margin = offset // Return a background blurred QImage to Draw as the widget's shadow static QImage paint(QWidget *target, const QRect &box, qreal radius, qreal distance, const QColor &c, Sides sides = Side::Around); protected: void draw(QPainter *painter) override; private: Sides _side; QColor _color; qreal _distance; qreal _blurRadius; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Shadow::Sides)
shadow.cpp
:#include "shadow.h" Q_DECL_IMPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); // src/widgets/effects/qpixmapfilter.cpp Shadow::Shadow(QObject *parent) :QGraphicsEffect(parent) { } Shadow::Shadow(const QColor &c, qreal distance, qreal radius, Sides s, QObject *parent) : QGraphicsEffect(parent) { setColor(c); setBlurRadius(radius); setDistance(distance); setSides(s); } QRectF Shadow::boundingRectFor(const QRectF &r) const { qreal _delta = blurRadius() + distance(); return r.marginsAdded(QMarginsF( (sides() & Side::Left) ? _delta : 0, (sides() & Side::Top) ? _delta : 0, (sides() & Side::Right) ? _delta : 0, (sides() & Side::Bottom) ? _delta : 0 )); } void Shadow::draw(QPainter *painter) { if ((blurRadius() + distance()) <= 0) { drawSource(painter); return; } QPoint _offset; QPixmap _pixmap = sourcePixmap(Qt::DeviceCoordinates, &_offset, QGraphicsEffect::PadToEffectiveBoundingRect); if (_pixmap.isNull()) return; QTransform _transform = painter->worldTransform(); painter->setWorldTransform(QTransform()); QSize _backgroundSize = QSize(_pixmap.size().width() + 2 * distance(), _pixmap.size().height() + 2 * distance()); QImage _temp(_backgroundSize, QImage::Format_ARGB32_Premultiplied); QPixmap scaled = _pixmap.scaled(_backgroundSize); _temp.fill(0); QPainter _tempPainter(&_temp); _tempPainter.setCompositionMode(QPainter::CompositionMode_Source); _tempPainter.drawPixmap(QPointF(-distance(), -distance()), scaled); _tempPainter.end(); QImage blurred(_temp.size(), QImage::Format_ARGB32_Premultiplied); blurred.fill(0); QPainter blurPainter(&blurred); qt_blurImage(&blurPainter, _temp, blurRadius(), true, false); blurPainter.end(); _temp = blurred; _tempPainter.begin(&_temp); _tempPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); _tempPainter.fillRect(_temp.rect(), color()); _tempPainter.end(); painter->drawImage(_offset, _temp); painter->drawPixmap(_offset, _pixmap, QRectF()); painter->setWorldTransform(_transform); } QPixmap Shadow::grab(QWidget *target, const QRect &rect, int offset) { auto result = QPixmap(rect.size()); auto r = rect.marginsRemoved(QMargins(offset, offset, offset, offset)); result.fill(Qt::transparent); { QPainter p; p.begin(&result); target->render(&p, QPoint(offset, offset), r); p.end(); } return result; } QImage Shadow::paint(QWidget *target, const QRect &box, qreal radius, qreal distance, const QColor &c, Sides sides) { const auto _source = grab(target, box, distance); if (_source.isNull() || distance <= 0) return QImage(); QImage _backgroundImage(box.size(), QImage::Format_ARGB32_Premultiplied); _backgroundImage.fill(0); QPainter _backgroundPainter(&_backgroundImage); _backgroundPainter.drawPixmap(QPointF(), _source); _backgroundPainter.end(); QImage blurredImage(_backgroundImage.size(), QImage::Format_ARGB32_Premultiplied); blurredImage.fill(0); { QPainter blurPainter(&blurredImage); qt_blurImage(&blurPainter, _backgroundImage, radius, true, false); blurPainter.end(); } _backgroundImage = blurredImage; _backgroundPainter.begin(&_backgroundImage); _backgroundPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); auto margin = _backgroundImage.rect().marginsRemoved(QMargins( (sides & Left) ? 0 : distance, (sides & Top) ? 0 : distance, (sides & Right) ? 0 : distance, (sides & Bottom) ? 0 : distance )); _backgroundPainter.fillRect(margin, c); _backgroundPainter.end(); return _backgroundImage; }
main.cpp
:#include <QtWidgets/qapplication.h> #include "shadow.h" class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0) :QWidget(parent) { setShadowWidth(7); setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_NoSystemBackground, true); setStyleSheet("background:#FFFFFF;"); setAutoFillBackground(false); } void setShadowWidth(int w) { _shWidth = w; //setContentsMargins(w, w, w, w); } int shadowWidth() const { return _shWidth; } void ensureLoaded() { if (_cashe.isNull()) { _cashe = QPixmap::fromImage(Shadow::paint(this, rect(), 18.0, shadowWidth(), QColor(0, 0, 0, 128))); } } protected: void paintEvent(QPaintEvent*e) override { QPainter painter(this); if (!_cashe.isNull()) painter.drawPixmap(QPoint(), _cashe); painter.setBrush(palette().background()); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, true); auto m = QMargins(shadowWidth(), shadowWidth(), shadowWidth(), shadowWidth()); painter.drawRoundedRect(rect().marginsRemoved(m), 2.0, 2.0); painter.setRenderHint(QPainter::Antialiasing, false); } void resizeEvent(QResizeEvent *e) { ensureLoaded(); _cashe = _cashe.scaled(e->size()); QWidget::resizeEvent(e); } private: QPixmap _cashe = QPixmap(); int _shWidth = 0; }; int main(int argc, char *argv[]) { QApplication app(argc, argv); Widget *widget = new Widget; widget->resize(200, 200); widget->show(); return app.exec(); } #include "main.moc"
Result for borderless QWidget (without parent):