QGraphicsView render issues when scaling
-
I have a simple QGraphicsScene and QGraphicsView setup. So far I have never scaled or resized any of the two and everything was working perfectly.
I draw a slider using the normal QPainter API and the result looks just fine: !http://paste.ugfx.org/sores/7505d64a54e0/0af95a019669.jpg(Slider)!Now I scale the scene and resize the view so it can still display the entire scene at once. I do this by using this code:
@
int screenWidth = 640;
int screenHeight = 480;_scaleFactor = 1.0; // This is a qreal setFixedSize(screenWidth*_scaleFactor, screenHeight*_scaleFactor); scale(_scaleFactor, _scaleFactor); fitInView(0, 0, screenWidth, screenHeight);
@
As you can see I am not really scaling it as the scale factor is 1.0. The dimensions of everything is still correct but there are now rendering issues happening:
!http://paste.ugfx.org/sores/7505d64a54e0/291bca6a907a.jpg(slider scaled)!
Now the green rectangle is shifted downwards by one pixel.For drawing the green rectangle I draw it using the following code:
@
QRectF greenRect = rect();
greenRect .setX(borderPen.width());
greenRect .setY(borderPen.width());
greenRect .setHeight(rect().height() - borderPen.width());
greenRect .setWidth(knobPos);
@I don't really know how to address this issue. What causes this issue? I assume it's some float-to-integer rasterization. But I use QRectF everywhere.
I experience similar issues with other items that I draw in the scene. Thins are just off by 1px in random cases and sometimes 1px width lines are missing at all.
-
Don't know if this is really the solution.
But you can try to use qreal instead of int with your screen variables@qreal screenWidth = 640;
qreal screenHeight = 480;@Using integer values will cause that in that line :
@setFixedSize(screenWidth*_scaleFactor, screenHeight*_scaleFactor);@
values will be casted to integer values. -
Can you post your painting code?
-
@euchkatzl: I casted the values as you suggested but that didn't make the issue go away. And from my understanding they should if anything be casted to a qreal anyway as this is the type which provides more accuracy than the integer.
@asperamanca: Here's my full paint() routine for the slider that you can see in the images above: http://paste.ugfx.org/show/3eefb77194
-
You should check the member functions (this->rect() in particular). This probably returns QRect and not QRectF. I can't tell by looking at the code.
@
// Create the active area rectangle
QRectF activeRect = rect();
activeRect.setX(rectanglePen.width());
activeRect.setY(rectanglePen.width());
activeRect.setHeight(rect().height() - rectanglePen.width());
activeRect.setWidth(knobPos);
@Integer rounding (or lack thereof) will cause this kind of problem.
-
Thank you very much for your suggestion. My UGfxSlider is derived from QGraphicsRectItem. QGraphicsRectItem::rect() returns a QRectF.
I have checked the other stuff as well and everything looks fine from that point of view.
I don't understand why this only happens after setting the scale factor etc. as shown above. The default scale factor is 1.0 and when I don't change it it is fine. But when I manually set it to 1.0 it shows the problems.
-
Could you try to use use widthF of QPen instead of width.
-
Thank you very much for your suggestion. I have modified the code to use QPen::widthF() but the result is exactly the same.
@
QRectF activeRect = rect();
activeRect.setX(rectanglePen.widthF());
activeRect.setY(rectanglePen.widthF());
activeRect.setHeight(rect().height() - rectanglePen.widthF());
activeRect.setWidth((qreal)knobPos);
@
Any other ideas? :/ -
Try this in your paint and look if there are still problems. That code works for me. It a simplified version of yours without text an knob.
When it works as aspected you could use it to fix your code. I think there a several problems with pen width handling.
@painter->setRenderHint(QPainter::Antialiasing,true);
painter->save();
QPen rectanglePen;
rectanglePen.setColor(Qt::blue);
rectanglePen.setWidthF(1.0);
rectanglePen.setJoinStyle(Qt::MiterJoin);
rectanglePen.setCapStyle(Qt::FlatCap);QBrush rectangleBrush;
rectangleBrush.setStyle(Qt::SolidPattern);
rectangleBrush.setColor(Qt::red);painter->setPen(QPen(Qt::blue,1.0));
painter->setBrush(rectangleBrush);
painter->drawRect(rect());painter->restore();
QRectF activeRect = rect().adjusted(rectanglePen.widthF() / 2.0,
rectanglePen.widthF() / 2.0,
-rectanglePen.widthF() / 2.0,
-rectanglePen.widthF() / 2.0);
activeRect.setWidth(30);painter->fillRect(activeRect, Qt::green);@
-
I took your code and replaced my paint() routine with it. Sadly I still observe the same issues.
I'm really lost... Here is the full code of my QGraphicsView and QGraphicsScene derived classes:
mygraphicsview.h: http://paste.ugfx.org/show/e98773b84a
mygraphicsview.cpp: http://paste.ugfx.org/show/14e15a6d66
mygraphicsscene.h: http://paste.ugfx.org/show/450f49bc5e
mygraphicsscene.cpp: http://paste.ugfx.org/show/d5d4a8e53bNow that I am pasting this, might it be related to _MyGraphicsScene::drawBackground() _?
Edit: I just removed my MyGraphicsScene::drawBackground() implementation and used the default one from QGraphicsScene and the issues remain.
-
I noticed in your code you have this:
@
// Actually modify the possition
float pos = selectedWindows.first()->y() + selectedWindows.first()->rect().height();
for (int i = 1; i < selectedWindows.count(); i++) {
pos += spaceBetweenItems;
selectedWindows.at(i)->setY((int)(pos + 0.5));
pos += selectedWindows.at(i)->rect().height();
}
@This would set the Y position of each window rounded to the nearest integer value. Your screen shot shows the green rect offset from the frame by 1 pixel in the Y axis.
Does each object 'UGfxSlider' contain its own drawing code (dependent, i.e. upper left = 0,0) or are you drawing the position based on its calculated position (drawing independent of each item)?
-
That code is only executed when the user clicks the alignment (in this case vertical distribution) button. It is not relevant to my problem as I get the issues by just moving the item around in the scene without using any of the alignment or distribution function in MyGraphicsScene.
Also, please note that those functions work just fine as long as I don't execute the MyGraphicsView::updateScale(). I get the artefacts as soon as I execute that code (although my _scaleFactor is set to 1.0).
Each slider does contain it's own drawing code. I simply implemented the QGraphicsRectItem::paint() routine. You can see my implementation at line 133 in this file: http://paste.ugfx.org/show/ded30acd54
Note that I have this "off by one pixel" issue with other QGraphicsRectItem based classes too.
Also, the "off by one pixel" changes by moving the item across the scene. Sometimes it's right and sometimes it shows the errror. So it's definitely a rounding issue.
-
Ok. I figured it out.
I ran some tests on a project I have and saw the exact same thing you described using a subclass of QGraphicsItem. I didn't have anything other then two rect()'s drawn.
I found that by adding the following in the constructor of the QGraphicsView it solved this problem:
@
this->setRenderHints(QPainter::Antialiasing);
@At some point in the drawing process only one pixel of the display is lit up (this is where the truncating occurs) . Antialiasing is a trick to fool your eye so that the apparent position appears somewhere between pixels.
-
I did see that the problem becomes less visible when enabling Anti-Aliasing when I tested your code. However, as you said you are still missing one of the pixel lines sometimes. This is way less visible but in my case I can't / don't want to use anti-aliasing.
The application I am writing is a drag'n'drop GUI designer for an embedded GUI library that targets VERY low resolution displays. If you have a resolution of just 320 x 240 pixels and a 150 x 30 pixels slider and you turn on anti-aliasing you see a big difference between the QGraphicsView rendering and the actual result (after generating the resulting code).
The preview that the user sees in the QGraphicsView should be as close to the actual result afterwards as possible.The issue is clearly somewhere in the scaling and I hope that it can be fixed or at least workaround-ed.
Thank you very much for your endurance. It is appreciated a lot.
-
The way I did this was to draw to a QPixmap. I draw the pixmap and create or update the associated QGraphicsPixmapItem each time there is a change. I didn't even see your problem until I tested it using the QGraphicsItem.
If you try the QPixmap method you would have to re-draw the QGraphicsPixmapItems when the contents or the scale changes. I don't know if this a performance hit or not. It is easier to copy the contents of a bitmap then to draw directly so it should be better from that point of view at least.
Maybe separating the QGraphicsItem from the QGraphicsView might be the answer. You can use setFlag(QGraphicsItem::ItemIgnoresTransformations) to do this. You will have to manage the size and position of the control yourself but at least you have some control.
I don't know. I am beginning to think this is more internal to QGraphicsView/QGraphicsScene then anything else.
-
I have one more idea. I didn't try it but it might be worth investigating.
What if you limit your scale values to those that only increase the size by 1 pixel for each QGraphicItem that you have?
For example, if the QGraphicItem height is 100 pixels then make sure your scale value is restricted to 1.000, 1.0100, 1.0200 and so on. Part of the problem may be that scale values that are between even pixel sizes cause something, somewhere, to shift position.
You can do this independently for both axis. This assumes your QGraphicItems are not mixed sizes.
-