PDA

View Full Version : drawing contours



PierreA
13th November 2017, 20:26
I just got my program to display contours. (I've had it drawing them in PostScript for over a year and drawing them correctly for a few months, but just got it to draw them in Qt.) The relevant part of the paintEvent method is this:

for (i=0;i<doc.pl[plnum].contours.size();i++)
{
b3d=doc.pl[plnum].contours[i].approx3d(pixelScale());
path=QPainterPath();
for (k=0;k<b3d.size();k++)
{
beziseg=b3d[k];
if (k==0)
path.moveTo(worldToWindow(beziseg[0]));
path.cubicTo(worldToWindow(beziseg[1]),worldToWindow(beziseg[2]),worldToWindow(beziseg[3]));
}
if (!doc.pl[plnum].contours[i].isopen())
path.closeSubpath();
contourType=doc.pl[plnum].contourInterval.contourType(doc.pl[plnum].contours[i].getElevation());
painter.strokePath(path,contourPen[contourType>>8][contourType&31]);
}
There are hundreds of contours, many of which have hundreds of segments (which will be spiralarcs once I smooth them), and the whole thing takes about 200 or 300 ms to draw. It's common in CAD programs to keep a list of lines to draw and regenerate the list when the scale changes by a lot, but I don't see how that can work with QPainterPaths. There's a way to translate a QPainterPath, but not to scale or rotate it. Suggestions?

d_stranz
14th November 2017, 01:52
Suggestions?

Why are you regenerating the QPainterPath for every paintEvent()? (Your comment implies that's what you're doing). Store the path in a member variable and simply use QPainter::drawPath() in the paintEvent() to redraw it. Most of your overhead is likely due to recreating the entire path with every paintEvent(). You should re-create the path only when the contours change.

As for scaling, can you not apply the scaling transformation to the QPainter itself? Apply the scale transform, draw the path, reset the transform?

QPainter also has the world-to-screen transformations built it, so could probably do what you seem to be doing manually.

PierreA
14th November 2017, 07:29
Would the world-to-screen transformation handle tilting the view? I don't have a way to tilt the view yet, but I'm thinking about it.

I have to regenerate the bezier3d objects (and therefore the QPainterPaths) when the scale changes (including when the window is resized). How much scale change should need to happen before I regenerate them? Right now the scale argument to approx3d makes no difference, since the contours are made of straight lines, but once I smooth them, it will make a difference.

If I scale the QPainter, what happens to line widths?

ETA: How can I find out how much time the program is spending in approx3d and in drawing paths? Callgrind (a Valgrind tool) works well on the console program, but not as well on Qt GUI programs.

d_stranz
14th November 2017, 19:11
Would the world-to-screen transformation handle tilting the view?

Unfortunately, QTransform and QPainter are 2D coordinates only. If you are doing 3D, then you are stuck with transforming coordinates yourself.


If I scale the QPainter, what happens to line widths?

If you use a cosmetic QPen, then line widths stay constant. Non-cosmetic pens will scale the line width.


How much scale change should need to happen before I regenerate them?

When I have a case of expensive redraws, I set a short-duration (maybe 250 ms) QTimer in the resizeEvent() and set a Boolean flag to determine what happens in the paintEvent() (or instead of a Boolean, check QTimer::isRunning()). When the timer times out, it resets the flag to trigger a redraw. Each time the resize event is called, it starts the timer again, so as long as the user is resizing the window, the contents won't be repainted and the UI stays "alive". Once the user stops moving the window, the timer times out, the flag is reset and the paintEvent will redraw the contents. You can do variations on this - redraw every "n" resize events, or when the scale has changed by "x", or some combination.

If it is necessary for the user to see the resize / rescale in action, one possibility instead of a complete redraw is to capture the window to a QImage at the beginning, then call QPainter::drawImage() with scaling into the resized window. When resizing or scaling is done, remove the bitmap and redraw the actual contents to the new size or scale.

In any case, if I am using a QPainterPath, I re-create the path only when absolutely necessary and store the result for the paint events.

PierreA
24th December 2017, 10:01
I used QTime to find out how much time it's spending rendering the objects. Using a cache to cache the rendered bezier3d objects reduced the total time for the paintEvent method from 380 to 190 ms. Of this, 82 ms is spent drawing the bezier3ds as QPainterPaths and 47 ms drawing the QPainterPaths to the window. (The rest is probably mostly drawing the TIN. The average paintEvent time is 192 ms this time; there's a lot of standard deviation, so this difference isn't significant.) I'm going to leave it like this for now.

It takes a lot longer to paint smooth contours, because they have more splines. It also takes longer to compute them than to compute rough contours.

Cruz
25th December 2017, 13:07
If you have so many little spline pieces, maybe you could replace the cubicTo() call with a lineTo() call without a noticeable visual difference?

PierreA
25th December 2017, 20:35
It's very noticeable when magnified. Should I check how far a spline is from straight, in window coordinates, and substitute a line if it isn't?

Cruz
25th December 2017, 22:00
I am not sure. The code could become cumbersome for not much speedup. I think the most promising next step would be to cache the QPainterPath so that you only have to paint it and not regenerate it. You could use an OpenGL environment to do your drawing so that you can cache everything and only need to modify the painter or view transformation. There is a QOpenGLWidget that might come in handy. Then there is the QGLViewer library, just google it.