Qwt version: 6.2.0 (code path unchanged in 6.3.0 — almost certainly also affected)
Qt versions: broken on Qt 6.5+ (reproduced on Qt 6.11.1); works on Qt 5.x and Qt 6.4.2
Platforms: Linux (X11/offscreen), Windows 11 — Qt-version dependent, not platform dependent
Component: QwtGraphic command record/replay (src/qwt_graphic.cpp)
Every icon produced through QwtGraphic comes out completely blank when built against
newer Qt6. The most visible effect is that plot legends lose the colored preview line/
symbol next to each curve name (QwtPlotCurve::legendIcon() returns an empty-looking
icon). QwtGraphic::boundingRect() is still correct, but QwtGraphic::toPixmap() /
render() paint nothing.
Originally reported downstream as OpenModelica issue #15626.
#include <QGuiApplication>
#include <QPixmap>
#include <QImage>
#include <QPainter>
#include <QPen>
#include <qwt_graphic.h>
#include <cstdio>
int main(int argc, char** argv)
{
QGuiApplication app(argc, argv);
QwtGraphic graphic; // as in QwtPlotCurve::legendIcon()
graphic.setDefaultSize(QSizeF(30, 30));
graphic.setRenderHint(QwtGraphic::RenderPensUnscaled, true);
{
QPainter p(&graphic);
QPen pen(Qt::blue); pen.setWidthF(1.0); pen.setCapStyle(Qt::FlatCap);
p.setPen(pen);
p.drawLine(QLineF(0.0, 15.0, 30.0, 15.0)); // a horizontal "preview line"
p.end();
}
const QImage img = graphic.toPixmap().toImage();
int n = 0;
for (int y = 0; y < img.height(); ++y)
for (int x = 0; x < img.width(); ++x)
if (qAlpha(img.pixel(x, y)) > 0) ++n;
printf("Qt %s -> non-transparent pixels = %d (expected 30)\n", QT_VERSION_STR, n);
return 0;
}
Output:
Qt 6.4.2 -> non-transparent pixels = 30 (correct)
Qt 6.11.1 -> non-transparent pixels = 0 (bug)
QwtGraphic records painter commands through the custom QPaintEngine of
QwtNullPaintDevice and replays them in qwtExecCommand().
On Qt 6.5+ the initial state change delivered to a custom paint engine has the
QPaintEngine::DirtyTransform flag set, but state.transform() returns a
default-constructed, null QTransform (all elements 0, non-invertible) instead of
the identity. QwtPainterCommand stores it (qwt_painter_command.cpp:95-96), and on
replay qwtExecCommand() applies it (qwt_graphic.cpp):
if ( data->flags & QPaintEngine::DirtyTransform )
painter->setTransform( data->transform * transform ); // data->transform is the null matrix
null * transform is still a singular (zero) matrix, so the painter's transform
collapses to zero and every subsequent command paints nothing. Confirmed by tracing:
the painter matrix is identity at the start of renderGraphic() and becomes
m11=m12=m21=m22=dx=dy=0 immediately after the first (State) command on Qt 6.11, whereas
on Qt 6.4 it stays identity.
This also explains why RenderPensUnscaled icons (legend lines) are hit hardest: with a
zeroed, type-TxScale matrix, painter->transform().isScaling() returns true, so the
doMap branch runs and tr.map(path) maps the geometry to the origin as well.
Ignore a degenerate (non-invertible) captured transform on replay — it can never produce
visible output anyway, so legitimate scaled rendering is unaffected:
if ( data->flags & QPaintEngine::DirtyTransform )
{
if ( data->transform.isInvertible() )
painter->setTransform( data->transform * transform );
else
painter->setTransform( transform );
}
(Equivalently it could be filtered at record time in
QwtPainterCommand(const QPaintEngineState&) by not storing a non-invertible transform.)
With the guard above, the reproducer prints 30 on Qt 6.11.1 and remains 30 on
Qt 6.4.2. Genuinely scaled 2D graphics (line + rectangle rendered into 60×60 and 45×45)
continue to scale correctly.
Anonymous
Thanks for this fix, this solves this issue for me!
I tried the provided example on my system ( Linux/X11, Qt 6.11.0 ) and did not run into the error. Actually I do not have any QPaintEngine::DirtyTransform state being set when recording the painter commands in QwtGraphic.
Having QPaintEngine::DirtyTransform with a zeroed transformation matrix looks more than questionable to me as the default constructor of QTransform is the identity ( = having some values set to 1 ). So my first guess is that both - flags and matrix - might be random values related to some sort of memory corruption that happened before.
To have better a understanding of what is going on on your system: there are only a few places where the QPaintEngine::DirtyTransform flag is set in qtbase/src/gui/painting. Could you please try to find if one of them is reached in your environment ?