Menu

#390 QwtGraphic legend icons render blank on Qt 6.5+ (initial null DirtyTransform collapses painter)

open
nobody
None
5
4 days ago
2026-05-30
Anonymous
No

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)

Symptom

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.

Minimal reproduction (public API only)

#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)

Root cause

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.

Suggested fix

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.)

Verification of the fix

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.


Discussion

  • Frank Bergmann

    Frank Bergmann - 5 days ago

    Thanks for this fix, this solves this issue for me!

     
  • Uwe Rathmann

    Uwe Rathmann - 4 days ago

    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 ?

     

Anonymous
Anonymous

Add attachments
Cancel