Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficiently painting physically accurate ruler in Qt

Tags:

c++

qt

I have a ruler class (called Graduation) that uses orientation to calculate where lines should be drawn. So like this:

enter image description here

When the orientation is set to Qt::Horizontal I do line_xpos = precendent_line_xpos + number. Then if the orientation is Qt::Vertical I add to the y position.

I have some code below which is an example of how I do this with if...else and ?: statements:

/* std::vector<QLineF> m_lines; */

void Graduation::resizeEvent(QResizeEvent *event)
{   
    qreal newLength = (m_orientation == Qt::Horizontal)
        ? event->size().width()
        : event->size().height();
    qreal oldLength = (m_orientation == Qt::Horizontal)
        ? event->oldSize().width()
        : event->oldSize().height();

    if(newLength != oldLength) {
        if(newLength < oldLength) { /* Delete lines */
            int count = m_lines.size();
            if(count == 0)
                return;

            if(m_orientation == Qt::Horizontal) {
                while(m_lines[count-1].x1() > newLength) {
                    --count;
                }
            } else {
                while(m_lines[count-1].y1() > newLength) {
                    --count;
                }
            }

            m_lines.erase(
                m_lines.begin()+count,
                m_lines.begin()+m_lines.size()
            );
        } else
          /* ... Append lines ... */
    }
}

Here is the paintEvent to show how the lines are drawn:

void Graduation::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    QPainter painter(this);
    painter.setPen(QPen(Qt::black, 0.5));

    for(unsigned int i = 0; i < m_lines.size(); ++i)
        painter.drawLine(m_lines[i]);
}

I want to know if I can keep from having those conditions checked possibly thousands of times. I'm interested in what code pattern you can use to avoid that (although it could be better to calculate lines position only one time and draw only what is visible).

like image 451
Eliot545 Avatar asked Nov 09 '14 13:11

Eliot545


1 Answers

OK, after you posted that image, it does look like you can go for a much simpler and effective solution.

Your lines form a repeatable pattern and as such, it would be a waste to draw the whole thing, manage lines manually and whatnot. You can create a small pixmap and draw only one segment of the repeating pattern like this:

pattern

Then depending on the orientation you can leave it as it is or rotate/flip it. Then you can use QPainter to fill your Graduation widget with it a Qt::TexturePattern using QBrush::setTexture(pixmap) and it will quickly and efficiently fill the entire widget length/height with the pattern, regardless of how much it is.

If you want to change the style or spacing of the ruler, all you have to do is redraw the cached pattern and use the same process to update the rulers.

And last but not least, you can use the layouting Qt provides to snap your rulers to the right place so they are managed 100% automatically, making your currently used logic redundant.

OK, here is a little more help, plus this will show you you don't have to have each and every line to draw lines, the code is simple enough and will generate the pattern segment for a given DPI:

QPixmap drawCMPattern(qreal dpi) {
    qreal dpcm = dpi / 2.54;
    QPixmap rulerCache(dpcm, dpcm / 2);
    rulerCache.fill();
    qreal dpmm = dpcm / 10;
    QPainter p(&rulerCache);
    p.setRenderHint(QPainter::HighQualityAntialiasing);
    qreal lineWidth = dpmm / 5;
    p.setPen(QPen(Qt::black, lineWidth));
    qreal xpos = lineWidth / 2;
    for (int i = 0; i < 10; ++i) {
        if (i == 0) p.drawLine(QLineF(xpos, 0, xpos, rulerCache.height()));
        else if (i == 5) p.drawLine(QLineF(xpos, 0, xpos, rulerCache.height() / 2));
        else p.drawLine(QLineF(xpos, 0, xpos, rulerCache.height() / 4));
        xpos += dpmm;
    }
    return rulerCache;
}

You may notice the lines are somewhat blurry on a typical low DPI desktop monitor, but that's the price you pay for being "physically accurate" - you are either metric perfect or pixel perfect, you can't have both unless your DPI match metric units perfectly, most lines are bound to happen between pixels. On a high DPI mobile device it will not look as bad.

Alternatively, you can get crisp lines if you omit the HighQualityAntialiasing, but then the lines will not be at equal distance. Still, this may prove good for you, since it is apparently good enough for commercial software like Photoshop:

PS ruler

PS is however tricky and has less lines in 1 cm, so the difference is not as striking as it is when having a line for each mm. Here is how that code result looks on my 108.79 DPI desktop:

this code

like image 143
dtech Avatar answered Nov 08 '22 16:11

dtech