Scaling problems in Ubuntu using QPixmap and QPainter
-
Hi, I've created a drawing program that allow zooming of the image. For this I am using QPixmaps and QPainter. I have 2 functions for drawing. One is for drawing shapes to the QPixmap and the other outputs the QPixmap to the screen. These are:
@void Window::paintEvent(QPaintEvent * /* event */) //for painting to screen@
and
@void Window::drawShape(moveTypes t) {} //for drawing to the QPixmap@I have included the code for paintEvent at the end of this message. Note that paintEvent first paints the background, then the QPixmap followed by the shape 'cursor' outline. It also uses double buffering.
The problem I am having is that the program runs very fast when compiled for Windows XP (using Qt Creator/mingw32/g++). And this applies both without scaling and with the scale enlarged/reduced (using QPainter::scale -- and scaling up 8 times the length of the original image). However when I compile the program in Ubuntu (again using Qt Creator) I find that the drawing is acceptable without scaling, but becomes incredibly slow when scaling is being used (regardless of of the amount of scaling applied).
For example when compiled for Windows I can work with very large images (e.g. 5000 x 5000 pixels) with only a small reduction of performance and regardless of scale. However with Ubuntu drawing becomes very slow when scaling is applied. It seems strange also because the windows release works much better on Ubuntu using Wine than the native compiled program. Of course it runs slower than on windows, however there is no performance loss with scaling -- so overall it draws much faster. I have also tried using 'setAttribute(Qt::WA_PaintOnScreen);' with the Ubuntu version and it makes very little difference.
Is there something I can do to fix the code for Ubuntu? I'm used to Java and I'm also a C++/Qt beginner so please go gentle with me :-) Many thanks.
Michael
@void Window::paintEvent(QPaintEvent * /* event */)
{QPainter painter(this); painter.scale(zoomScale, zoomScale); //painter.setBackgroundMode(Qt::TransparentMode); painter.drawPixmap(0, 0, *grid); //painter.drawRect(QRect(0,0, imageWidth*zoomScale, imageHeight*zoomScale)); painter.drawPixmap(0, 0, *pixMap); //if (at_zero == false) { QBrush outline(QColor("black")); lineWidth = penWidth; if (RELATIVE_PEN_WIDTH == true) { if (lineWidth > (xRadius / 3)) lineWidth = xRadius/3; if (lineWidth > (yRadius / 3)) lineWidth = yRadius/3; if (lineWidth < 1) lineWidth = 1; } else { if (lineWidth > (xRadius * 2)) lineWidth = xRadius * 2; if (lineWidth > (yRadius * 2)) lineWidth = yRadius * 2; } //if (curTool != BRUSH_TOOL) return; QPen pen( outline, lineWidth * brushScale, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin ); painter.setPen(pen); int penOffset = 0; if (antiAliasing == false && zoomScale > 1) penOffset = 1; int drawX = (xPos - xRadius) * brushScale; int drawY = (yPos - yRadius) * brushScale; int transX = drawX + (xRadius * brushScale); int transY = drawY + (yRadius * brushScale); painter.translate(transX, transY); painter.rotate(rotateAngle); painter.translate(-transX, -transY); if (shape == ELLIPSE) painter.drawEllipse(drawX, drawY , (xRadius * 2) * brushScale , (yRadius * 2)*brushScale); else painter.drawRect(drawX, drawY , (xRadius * 2) * brushScale , (yRadius * 2)*brushScale); //} //}
};@
-
That's awesome thank you! It's now drawing 'almost' as fast as on windows and without the scaling penalty. Is it possible to hard code this setting into the application? Or is this something particular to linux?
I've got another question which is that the update when dragging the mouse is somewhat slower on Ubuntu (i.e. the program uses the Window::mouseMoveEvent ( QMouseEvent * event ){} to call the function for drawing shapes to the QPixmap/screen). It's not really all that much of a problem because I can simple increase the number of interpolation steps. And I also quite like the fact that the program "feels and runs" differently with different operating systems (since this is an artsy 'no limits' kind of application!). However ideally I would like to fix this (but only if it doesn't have negative side-effects -- I realise that there may be good reasons for this slower drag-update). I don't know if this is a setting specific to Ubuntu/Gnome or if this is more related to how Qt runs under Ubuntu/Gnome.
Michael
-
The default graphics system for Qt apps is determined at configure time for Qt itself. It sounds as it Ubuntu is using the X11 graphics system as default then. Some code paths are known to be slow in the X11 graphics system which is why I suggested you try the raster one - that is the default on Windows. [NB. I use Gentoo and use raster as the default]
It is possible to override the default graphics system on the command line as I showed you, or you can hard code it into the app using "QApplication::setGraphicsSystem":http://doc.qt.nokia.com/latest/qapplication.html#setGraphicsSystem. Note that you may want to surround this function call with platform-specific #ifdefs so that you override it for platforms only on platforms that need it.
As for the dragging question, can you provide a small compilable example that reproduces it please? I am not quite sure what you mean from your description.
-
I think you are right when you say Ubuntu uses X11. I will try your suggestion of using QApplication::setGraphicsSystem.
It would be hard to provide a small example of the dragging problem because it uses a lot of class scope variables. However I don't think it would help you much because it is only slow relative to windows. I mean if I had never run the program on windows I would not think there is a problem.
The update effect is seen when the user drags the mouse which triggers the mouseMoveEvent function which rotates the brush (if required) and then calls DrawShape(DRAG) function.
@void Window::mouseMoveEvent ( QMouseEvent * event ) {
//repaint(); if (positioning == false) { //if (curTool != BRUSH_TOOL) return; xAbsolute = (float)event->x(); yAbsolute = (float)event->y(); QPoint qP = getCursorLocation(event->pos()); xPos = qP.x() - xOffset; yPos = qP.y() - yOffset; if (drawing == false && justChangedBrushScale == true) { drawing = true; justChangedBrushScale = false; prevX = xPos; prevY = yPos; prevRotateAngle = rotateAngle; } bool dragDraw = true; if (CUTOFF_ZERO == true && at_zero == true) dragDraw = false; if (drawing == true && dragDraw == true) { //mouseMovedCount ++; if (moveRotate == ROTATE_CLOCKWISE) rotateShapeClockwise(); else if (moveRotate == ROTATE_ANTI_CLOCKWISE) rotateShapeAntiClockwise(); drawShape(DRAG); //Call function for updated QPixmap prevX = xPos; prevY = yPos; prevRotateAngle = rotateAngle; } } else { //positioning is true QPoint qP = getCursorLocation(event->pos()); xOffset = qP.x() - xPos ; yOffset = qP.y() - yPos ; } update(); updateCoordsMessage();
};@
-
Here is the code for updating the QPixmap. This is called from Window::mouseMoveEvent.
@void Window::drawShape(moveTypes t) {
lineWidth = penWidth; if (RELATIVE_PEN_WIDTH == true) { if (lineWidth > (xRadius / 3)) lineWidth = xRadius/3; if (lineWidth > (yRadius / 3)) lineWidth = yRadius/3; if (penWidth > 0 && lineWidth == 0) lineWidth = 1; } else { if (lineWidth > (xRadius * 2)) lineWidth = xRadius * 2; if (lineWidth > (yRadius * 2)) lineWidth = yRadius * 2; //if (penWidth > 0 && lineWidth == 0) lineWidth = 1; } QPainter painter(pixMap); painter.setBackgroundMode(Qt::TransparentMode); painter.scale(brushScale, brushScale); painter.setBackgroundMode(Qt::TransparentMode); painter.setRenderHint(QPainter::Antialiasing, antiAliasing); //QBrush outline(*penColour, Qt::CrossPattern); QBrush outline(*penColour); QPen pen( outline, lineWidth , Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin ); if (penWidth > 0) painter.setPen(pen); else painter.setPen(Qt::NoPen); //QBrush brush(*brushColour, Qt::CrossPattern); QBrush brush(*brushColour); painter.setBrush(brush); if (t == DRAG && interpolationSteps > 1) { //----------------------------- //create steps for interpolation float width = abs(xPos - prevX); float height = abs(yPos - prevY); float hyp = sqrt((width * width) + (height * height)); float distance = hyp / interpolationSteps; float tangent = width / height; float angle = atan(tangent); //double angleDegrees = angle * 57.29578; float newWidth = sin(angle) * distance; float newHeight = cos(angle) * distance; if (prevX > xPos) newWidth = -newWidth; if (prevY > yPos) newHeight = -newHeight; float curStepX = newWidth; float curStepY = newHeight; //angle steps float angleDistance = 0; float angleStep = 0; //qDebug() << "angleDistance " << angleDistance; if (ROTATE_INTERPOLATE == true) { if (moveRotate == ROTATE_CLOCKWISE) { if (rotateAngle >= prevRotateAngle) angleDistance = rotateAngle - prevRotateAngle; else angleDistance = 360 - prevRotateAngle +rotateAngle; angleStep = angleDistance / interpolationSteps; } else if (moveRotate == ROTATE_ANTI_CLOCKWISE) { if (prevRotateAngle >= rotateAngle) angleDistance = prevRotateAngle - rotateAngle ; else angleDistance = 360 -rotateAngle + prevRotateAngle ; angleStep = angleDistance / interpolationSteps; angleStep = -angleStep; } } float curAngleStep = angleStep; //----------------------------- for (int i = 0; i < interpolationSteps; i++) { int drawX = prevX + curStepX - xRadius; int drawY = prevY + curStepY - yRadius; int transX = drawX+ xRadius; int transY = drawY + yRadius; painter.translate(transX, transY); painter.rotate(prevRotateAngle + curAngleStep); painter.translate(-transX, -transY); //painter.setOpacity(0.3); painter.setOpacity(opacity); if (shape == ELLIPSE) painter.drawEllipse(drawX , drawY , xRadius *2, yRadius *2); else painter.drawRect(drawX , drawY , xRadius *2, yRadius *2); curStepX += newWidth; curStepY += newHeight; painter.translate(transX, transY); painter.rotate(-(prevRotateAngle + curAngleStep)); painter.translate(-transX, -transY); curAngleStep += angleStep; //painter.rotate(-rotateAngle); } } else { int drawX = xPos - xRadius; int drawY = yPos - yRadius; int transX = drawX + xRadius; int transY = drawY + yRadius; painter.translate(transX, transY); painter.rotate(rotateAngle); painter.translate(-transX, -transY); painter.setOpacity(opacity); if (shape == ELLIPSE) painter.drawEllipse(drawX , drawY , xRadius *2, yRadius *2); else painter.drawRect(drawX , drawY , xRadius *2, yRadius *2); }
}@
-
Sorry I probably didn't explain that well. I mean it is the 'frequency' of calls to drawShape that is slow in mouseMoveEvent. So as the user drags the mouse the QPixmap image is updated less frequently when using the program with Ubuntu as compared to Windows. The resulting effect is that the shapes are spaced further apart... and so more interpolation is required to correct this.
-
Ah OK I think I understand now. I am not entirely sure what the limiting factor is in the rate at which mouse mouse events are generated. It could well be tied into the resolution used by your X mouse driver - probably a setting in xorg.conf but depend on which version of X you have - one day they'll settle on a configuration method ;-). One thing to try would be to try changing the mouse driver resolution and seeing if that affects the frequency of your mouse move events. Let us know how you get on.