Storing the results of QPainter rendering into a QPixmap instance - qt

I want to apply blur effect to a gradient generated by GRadialGradient and rendered by QPainter.Looks like for such graphical effects I have to provide a pixmap and a QGraphicsScene and then call the ->render() method but I couldn't find any ways to add a QPainter directly into any subclass of QGraphicsItem.
So is there any way to do that?
I think converting the QPainter render results to QPixmap can solve the problem.But I don't know how.And I don't know how the performance of converting then applying the blur effect in real-time would be.
Here's an excerpt from what I've written so far:
void MainWindow::paintEvent(QPaintEvent *event){
Q_UNUSED(event);
QRadialGradient grad(QPoint(this->width()/2,this->height()/2) , 50);
grad.setSpread(QGradient::RepeatSpread);
grad.setColorAt(0 , QColor(0,0,0));
grad.setColorAt(1 , QColor(100,100,100));
QPainter paint(this);
paint.setRenderHint(QPainter::Antialiasing , true);
QRectF r1(0,0,this->width(),this->height());
paint.drawRect(r1);
QBrush brush(grad);
paint.fillRect(r1 , brush);
...
...
}
And here's the results:
Thanks.

Your question is clearly an XY problem, instead of asking about the underlying problem: How to display a QGraphicsItem that shows the circular gradient + blur effect, questions about an attempted solution: convert to QPixmap.
In this case, the simplest thing is to use a QGraphicsRectItem where you set the gradient as brush and the effect is applied to that item:
QGraphicsBlurEffect *effect = new QGraphicsBlurEffect(this);
effect->setBlurRadius(100);
QRadialGradient grad(QPoint(50, 50), 50);
grad.setSpread(QGradient::RepeatSpread);
grad.setColorAt(0 , QColor(0, 0, 0));
grad.setColorAt(1 , QColor(100, 100, 100));
QGraphicsRectItem *item = new QGraphicsRectItem(0, 0, 100, 100);
item->setBrush(QBrush(grad));
item->setGraphicsEffect(effect);

Related

How do I add a border to a rounded QPixmap?

I'm trying to show a rounded avatar QPixMap with a white border around it. However, I have no clue as to how I could add the border... Is it even possible?
This is the function I have to draw the avatar image.
void AccountDropDownMenu::setAvatar(
const QByteArray& bytes)
{
QPixmap new_avatar;
new_avatar.loadFromData(bytes);
new_avatar = new_avatar.scaledToHeight(40, Qt::SmoothTransformation);
QBitmap map(new_avatar.size());
map.fill(Qt::color0);
QPainter painter(&map);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::color1);
painter.setPen(QPen(Qt::white, 10));
painter.drawRoundedRect(
m_avatar_label->x(),
m_avatar_label->y(),
new_avatar.width(),
new_avatar.height(),
45,
45);
new_avatar.setMask(map);
avatar_label->setPixmap(new_avatar);
}
Update
Thanks to dtech I was able to get the desired output using the following updated function.... Although it's a bit pixly (the border).
void AccountDropDownMenu::setAvatar(
const QByteArray& bytes)
{
QPixmap new_avatar;
new_avatar.loadFromData(bytes);
new_avatar = new_avatar.scaledToHeight(40, Qt::SmoothTransformation);
QBitmap map(new_avatar.size());
map.fill(Qt::color0);
QPainter painter(&map);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::color1);
painter.drawRoundedRect(
QRectF(
avatar_label->x(),
avatar_label->y(),
new_avatar.width(),
new_avatar.height()),
40,
40);
new_avatar.setMask(map);
QPainter painter2(&new_avatar);
painter2.setRenderHint(QPainter::Antialiasing);
painter2.setPen(QPen(Qt::white, 1));
painter2.drawRoundedRect(
QRectF(
avatar_label->x(),
avatar_label->y(),
new_avatar.width(),
new_avatar.height()),
40,
40);
avatar_label->setPixmap(new_avatar);
}
In Qt you draw fills with a brush, but outlines are drawn with a QPen.
I haven't used QPainter in a long time, but IIRC, the default pen is zero width, which would explain why you aren't getting anything - you are not setting a pen.
Also, you don't need "another" rounded rectangle, you can apply fill and outline to the same geometry, as demonstrated in this answer.
Update:
Your updated code sets a mask, which sets an alpha channel. That cuts away from what you already have, it could not possibly add anything. You have to paint on the pixmap. Simply use new_avatar as the paint device - QPainter painter(&new_avatar); and get rid of the rest.

QPropertyAnimation for rotating QWidget

I'm new to Qt and I'm having some problem with QWidget rotation.
I have a QPixmap inside a QLabel.
What I want is to animate it with a continuous rotation by 90 degrees.
I know QPropertyAnimation and I know how to use it, but I'm struggling with How to use it for rotating a QWidget. Is there any simple way to use achieve my goal and rotate the entire QLabel or the QPixmap inside it with an animation?
Thank you.
This is the demo for rotation of QLabel/QPixmap with animation.
it's not necessary to use QPropertyAnimation. Because there is no rotate property for QLabel or QPixmap. So used QVariantAnimation make QPixmap rotate as animation and use QPixmap::transformed to rotate it. If you want well to control the animation of the pixmap, highly recommend QGraphicsPixmapItem with QPropertyAnimation
class RotateMe : public QLabel {
Q_OBJECT
public:
explicit RotateMe(QWidget* parent = Q_NULLPTR) :
QLabel(parent),
pixmap(100, 100),
animation(new QVariantAnimation )
{
resize(200, 200);
pixmap.fill(Qt::red);
animation->setDuration(10000);
animation->setStartValue(0.0f);
animation->setEndValue(90.0f);
connect(animation, &QVariantAnimation::valueChanged, [=](const QVariant &value){
qDebug()<<value;
QTransform t;
t.rotate(value.toReal());
setPixmap(pixmap.transformed(t));
});
animation->start();
}
private:
QPixmap pixmap;
QVariantAnimation *animation;
};
You can implement the rotation in two ways:
1) Create a collection of static images each of which represents the original pixmap rotated by some angle. With a timer you can change your label's pixmap with one from your collection. This will imitate the animated rotation.
2) Use a single pixmap and override your label's QLabel::painEvent() where you should rotate the QPainter object with QPainter::rotate() function each time you redraw the label.

