QBrush texture without tiling - qt

Is there an easy way to get rid of tiling when using a QBrush with texture?
QImage* texture = CreateQImage(); // create texture
QBrush* brush = new QBrush(*texture); // create texture brush
QPainter* painter = CreateQPainter(); // create painter
painter->fillRectangle(0, 0, 500, 500, *brush);
Suppose we have a QImage texture with size of 20x20 pixels. The code above will tile this texture all across the rectangle being filled. Is there an easy way to draw only a single instance of this texture? The QBrush usage is crucial.
Theoretically, I could reload every fill and draw method of the QPainter that takes a QBrush as input and use a QPainter.drawImage() method, but I think there must be a simplier way.
Thanks, Tony.

I don't think there is (see Qt::BrushStyle - the only style with a texture tiles it), and it wouldn't really make sens IMO. If you just want one image, use the drawImage functions as you've stated.
(One of the problems with not tiling is: what do you fill the rest of the rectangle with? Nothing? Some default background color? Some other QBrush attribute?)

Related

(Qt) Render SVG with custom color

I want to render a SVG icon in a QPixmap by choosing the drawing color.
This is my code using PyQt:
def svg_to_pixmap(svg_filename: str, width: int, height: int, color: QColor) -> QPixmap:
renderer = QSvgRenderer(svg_filename)
pixmap = QPixmap(width, height)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
painter.setPen(QPen(color))
renderer.render(painter)
painter.end()
return pixmap
And a test code to display the pixmap:
app = QApplication([])
pixmap = svg_to_pixmap("test.svg", 512, 512, Qt.GlobalColor.red)
label = QLabel()
label.setStyleSheet("background-color: yellow;")
label.setPixmap(pixmap)
label.show()
app.exec()
The issue is that painter.setPen has no effect as expected (the drawing remains black). The background is transparent as expected and we can see the label background color behind.
An example of SVG file to test here
My configuration: Ubuntu22.10, X11, PyQt6.3.1
The answer is not simple. But, as long as you are completely sure that you only want to use the shape (or mask) of the original SVG, then it is feasible.
The reason for which setting the pen doesn't change the result is that SVG is a vector image format: it usually explicitly tells the colors of anything it wants to draw, so, setting the painter pen is almost useless, unless the SVG content is so simple to not specify it, which is clearly not the case: icons normally define colors of their shapes, and for good reasons.
The problem comes when trying to understand compositing and the possible alternatives that QPainter provides.
To be honest, even after trying to put efforts in understanding the results and associating them with the QPainter.CompositionMode enums, I still need to testing and checking in order to understand the proper mode I need.
What you probably need is the CompositionMode_SourceIn, which is cryptically explained in the documentation:
The output is the source, where the alpha is reduced by that of the destination.
As far as I can understand, the source is what is going to be painted, while the destination is what was previously painted (consider the source as a brush painting, and the destination as the canvas on which you paint).
With that in mind, we need to use the original svg as the destination and fill the whole pixmap with the color we want. Since only the alpha of the destination will be used (the visible part of the svg), we will be practically doing some sort of "stencil".
def svg_to_pixmap(svg_filename: str, width: int, height: int, color: QColor) -> QPixmap:
renderer = QSvgRenderer(svg_filename)
pixmap = QPixmap(width, height)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
renderer.render(painter) # this is the destination, and only its alpha is used!
painter.setCompositionMode(
painter.CompositionMode.CompositionMode_SourceIn)
painter.fillRect(pixmap.rect(), color)
painter.end()
return pixmap
Which seems to give the wanted result:

Best way to draw QPixmap with 'color mod'

Some rendering libraries allow to set a 'color mod' (for example, in SDL2 you do it with SDL_SetTextureColorMod) when drawing a texture, which would effectively multiply the colours of the pixels by a given value before drawing. What is the best way to achieve this in Qt5, for example, when drawing a QPixmap with QPainter::drawPixmap? So far the only option I see is to use a temporary pixmap, fill it with the colour by which I want to multiply, then draw on it with QPainter::CompositionMode_Multiply and then draw the result to the target device. Is there a more straightforward way that maybe does not include drawing to a temporary pixmap?
You can do without the temporay pixmap by drawing a rect with size of your pixmap in your target:
QPixmap const src(":/images/img.png");
painter->fillRect(QRect(QPoint(0, 0), src.size()), Qt::red);
painter->setCompositionMode(QPainter::CompositionMode_Multiply);
painter->drawPixmap(QPoint(0, 0), src);
If your pixmap has transparent regions, you can add painter->setClipRegion(src.mask()); before calling fillRect.

