QPainter composition not working as expected with background - c++

I'm trying to draw two rectangles with same color and transparency on a QFrame with a white background. These rectangles should overlap and the their transparency should not change (also in the overlapping region). So like this:
Here is the code I have so far:
class Canvas : public QFrame
{
public:
void paintEvent(QPaintEvent * event) override;
};
void Canvas::paintEvent(QPaintEvent *event)
{
QPainter painter( this );
painter.setPen(QPen(Qt::NoPen));
painter.setBrush(QBrush(QColor(0,0,255,125)));
painter.drawRect(QRect(10,10,100,100));
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.setBrush(QBrush(QColor(0, 0, 255, 125)));
painter.drawRect(QRect(80, 80, 100, 100));
}
int main( int argc, char **argv )
{
QApplication a( argc, argv );
Canvas canvas;
canvas.setAutoFillBackground(true);
QPalette pal;
pal.setColor(QPalette::Window, QColor(Qt::red));
canvas.setBackgroundRole(QPalette::Window);
canvas.setPalette(pal);
canvas.show();
return a.exec();
}
However this produces the following image:
I have tried every possible composition mode for the painter, but none seem to give me the desired effect. I guess CompositionMode_Source is the correct one since if I use the following code:
QPixmap pixmap(200, 200);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setPen(QPen(Qt::NoPen));
painter.setBrush(QBrush(QColor(0, 0, 255, 125)));
painter.drawRect(QRect(10, 10, 100, 100));
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.setBrush(QBrush(QColor(0, 0, 255, 125)));
painter.drawRect(QRect(80, 80, 100, 100));
QLabel label;
label.setPixmap(pixmap);
label.show();
I do get the desired effect (but without the red background):
However if I change the fill to Qt::red I get again:
What am I missing here? How can I get my desired effect? The actual application for this is that I want to draw rectangles on a QFrame derived class which is implemented in a third party lib over which I have limited control.

I spot three problems with the code:
the first rectangle is drawn with alpha blending (Source Over mode) because you're setting the composition mode after the first draw call. The second one instead uses Source mode (i.e. copy the source pixels as-is, do not perform alpha blending).
Indeed Source does not perform alpha blending, which you seem to want. So don't use that! The default composition mode does what you want.
Drawing two different shapes will perform composition between them. That's obviously expected, since you're doing two draw calls; the second draw call will find the destination already changed by the first. If you don't want that, you must find a way to draw both shapes in one draw call (for instance: add both of them to one QPainterPath, then draw the path in one draw call), or perform composition at a later stage (for instance: draw them onto an opaque QImage, then blend the image over the destination in one draw call).

Related

QPixmap with a transparent part

I'm making a simple puzzle game. The piece is a square and it contains the corresponding part of an image. What I need to do is to change the shape from the square to the common puzzle shape. I've made a template in Photoshop:
Corner piece
Now I need somehow to use it as a mask to change the shape of my square piece. I tried the following:
QImage temp;
temp.load("://Templates/c1.png");
temp = temp.convertToFormat(QImage::Format_Mono);
QBitmap bit;
bit = QBitmap::fromImage(temp);
puz->img.setMask(bit);
Where puzzle->img is my square piece. It works, but I get "rough corners" in the connectors. I suppose all of half-transparent pixels become fully filled with a color (or fully transparent?), so that's why it doesn't look smooth.
Is there a way to make it smoothier?
UPDATE: I tried a different approach: since setAlphaChannel() is an obsolete function, documentation says that I need to use QPainter::CompositionMode. I tried the following
void puzzle::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget )
{
painter->setCompositionMode(QPainter::CompositionMode_Source);
painter->fillRect(x, y, 90, 90, Qt::transparent);
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->drawPixmap(x, y, 90, 90, img);
painter->setCompositionMode(QPainter::CompositionMode_DestinationIn);
painter->drawImage(x,y, mask);
}
It works like that in Image Composition example, but I get black pixels instead of transparent in my project: Output. Can't figure out, what's wrong. mask initialization:
mask.load("://Templates/c1.png");
mask = mask.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation);
mask = mask.convertToFormat(QImage::Format_ARGB32_Premultiplied);
You should use setAlphaChannel instead. A mask only has values of 0 and 1 while the alpha channel should allow 256 variations in transparency.

How to fill a Rectangle (a Rect object) with an image in Qt?

