Search code examples
c++qtqpainter

QT 5.7 QPainter line aligment


I am working with QT 5.7 and C++. At the moment I try to get used to draw my own widgets with the QPainter class. But I noticed a problem I couldn't solve. I try to draw a border line extactly at the widget border but if I do so:

void MyWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter;
    painter.begin(this);
    painter.setBrush(Qt::cyan);

    QBrush brush(Qt::black);
    QPen pen(brush, 2);

    painter.setPen(pen);
    painter.drawRect(0, 0, size().width() - 1, size().height() - 1);
    painter.end();
}

The Line is at the bottom and right site bigger than the others:
problem

And before someone is telling me I have to remove the two -1 expressions, you should know if I do this and also set the pen width to 1 there is no line anymore at the bottom and right side.
problem

I think this artifact is caused by the "line aligment". QT tries to tint the the pixels near the logical lines defined by the rectangle but actually because finally all have to be in pixels it has to decide. If I am right, why there is no method to set the line aligment of the pen like in GDI+? And how I can solve this?


Solution

  • Everything depends on whether you want the entire pen's width to be visible or not. By drawing the rectangle starting at 0,0, you're only showing half of the pen's width, and that makes things unnecessarily complicated - never mind that the line appears too thin. In Qt, the non-cosmetic pen is always drawn aligned to the middle of the line. Qt doesn't let you change it: you can change the drawn geometry instead.

    To get it right for odd line sizes, you must give rectangle's coordinates as floating point values, and they must be fall in the middle of the line. So, e.g. if the pen is 3.0 units wide, the rectangle's geometry will be (1.5, 1.5, width()-3.0, width()-3.0).

    Here's a complete example:

    // https://github.com/KubaO/stackoverflown/tree/master/questions/widget-pen-wide-38019846
    #include <QtWidgets>
    
    class Widget : public QWidget {
       Q_OBJECT
       Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth)
       qreal m_penWidth = 1.0;
    protected:
       void paintEvent(QPaintEvent *) override {
          QPainter p{this};
          p.setPen({Qt::black, m_penWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
          p.setBrush(Qt::cyan);
          qreal d = m_penWidth/2.0;
          p.drawRect(QRectF{d, d, width()-m_penWidth, height()-m_penWidth});
       }
    public:
       explicit Widget(QWidget * parent = 0) : QWidget{parent} { }
       qreal penWidth() const { return m_penWidth; }
       void setPenWidth(qreal width) {
          if (width == m_penWidth) return;
          m_penWidth = width;
          update();
       }
       QSize sizeHint() const override { return {100, 100}; }
    };
    
    int main(int argc, char ** argv) {
       QApplication app{argc, argv};
       QWidget top;
       QVBoxLayout layout{&top};
       Widget widget;
       QSlider slider{Qt::Horizontal};
       layout.addWidget(&widget);
       layout.addWidget(&slider);
    
       slider.setMinimum(100);
       slider.setMaximum(1000);
       QObject::connect(&slider, &QSlider::valueChanged, [&](int val){
          widget.setPenWidth(val/100.0);
       });
    
       top.show();
       return app.exec();
    }
    
    #include "main.moc"