How can I show/hide background drawing on QGraphicsScene or QGraphicsView?

I would like to have certain things drawn on QGraphicsScene, but not be QGraphicsItem (it would interfere with the processing of the QGraphicsItem collection).
Example: a scene bounding rectangle, a grid
I am overriding the drawBackground(QPainter *painter, const QRectF &rect) for that purpose. (I should subclass the scene... )
void MyView::showHideBounds()
{
m_showBackgroundBounds = !m_showBackgroundBounds;
// can't triger an update ???
update(); // neither does anything
viewport()->update();
}
void MyView::drawBackground(QPainter *painter, const QRectF &rect)
{
QPen pen;
if(m_showBackgroundBounds)
pen = QPen(QColor(0, 0, 0), 10, Qt::PenStyle(Qt::SolidLine));
else
pen = QPen(QColor(255, 255, 255), 10, Qt::PenStyle(Qt::SolidLine));
painter->setPen(pen);
painter->drawRect(QRect(QPoint(-scene()->sceneRect().size().toSize().width()/2,
-scene()->sceneRect().size().toSize().height()/2),
scene()->sceneRect().size().toSize()));
}
I would like the option to show/hide either the bounding rectangle or the grid.
The only thing I can think of is paint over them with the color of the background brush ? Is there any other option ?
As I have written it above, it works - except I need user action on items (or a zoom or some other scene changing action) to trigger refresh, or call an update... (the function showHideBounds doesn't - not sure how to make it force a refresh)
I would call the drawBackground from the showHideBounds function - but I don't know how to get the painter
[Also, the drawBackground seems to be drawn automatically... how can I give it the rect argument it needs ? (it seems if I draw the rect it does draw the scene rectangle but I only see the right and bottom edges)]
In order to redraw a particular section of scene, you can call
QGraphicsScene->invalidate(rect_to_redraw, Backgroundlayer)
Note that if drawBackground(*painter, rect) paints over area outside rect, it will not update automatically. In that case invalidate has to be called with appropriate rect parameters.

Use Clipping in Qt

Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?
The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).
My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.
If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?
The used Qt version is Qt 4.8.6
My questions are:
Is it possible to achieve what I want with the mentioned strategy?
Is it possible in any way to clip all the children, too?
Is my strategy appropriate or is it a bad Idea to solve it that way?
Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?
Additional Information
I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:
This works.
/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry()
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setPen(QColor(255, 0, 0));
painter.setBrush(QColor(255, 0, 0));
painter.setOpacity(1);
painter.drawRect(rect);
}
But the following doesn't change anything:
/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry();
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setRenderHint(QPainter::Antialiasing);
painter.setOpacity(1);
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.
The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:
case PE_Widget:
if (w && !rule.hasDrawable()) {
QWidget *container = containerWidget(w);
if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
&& (container == w || !renderRule(container, opt).hasBackground())) {
//we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
// (this may happen if we have rules like :focus)
p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
}
break;
}
As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:
if (q->testAttribute(Qt::WA_StyledBackground)) {
painter->setClipRegion(rgn);
QStyleOption opt;
opt.initFrom(q);
q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}
Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.
To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:
QPixmap pixmap(widget->size());
widget->render(&pixmap);
And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.

Draw a multi-colored line in Qt

What I'm trying to achieve is the following: I have a QGraphicsScene with a QGraphicsPixmapItem shown in it. The pixmap has multiple colors and I need to draw a line across the pixmap that must be visible and recognizable in every single point.
My idea is to draw a line where every pixel has the negative (complementary) color of the pixmap's relative pixel. So I thought about subclassing QGraphicsItem and reimplement the paint() method to draw a multi-colored line.
However I'm stuck because I don't know how I can retrieve the pixel information of the pixmap from the paint function, and even if I found out, I can't think of a way to draw the line in this way.
Could you give me some advice on how to proceed?
You can use QPainter's compositionMode property to do something like this pretty easily, without having to read the source pixel colors.
Simple sample QWidget with a custom paintEvent implementation, which you should be able to adapt to your item's paint method:
#include <QtGui>
class W: public QWidget {
Q_OBJECT
public:
W(QWidget *parent = 0): QWidget(parent) {};
protected:
void paintEvent(QPaintEvent *) {
QPainter p(this);
// Draw boring background
p.setPen(Qt::NoPen);
p.setBrush(QColor(0,255,0));
p.drawRect(0, 0, 30, 90);
p.setBrush(QColor(255,0,0));
p.drawRect(30, 0, 30, 90);
p.setBrush(QColor(0,0,255));
p.drawRect(60, 0, 30, 90);
// This is the important part you'll want to play with
p.setCompositionMode(QPainter::RasterOp_SourceAndNotDestination);
QPen inverter(Qt::white);
inverter.setWidth(10);
p.setPen(inverter);
p.drawLine(0, 0, 90, 90);
}
};
This will output something like the following image:
Experiment with the other composition modes to get more interesting effects.

Resources