I've tried this:
QBrush brush(QPixmap(":/new/prefix1/car.jpg"));
painter.setBrush(brush);
QRectF car(positions[i],120, 20, 10 );
painter.drawRect(car);
It shows the image but it repeats itself during a simulation in QPaint.
I want a way to fill a rectangle with an image but i'm not finding any specific methods for Rect. Any tricks for that?
Use QPainters drawPixmap. There is an overloaded function, that takes both a QPixmap and a QRect into which the pixmap will be painted:
http://doc.qt.io/qt-5/qpainter.html#drawPixmap-9

Qt : Drawing Blended Transparent Lines/Curves/Paths

I have been able to draw long transparent curves with the QPainterPath so I wont get overlapping opacity joints that would result in connecting lines between points like in Scribble. But is there a way to make a path blend its continuous transparency through out in Qt as such:
I suspect the most visually satisfying solution will be to render the strokes yourself. For example, the image you posted was rendered by drawing a large number of partially-transparent circles over one another. This could be optimized by rendering a large number of ellipses onto a QImage, then later drawing the pre-rendered image to save time.
With the help of this question/answer I wrote this code that does the job:
/* Start and end point. */
const QPointF start{ 0,0 };
const QPointF end{ 100,100 };
QGraphicsLineItem line{ QLine(start, end) };
/* Make the Gradient for this line. */
QLinearGradient gradient(start, end);
QColor color(123, 123, 231); //some color
color.setAlphaF(0.9); //change alpha
gradient.setColorAt(0, color);
color.setAlphaF(0.1); //change alpha again
gradient.setColorAt(1, color );
/* Set the line's pen. */
QPen pen(QBrush(gradient), 10);
line.setPen(pen);

How to draw custom shapes in Qt with QPainter or QPainterPath using one shape or a group of shapes joined

How can I draw a shape like a tear? I need to draw without using more than one shape (an ellipse and a polygon) because QPen will draw for each shape. I need to join shapes to create a new one, or tell QT to join the border across both shapes, something like this:
If the shape you want to draw can be represented as a layering of other shapes, as with the image you've linked to, it's pretty easy to do:
First we need to build a QPainterPath to represent the outer edge of the shape. We build it by layering up simpler shapes; in the case of your example we need a circle and a square. Note the use of QPainterPath::setFillRule(Qt::WindingFill): this will later affect the way that the path is painted (try removing it to see the difference!).
QPainterPath OuterPath;
OuterPath.setFillRule(Qt::WindingFill);
OuterPath.addEllipse(QPointF(60, 60), 50, 50);
OuterPath.addRect(60, 10, 50, 50);
With the example you've given we'll also need to remove a circular area from the centre of our filled shape. Let's represent that inner 'border' as a QPainterPath and then use QPainterPath::subtracted() to subtract InnerPath from OuterPath and produce our final shape:
QPainterPath InnerPath;
InnerPath.addEllipse(QPointF(60, 60), 20, 20);
QPainterPath FillPath = OuterPath.subtracted(InnerPath);
Once we've built the shape paths, we need to use them to fill/outline the shape. Let's first create a QPainter and set it to use antialiasing:
QPainter Painter(this);
Painter.setRenderHint(QPainter::Antialiasing);
We then need to fill the shape that we've built:
Painter.fillPath(FillPath, Qt::blue);
Finally, let's paint the outlines. Note that, because we have separate paths for the inner and outer borders, we are able to stroke each border with different line thicknesses. Note also the use of QPainterPath::simplified(): this converts the set of layered shapes into one QPainterPath which has no intersections:
Painter.strokePath(OuterPath.simplified(), QPen(Qt::black, 1));
Painter.strokePath(InnerPath, QPen(Qt::black, 3));
If we put all of that together, it looks like this:
void Shape::paintEvent(QPaintEvent *)
{
QPainterPath OuterPath;
OuterPath.setFillRule(Qt::WindingFill);
OuterPath.addEllipse(QPointF(60, 60), 50, 50);
OuterPath.addRect(60, 10, 50, 50);
QPainterPath InnerPath;
InnerPath.addEllipse(QPointF(60, 60), 20, 20);
QPainterPath FillPath = OuterPath.subtracted(InnerPath);
QPainter Painter(this);
Painter.setRenderHint(QPainter::Antialiasing);
Painter.fillPath(FillPath, Qt::blue);
Painter.strokePath(OuterPath.simplified(), QPen(Qt::black, 1));
Painter.strokePath(InnerPath, QPen(Qt::black, 3));
}
This is actually quite difficult to do without a good math background. If you knew the formula to create that shape, you could just put it into your QGraphicsItem::paint() function. But there are some alternatives:
Make the image in a vector editing program like Inkscape (free), save it as a .svg file, and then load it into a QGraphicsSvgItem. (This is what I would do.)
Have a look at QPainterPath::cubicTo(), which allows you to make a Bezier curve

