QPainter composition mode example does not work as expected - c++

I'm stuck with the difference from the book example and my version of it.
Qt version 5.12.0
As it's shown in the example:
As I see from my output:
First, destination and source In/Atop modes have not the same pictures. And, another noticed thing is, that we can see the rectangle as an additional layer between two.
Code to create the label:
QLabel* lblCreate(const QPainter::CompositionMode& mode){
QLabel* lbl = new QLabel;
lbl->setFixedSize(100, 100);
QRect rect(lbl->contentsRect());
QPainter painter;
// create first image
QImage sourceImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
painter.begin(&sourceImage);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setBrush(QColor(0, 255, 0));
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
// draw triangle
painter.drawPolygon(QPolygon() << rect.bottomLeft()
<< QPoint(rect.center().x(), 0)
<< rect.bottomRight());
painter.end();
// create second image
QImage resultImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
painter.begin(&resultImage);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(QColor(0, 255, 0), 4));
painter.setBrush(QColor(255, 0, 0));
// draw circle
painter.drawEllipse(rect);
painter.setCompositionMode(mode);
painter.drawImage(rect, sourceImage);
painter.end();
lbl->setPixmap(QPixmap::fromImage(resultImage));
return lbl;}
How it creates in main.cpp:
innerLayout_2->addWidget(lblCreate(QPainter::CompositionMode_Source), 0, 0);
innerLayout_2->addWidget(new QLabel("<CENTER>Source</CENTER>"), 1, 0);
My own suspicion is it may be depend on QImage::Format_ARGB32_Premultiplied.
Or it's mine handmade bug.
Anyway, I would be grateful for any ideas.
Thnx in advance!

The composition mode works on transparent backgrounds, in your case it is not, so you must set it before painting, for this you could use the fill() method:
QImage sourceImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
sourceImage.fill(Qt::transparent);
QImage resultImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
resultImage.fill(Qt::transparent);

Related

How to create custom 3D looking button in Qt?

I'm trying to create the following custom button:
To do this I've create the class and overrided paintEvent:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPen pen(Qt::darkGray, 7, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen);
painter.drawEllipse(QPointF(width()/2, height()/2), width()/2.1,height()/2.1);
QPen pen2(Qt::lightGray, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen2);
painter.drawEllipse(QPointF(width()/2, height()/2), width()/2.15, height()/2.15);
QPen pen1(Qt::gray, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen1);
painter.drawEllipse(QPointF(width()/2, height()/2), width()/3.5, height()/3.5);
}
But, I'm not able to create the button like below with appropriate gradients and picture in the center.
Can you please help me?
The key thing here is to make the gradients. And you cannot make gradients on strokes, only on fills. Meaning that you will have to implement the outline as a fill.
There is the button's components digested, listed in the order you should draw them:
Mind you that only the first component is a solid color, everything else is gradients. It is those gradients which get the effect.
In order to get gradients for the inner ring you will have to use QPainterPath, fist add the outer circle, and then add a slightly smaller inner circle, which will effectively punch a hole in the first circle, giving you something that looks like an outline but is actually a fill, a fill you can use a gradient on.
As mentioned in the comments - this involves a lot of operations and is not ideal. It would be preferable to have such buttons as images instead of painting them with QPainter.
Update: here is some more help, to show you how to draw a gradient outline in order to achieve some 3D illusion:
class Test : public QWidget {
Q_OBJECT
public:
Test() { resize(200, 200); }
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(rect(), Qt::gray);
QPainterPath p;
p.addEllipse(rect().center(), 50, 50);
p.addEllipse(rect().center(), 45, 45);
QLinearGradient g(QPointF(0, 50), QPointF(0, 150));
g.setColorAt(0, Qt::white);
g.setColorAt(1, Qt::black);
QBrush b(g);
painter.fillPath(p, b);
QPainterPath p2;
p2.addEllipse(rect().center(), 45, 45);
p2.addEllipse(rect().center(), 40, 40);
QLinearGradient g1(QPointF(0, 50), QPointF(0, 150));
g1.setColorAt(0, Qt::black);
g1.setColorAt(1, Qt::white);
QBrush b1(g1);
painter.fillPath(p2, b1);
QPainterPath p3;
p3.addEllipse(rect().center(), 40, 40);
painter.fillPath(p3, b);
}
};
And the result:
Considering the question reworded as:
How to use QPainter to generate the given picture?
Using QPainter:
Get a vectorial image of the button, ideally in SVG.
Look the SVG code to extract colors, gradients, sizes, etc.
Reproduce them with QPainter, you will see that for each SVG command, you have an nearly equal equivalent in QPainter. QPainterPath will help.
Using QPixmap:
Get an SVG image of your button
Use QSvgRenderer to generate a QImage of the given size, and then convert it to a QPixmap.
Paint the QPixmap when required.
Benefits of each options:
QPixmap is simpler, and allows you to switch the button aesthetic easily.
QSvgRenderer allows you to manage hover easily
QPainter allows you to override colors with those of the OS
QPainter give you more flexibility for effects, animations, etc.