Does Qt have an additive blend mode?

QPainter has many composition modes but none called additive. I'm interested because additive blending is used all the time in games for lighting / particles whatever.
The overlay mode is the only one that had something like the effect of lighting.
EDIT: I figured it out, heres how you can efficiently make different coloured lights in Qt.
In constructor or where ever, not in paint event:
light = QPixmap("light.png");
QPainter pix(light);
pix.setCompositionMode(QPainter::CompositionMode_Overlay);
pix.fillRect(light.rect(), QColor(255, 0, 0, 255)); // colorize the light in any color
Paint Event:
// Do drawing, e.g. a background
p.drawPixmap(0, 0, QPixmap("background.png"));
// draw the lighting
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawPixmap(100, 100, light);
You can reuse the same pixmap as much as you like and draw it with different opacity or size etc.
The documentation for QPainter::CompositionMode_Plus says:
Both the alpha and color of the source and destination pixels are added together.

QPainter::drawPixmap() doesn't look good and has low quality?

I'm trying to draw an icon(.png) inside a QWidget with QPainter::drawPixmap()
:
QPixmap _source = "/.../.png";
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.drawPixmap(rect(), _source);
but in comparing to QLabel (for example) and in lower size (19*19 in my case) the result isn't perfect.
What can I do?
****Edit****
QLabel with pixmap # size 19*19:
My painting # size 19*19 via SmoothPixmapTransform render type:
You are setting the wrong render hint, you need QPainter::SmoothPixmapTransform to get smooth resizing. By default the nearest neighbor method is used, which is fast but has very low quality and pixelates the result.
QPainter::HighQualityAntialiasing is for when drawing lines and filling paths and such, i.e. when rasterizing geometry, it has no effect on drawing raster graphics.
EDIT: It seems there is only so much SmoothPixmapTransform can do, and when the end result is so tiny, it isn't much:
QPainter p(this);
QPixmap img("e://img.png");
p.drawPixmap(QRect(50, 0, 50, 50), img);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawPixmap(QRect(0, 0, 50, 50), img);
img = img.scaled(50, 50, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
p.drawPixmap(100, 0, img);
This code produces the following result:
There is barely any difference between the second and third image, manually scaling the source image to the desired dimensions and drawing it produces the best result. This is certainly not right, it is expected from SmoothTransformation to produce the same result, but for some reason its scaling is inferior to the scale() method of QPixmap.

Dynamic QImage , when no initial size specified

I'm trying to use QPainter to draw items onto a QImage , but since I can't predict the exact size of this QImage , I can't use QImage::save() , it always tell me:
QPainter::begin: Paint device returned engine == 0, type: 3
But if I specify the image height and width when declaring this QImage , it works smoothly:
QImage output = QImage (500 , 500 , QImage::Format_ARGB32);
QImage, QPixmap, etc. require data to be allocated before drawing can begin. Using the default constructor of QImage does not allocate any memory, so image.isNull() == true. So when you call QPainter::begin() (probably indirectly by creating one with the QImage as the paint device) it can't draw into any memory because it isn't there.
From the QPainter::begin() docs:
QPixmap image(0, 0);
painter->begin(&image); // impossible - image.isNull() == true;
So you have to come up with a size before drawing. In your situation, the best thing to do would be to decide on a maximum size (or calculate one if feasible), and then once you do know the exact size - crop the image.
Alternatively, you can draw on a QGraphicsScene which will expand automatically as you add items on it, then save only the painted area given by QGraphicsScene::itemsBoundingRect():
QGraphicsScene scene;
scene.addItem(...);
QImage image(scene.itemsBoundingRect().size(), QImage::Format_ARGB32);
QPainter painter(&image);
scene.render(&painter, image.rect(), scene.itemsBoundingRect());

Resources