Efficient off-screen rendering of QPainterPaths (OpenGL and non-OpenGL solution required)

In my application, I paint a street map using QPainter on a widget
made by QPainterPaths that contain precalculated paths to be drawn
the widget is currently a QWidget, not a QGLWidget, but this might change.
I'm trying to move the painting off-screen and split it into chunked jobs
I want to paint each chunk onto a QImage and finally draw all images onto the widget
QPainterPaths are already chunked, so this is not the problem
problem is, that drawing on QImages is about 5 times slower than drawing on QWidget
Some benchmark testing I've done
time values are rounded averages over multiple runs
test chunk contains 100 QPainterPaths that have about 150 linear line segments each
the roughly 15k paths are drawn with QPainter::Antialiasing render hint, QPen uses round cap and round join
Remember that my source are QPainterPaths (and line width + color; some drawn, some filled)
I don't need all the other types of drawing QPainter supports
Can QPainterPaths be converted to something else which can be drawn on a OpenGL buffer, this would be a good solution.
I'm not familiar with OpenGL off-screen rendering and I know that there are different types of OpenGL buffers, of which most of them aren't for 2D image rendering but for vertex data.
Paint Device for chunk | Rendering the chunk itself | Painting chunk on QWidget
-----------------------+----------------------------+--------------------------
QImage | 2000 ms | < 10 ms
QPixmap (*) | 250 ms | < 10 ms
QGLFramebufferObj. (*) | 50 ms | < 10 ms
QPicture | 50 ms | 400 ms
-----------------------+----------------------------+--------------------------
none (directly on a QWidget in paintEvent) | 400 ms
----------------------------------------------------+--------------------------
(*) These 2 lines have been added afterwards and are solutions to the problem!
It would be nice if you can tell me a non-OpenGL-based solution, too, as I want to compile my application in two versions: OpenGL and non-OpenGL version.
Also, I want the solution to be able to render in a non-GUI thread.
Is there a good way to efficiently draw the chunks off-screen?
Is there an off-screen counter part of QGLWidget (an OpenGL off-screen buffer) which can be used as a paint device for QPainter?
The document of Qt-interest Archive, August 2008 QGLContext::create()
says:
A QGLContext can only be created with a valid GL paint device, which
means it needs to be bound to either a QGLWidget, QGLPixelBuffer or
QPixmap when you create it. If you use a QPixmap it will give you
software-only rendering, and you don't want that. A QGLFramebufferObject
is not in itself a valid GL paint device, it can only be created within
the context of a QGLWidget or a QGLPixelBuffer. What this means is that
you need a QGLWidget or QGLPixelBuffer as the base for your
QGLFramebufferObject.
As the document indicated, if you want to render in an off-screen buffer using opengl, you need QGLPixelBuffer. The code below is a very simple example which demonstrates how to use QGLPixelBuffer with OpenGL:
#include <QtGui/QApplication>
#include <Windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include <QtOpenGL/QGLFormat>
#include <QtOpenGL/QGLPixelBuffer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Construct an OpenGL pixel buffer.
QGLPixelBuffer glPixBuf(100, 100);
// Make the QGLContext object bound to pixel buffer the current context
glPixBuf.makeCurrent();
// The opengl commands
glClearColor(1.0, 1.0, 1.0, 0.0);
glViewport(0, 0, 100, 100);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 100, 0, 100);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 0.0, 0.0);
glPointSize(4.0);
glBegin(GL_TRIANGLES);
glVertex2i(10, 10);
glVertex2i(50, 50);
glVertex2i(25, 75);
glEnd();
// At last, the pixel buffer was saved as an image
QImage &pImage = glPixBuf.toImage();
pImage.save(QString::fromLocal8Bit("gl.png"));
return a.exec();
}
The result of the program is a png image file as:
For non-opengl version using QPixmap, the code maybe in forms of below:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPixmap pixmap(100, 100);
QPainter painter;
painter.begin(&pixmap);
painter.drawText(10, 45, QString::fromLocal8Bit("I love American."));
painter.end();
pixmap.save(QString::fromLocal8Bit("pixmap.png"));
return a.exec();
}
The result of the program above is a png file looks like:
Though the code is simple, but it works, maybe you can do some changes to make it suitable for you.