Painting shadow around a parentless QWidget



  • I'm trying to have a nice shadow effect around my QWidget by drawing that inside widget's coordinate
    Could you please put me on the right way to do that ?
    like this one:
    enter image description here


  • Lifetime Qt Champion

    Hi,

    Is QGraphicsDropShadowEffect what you are looking for ?



  • @SGaist
    Hi.
    No actually all QGraphicsEffect 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 have 5dp shadow surrounding the widget like this:

    enter image description here

    So what should be used inside paintEvent ?



  • @SGaist
    There are two possibility in my mind :
    1.Render shadow on QPixmap (one pixmap per each edge.r.g right, top, bottomleft, ...) and then draw these cashed pixmaps on desired region of widget
    2.Use QLinearGradient to draw (corners are the problem)
    There are no workaround out there! that's wierd


  • Lifetime Qt Champion

    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);
    	}
    };
    


  • @IMAN4K

    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 exactly drawShadow(..) 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:
    enter image description here


  • Moderators



  • @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 widget paintEvent()) :
    static void paintShadow(QPainter *, qreal radius, qreal distance,.....)
    And i couldn't find the source code for sourcepixmap()
    We just need to produce this kind of blur image (if we remove source painting):
    enter image description here

    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 parentless QWidget and normal Widget with parent.for parentless Widget use it inside paintEvent():
    enter image description here

    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):

    enter image description here


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.