QPaintedTextureImage in Qt3D (Qt 5.8)

I want to create an entity with Qt3D that has a custom image as texture. I came across the QPaintedTextureImage (link leads to Qt 5.9 version for details. Here ist doc for 5.8), which can be written with a QPainter but I don't understand how.
First, this is how I imagine the entity could look like:
[EDIT]: code is edited and works now!
planeEntity = new Qt3DCore::QEntity(rootEntity);
planeMesh = new Qt3DExtras::QPlaneMesh;
planeMesh->setWidth(2);
planeMesh->setHeight(2);
image = new TextureImage; //see below
image->setSize(QSize(100,100));
painter = new QPainter;
image->paint(painter)
planeMaterial = new Qt3DExtras::QDiffuseMapMaterial;
planeMaterial->diffuse()->addTextureImage(image);
planeEntity->addComponent(planeMesh);
planeEntity->addComponent(planeMaterial);
TextureImage is the subclassed QPaintedTextureImage with paint function:
class TextureImage : public Qt3DRender::QPaintedTextureImage
{
public:
void paint(QPainter* painter);
};
What does the QPainter, passed to paint function, need to do in the implementation of paint if I just want to draw a big circle to the planeEntity?
[Edit] Implementation:
void TextureImage::paint(QPainter* painter)
{
//hardcoded values because there was no device()->width/heigth
painter->fillRect(0, 0, 100, 100, QColor(255, 255, 255));
/* Set pen and brush to whatever you want. */
painter->setPen(QPen(QBrush(QColor(255, 0, 255)) ,10));
painter->setBrush(QColor(0, 0, 255));
/*
* Draw a circle (or an ellipse -- the outcome depends very much on
* the aspect ratio of the bounding rectangle amongst other things).
*/
painter->drawEllipse(0, 0, 100, 100);
}
The short answer is... use QPainter exactly the same way you would normally.
void TextureImage::paint (QPainter* painter)
{
int w = painter->device()->width();
int h = painter->device()->height();
/* Clear to white. */
painter->fillRect(0, 0, w, h, QColor(255, 255, 255));
/* Set pen and brush to whatever you want. */
painter->setPen(QPen(QBrush(QColor(0, 0, 0)) ,10));
painter->setBrush(QColor(0, 0, 255));
/*
* Draw a circle (or an ellipse -- the outcome depends very much on
* the aspect ratio of the bounding rectangle amongst other things).
*/
painter->drawEllipse(0, 0, w, h);
}
However, note that you really shouldn't invoke the paint method directly. Instead use update which will cause Qt to schedule a repaint, initialize a QPainter and invoke your overridden paint method with a pointer to that painter.
It might be simpler to dynamically load the image you need in QML.
I had to do it not so long ago and opened a question on SO for it:
Qt3D dynamic texture

QT5: Drawing rect on top of another rect using drawPixMap()

