Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt: Drawing high DPI QPixmaps

I have written application that draws two smiling faces:

enter image description here

First one is painted directly on QWidget:

void DirectFace::paintEvent(QPaintEvent *ev)
{
    QPainter painter(this);
    paintFace(painter);
}

Second one is painted on a QPixmap, which in turn is blitted to widget:

void BufferedFace::paintEvent(QPaintEvent *ev)
{
    QPixmap buffer(width(), height());
    buffer.fill(Qt::transparent);
    QPainter painter(&buffer);
    paintFace(painter);

    QPainter p(this);
    p.drawPixmap(ev->rect(), buffer, ev->rect());
}

So far so good. I wanted to see how my app looks like on high resolution screen (I don't have one), so I set QT_SCALE_FACTOR=2 and run my app:

enter image description here

First face is sharp and crisp, whereas the seconf one is pixelated. That's because it is drawn to low resolution pixmap. So I have enlarged that QPixmap and set correct devicePixelRatio:

void BufferedFace::paintEvent(QPaintEvent *ev)
{
    qreal pixelRatio = qApp->devicePixelRatio();
    QPixmap buffer(width() * pixelRatio, height() * pixelRatio);
    buffer.setDevicePixelRatio(pixelRatio);
    buffer.fill(Qt::transparent);
    QPainter painter(&buffer);
    paintFace(painter);

    QPainter p(this);
    p.drawPixmap(ev->rect(), buffer, ev->rect());
}

Result:

enter image description here

Second face looks like it's drawn with correct resolution but then upscaled. Now I'm stuck. How to draw on QPixmap and then draw that QPixmap so it works correctly on Retina/HiDPI screens?

Whole application:

#include <QtWidgets>

class SmilingFace : public QWidget
{
    public:
    SmilingFace(QWidget *parent) : QWidget(parent) {};
    void paintFace(QPainter &painter);
};

class DirectFace : public SmilingFace
{
    public:
    DirectFace(QWidget *parent) : SmilingFace(parent) {}
    void paintEvent(QPaintEvent *ev) override;
};

class BufferedFace : public SmilingFace
{
    public:
    BufferedFace(QWidget *parent) : SmilingFace(parent) {}
    void paintEvent(QPaintEvent *ev) override;
};


void SmilingFace::paintFace(QPainter &painter)
{
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(Qt::lightGray));
    painter.drawEllipse(1, 1, width()-2, height()-2);

    painter.setPen(Qt::white);
    painter.setFont(QFont("", 32));
    painter.drawText(rect(), Qt::AlignHCenter, ";)");
}

void DirectFace::paintEvent(QPaintEvent *ev)
{
    QPainter painter(this);
    paintFace(painter);
}

void BufferedFace::paintEvent(QPaintEvent *ev)
{
    QPixmap buffer(width(), height());
    buffer.fill(Qt::transparent);
    QPainter painter(&buffer);
    paintFace(painter);

    QPainter p(this);
    p.drawPixmap(ev->rect(), buffer, ev->rect());
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    w.setWindowTitle("HiDPI");

    DirectFace d(&w);
    d.resize(48, 48);
    d.move(16, 16);

    BufferedFace i(&w);
    i.resize(48, 48);
    i.move(16 + 48 + 16, 16);

    w.show();

    return a.exec();
}
like image 212
el.pescado - нет войне Avatar asked Feb 02 '17 20:02

el.pescado - нет войне


Video Answer


1 Answers

If you want HighDPI rendering, you should also use QRectF and QPointF arguments for the QPainter functions. In your paintFace(...) function adjust the drawEllipse and drawText functions to use QRectF arguments and not QRect. This might help.

It is not a good idea to use qApp->devicePixelRatio(). There are people with mixed HighDPI and Non-HighDPI monitors. As you are within a widget paintEvent(...) function, you can use directly the QWidget member function devicePixelRatioF(), and not the qApp->devicePixelRatio(). This will handle proper rendering of the widget, even when the user moves the widget in between monitors with a mixed resolution.

You should also enable High DPI scaling in Qt via: QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

Here comes the full solution, which renders the smiling face perfectly on HighDPI and Non-HighDPI screen, even when the widget is moved in between. Tested with Qt 5.9.2

#include <QtWidgets>

class SmilingFace : public QWidget
{
public:
    SmilingFace(QWidget *parent) : QWidget(parent) {};
    void paintFace(QPainter &painter);
};

class DirectFace : public SmilingFace
{
public:
    DirectFace(QWidget *parent) : SmilingFace(parent) {}
    void paintEvent(QPaintEvent *ev) override;
};

class BufferedFace : public SmilingFace
{
public:
    BufferedFace(QWidget *parent) : SmilingFace(parent) {}
    void paintEvent(QPaintEvent *ev) override;
};


void SmilingFace::paintFace(QPainter &painter)
{
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(Qt::lightGray));
    painter.drawEllipse(QRectF(1, 1, width() - 2, height() - 2));

    painter.setPen(Qt::white);
    painter.setFont(QFont("", 32));
    painter.drawText(QRectF(0, 0, width(), height()), Qt::AlignHCenter, ";)");
}

void DirectFace::paintEvent(QPaintEvent *ev)
{
    QPainter painter(this);
    paintFace(painter);
}

void BufferedFace::paintEvent(QPaintEvent *ev)
{
    qreal dpr = devicePixelRatioF();
    QPixmap buffer(width() * dpr, height() * dpr);
    buffer.setDevicePixelRatio(dpr);
    buffer.fill(Qt::transparent);
    QPainter painter(&buffer);
    paintFace(painter);

    QPainter p(this);
    p.drawPixmap(ev->rect(), buffer, buffer.rect());
}

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QApplication a(argc, argv);

    QWidget w;
    w.setWindowTitle("HiDPI");

    DirectFace d(&w);
    d.resize(48, 48);
    d.move(16, 16);

    BufferedFace i(&w);
    i.resize(48, 48);
    i.move(16 + 48 + 16, 16);

    w.show();

    return a.exec();
}
like image 86
tiho Avatar answered Sep 20 '22 15:09

tiho