0

I am trying to use QLabel as a powerful tool to render some text with CSS into a texture. After I obtain an image, a texture is formed in OpenGL and the original Qt object is discarded. Afterwards I use the texture as any other texture in plain OpenGL (without Qt) rendering pipeline.

However, I am having problems handling the transparency of the background. Something - probably some Qt setting that I am not aware of - seems to be messing up my settings.

After a bit of simplification, my texture is produced like this:

QLabel label;
label.setAttribute(Qt::WA_TranslucentBackground, true);

const QString styleSheet{"color : #00bbbb; background-color : rgba(0,0,0,0);"
                         "font-family: 'Calibri'; text-decoration: none; margin: 5px;"};
label.setWordWrap(true);
label.setAutoFillBackground(false);
label.setStyleSheet(styleSheet);

QFont font = label.font();
font.setPointSizeF(12.0f);
label.setFont(font);

// set text
label.setText(QString("The quick brown fox jumps over the lazy dog"));

// render
label.adjustSize();
label.updateGeometry();

std::unique_ptr<QImage> imgTexture = std::make_unique<QImage>(label.size(), QImage::Format_RGBA8888);
QPainter painter(imgTexture.get());
label.render(&painter);

uint8_t *bits = imgTexture->bits();
std::cout << int(bits[0]) << " " << int(bits[1]) << " " << int(bits[2]) << " " << int(bits[3]) << std::endl;

The output - the value of the top-left pixel of the produced image - is:

205 205 205 205

and not 0 0 0 0 as I expected. Thus, the problem is already at this point and not later in my OpenGL handling of the texture. Ultimately my output is:

enter image description here

As seen, the background is not entirely transparent as expected.


Update

As per G.M.'s suggestion I tried setCompositionMode (with no effect), but also other painter settings. Apparently setting QPainter::setBackgroundMode to Qt::OpaqueMode steps one step further:

enter image description here

The Qt manual says:

Qt::TransparentMode (the default) draws stippled lines and text without setting the background pixels. Qt::OpaqueMode fills these space with the current background color.

so, it seems the default is to not change the original pixels of the output image anywhere where letters are not present. So, the transparent (0,0,0,0) is not drawn, and the previous color (apparently 205, 205, 205, 205 for some reason) remains unchanged.

Forcing drawing the background updates all pixels but only in the neighbourhood of the letters. I now need to figure out how to force clearing all pixels to the color specified in CSS.

Update Apparently it is not as simple as it seems. I tried painter.eraseRect(0, 0, width, height); but this clears the rectangle into white, ignoring the CSS settings.

CygnusX1
  • 20,968
  • 5
  • 65
  • 109
  • Can you try adding `painter.setCompositionMode(QPainter::CompositionMode_Source)` immediately before the `label.render(&painter)` call. – G.M. Nov 16 '19 at 12:37
  • `painter.eraseRect(0, 0, width, height);`? What about `painter.fillRect(0, 0, width, height, QColor(0, 0, 0, 0));`? – Scheff's Cat Nov 17 '19 at 07:23
  • I had a look at [QPainter::eraseRect()](https://doc.qt.io/qt-5/qpainter.html#eraseRect-1) (hadn't noticed before): _Equivalent to calling `fillRect(rectangle, background())`._ ;-) I wonder a bit that the background is white - I rather would've expected gray (or, maybe, what you defined in CSS). – Scheff's Cat Nov 17 '19 at 07:29
  • Have you noticed that 205 is in hex `CD`? This sounds somehow very familiar. (If you use VisualStudio on Windows, you probably know what I mean.) I once wrote an answer which appears to me about a similar issue: [SO: Qt renders this SVG correctly in “debug” mode but not in “release”](https://stackoverflow.com/a/55898356/7478597) – Scheff's Cat Nov 17 '19 at 07:49
  • 1
    @Scheff Oh, that explains 205. Still, I was hoping QLabel to overdraw everything, so I didn't care about clearing the data. – CygnusX1 Nov 17 '19 at 07:53

1 Answers1

2

To combine the results of experiments and some comments. The behavior is a combination of:

  • When QImage is constructed with some image area, that area is uninitialized.

    QImage::QImage(int width, int height, QImage::Format format)

    Constructs an image with the given width, height and format.

    A null image will be returned if memory cannot be allocated.

    Warning: This will create a QImage with uninitialized data. Call fill() to fill the image with an appropriate pixel value before drawing onto it with QPainter.

    On Visual Studio in Debug mode, uninitialized area is initialized with 0xCD. In Release it would be some garbage

  • By default, when drawing text with QPainter, the background remains unchanged. The painter just adds the glyphs to whatever was previously present on the target image (in our case: the uninitialized area).

    void QPainter::setBackgroundMode(Qt::BGMode mode)

    Sets the background mode of the painter to the given mode

    Qt::TransparentMode (the default) draws stippled lines and text without setting the background pixels. Qt::OpaqueMode fills these space with the current background color.

    Note that in order to draw a bitmap or pixmap transparently, you must use QPixmap::setMask().

    So, in order for background pixels to be drawn, as set in QLabel's CSS, the mode needs to be changed to Qt::OpaqueMode. This however draws only around glyphs, not the whole area unfortunately. We need to manually clear the whole area first.

  • Image can be cleared via QPainter::fillRect:

    void QPainter::fillRect(int x, int y, int width, int height, const QColor &color)

    This is an overloaded function.

    Fills the rectangle beginning at (x, y) with the given width and height, using the given color.

    This function was introduced in Qt 4.5.

    but there is a caveat - all QPainter operations are blended with the underlying image. By default it takes the alpha channel of the drawing color into account. Thus, if you paint with (0,0,0,0) you get... no change. The blending operation is controlled by:

    void QPainter::setCompositionMode(QPainter::CompositionMode mode)

    Sets the composition mode to the given mode.

    Warning: Only a QPainter operating on a QImage fully supports all composition modes. The RasterOp modes are supported for X11 as described in compositionMode().

    See also compositionMode().

enum QPainter::CompositionMode

Defines the modes supported for digital image compositing. Composition modes are used to specify how the pixels in one image, the source, are merged with the pixel in another image, the destination. [...] When a composition mode is set it applies to all painting operator, pens, brushes, gradients and pixmap/image drawing.

Constant Value Description

QPainter::CompositionMode_SourceOver 0 This is the default mode. The alpha of the source is used to blend the pixel on top of the destination.

QPainter::CompositionMode_DestinationOver 1 The alpha of the destination is used to blend it on top of the source pixels. This mode is the inverse of CompositionMode_SourceOver.

QPainter::CompositionMode_Clear 2 The pixels in the destination are cleared (set to fully transparent) independent of the source.

QPainter::CompositionMode_Source 3 The output is the source pixel. (This means a basic copy operation and is identical to SourceOver when the source pixel is opaque).

QPainter::CompositionMode_Destination 4 The output is the destination pixel. This means that the blending has no effect. This mode is the inverse of CompositionMode_Source.

[...] (30 more modes...)

So, the default SourceOver needs to be replaced by Source before calling fillRect.

  • Clearing can be also done by QImage::fill. Much easier and no mess with draw modes!

    Unfortunately, either solution (QImage::fill or QPainter::fillRect) requires the specification of the background color explicitly. It cannot be just read from QLable's CSS.


P.S. I don't know how to blockquote a table :(

CygnusX1
  • 20,968
  • 5
  • 65
  • 109