am I using drawPixmap() correctly?
Essentially my goal is to take an tileset image, and replace an individual tile with a custom tile image.
I'm able to get both images to load on the screen, but when I call drawPixmap(), then original image doesn't change at all.
Thanks in advance.
void replaceCustomTile(QPixmap custom, QPixmap target, int whichTile) {
QRect rect(0, 0 + (squareTileSize * whichTile), squareTileSize, squareTileSize);
QRect customRect = custom.rect();
QPainter painter(this);
painter.drawPixmap(rect, target, customRect);
painter.end();
}
EDIT:
This is how replaceCustomTile is called:
QPixmap terrainTiles(":/static/Terrain.png");
QPixmap customTile(":/static/Smiles.png");
replaceCustomTile(customTile, terrainTiles, 0);
To intialize QPainter by this it must be called from the widget paintEvent(QPaintEvent *) if you want to draw it on some widget. So, replaceCustomTile() should be called from the event handler in that case.
To draw some pixmap on top of another pixmap QPainter should be initialized by the target pixmap using QPainter::begin():
QPainter painter;
painter.begin(&target);
painter.drawPixmap(rect, custom);
painter.end();
The above code draws QPixmap custom into given QRect rect over QPixmap target. The target is modified.

Qt- Change opacity of QPixmap

How to change opacity of QPixmap?
I've set an image as background actually I want to change its opacity, Here is my code:
Call.h:
private:
QPixmap m_avatar;
Call.cpp:
void Call::resizeEvent(QResizeEvent *e)
{
QPalette pal = palette();
pal.setBrush(backgroundRole(), m_avatar.scaled(e->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
setPalette(pal);
}
I've changed resizeEvent function, but it doesn't change background's opacity.
void Call::resizeEvent(QResizeEvent *e)
{
QPixmap result_avatar(m_avatar.size());
result_avatar.fill(Qt::transparent);
QPainter painter;
painter.setOpacity(0.5);
painter.begin(&result_avatar);
painter.drawPixmap(0, 0, m_avatar);
painter.end();
QPalette pal = palette();
pal.setBrush(backgroundRole(), result_avatar.scaled(e->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
setPalette(pal);
}
Any suggestion?
You are not using local QPainter object. According to QWidget Events:
paintEvent() is called whenever the widget needs to be repainted.
Every widget displaying custom content must implement it. Painting
using a QPainter can only take place in a paintEvent() or a function
called by a paintEvent().
Here it works:
void Call::paintEvent(QPaintEvent *)
{
// create a new object scaled to widget size
QPixmap result_avatar = m_avatar.scaled(size());
QPainter painter(this);
painter.setOpacity(0.5);
// use scaled image or if needed not scaled m_avatar
painter.drawPixmap(0, 0, result_avatar);
}
Update for paiting on pixmap case
If it is needed only to paint with some opacity on a pixmap using QPainter, the opacity must be set only after QPainter activation by QPainter::begin(). So, after changing the order the pixmap result_avatar has two images (one resized with opacity 1 and original pixmap on top with opacity 0.5):
QPainter painter;
painter.begin(&result_avatar);
painter.setOpacity(0.5);
painter.drawPixmap(0, 0, m_avatar);
painter.end()

How to flip a QImage

What is the right way to flip/mirror a QImage? The following snippet does not work.
//allocate buffer
BYTE* pRgb32Buffer = new BYTE[width*height* 4];
//create paint device
QImage img = QImage(pRgb32Buffer , width, height, getStride(width, pixelFormat), QImage::Format_RGB32);
//do some drawing on image (works!)
QPainter painter(&img);
painter.drawText(10, 50, QString("some text"));
//mirrore image (doesn't mirror the orignal buffer!!!)
img = img.mirrored(false,true);
//doesn't work either
//QImage mirrored = img.mirrored();
//img = mirrored;
//mirrored.detach();
A solution using QImage::mirrored():
memmove(pRgb32Buffer, img.mirrored().bits(),img.byteCount());
I have this working code to mirror a QPixmap onto an QImage.
QPixmap* source = //... Getting my pixmap form somewhere...
QImage target(QSize(source->width(), source->height()), QImage::Format_ARGB32);
QPainter painter(&target);
QTransform transf = painter.transform();
transf.scale(-1, 1);
painter.setTransform(transf);
painter.drawPixmap(-source->width(), 0, *source);
The source contains the mirrored pixmap after that code. You should be able to do the same with QImageand the QPainter::drawImage function as alternative.
Optionally you can save the file if you want (make sure you have the imageformats dll's or it won't write):
QImageWriter writer("c:\\theimage.tiff", "tiff");
writer.setCompression(1);
writer.write(target);