Antialiasing not working in QGraphicsView - qt

I re-implemented QGraphicsView to have the scene zoomed with a mouse wheel event. The scene contains several QGraphicsPixmapItem. The wheel event calls QGraphicsView::scale(qreal sx, qreal sy)
Everything works perfectly but the rendering. As I zoom out (the scene gets smaller), aliasing appears. I tried setting the render hints as following in the re-implemented QGraphicsView constructor:
ImageViewer::ImageViewer(QWidget * parent) :
QGraphicsView(parent)
{
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing);
}
I still see these artifacts. How can I get rid of this ?

Please see my comments for this question.
Basically you have to call setTransformationMode(Qt::SmoothTransformation) on the QGraphicsPixmapItems you want anti-aliasing to apply to.
Calling setRenderHints on the view did not work for me, either.

The render hints are only applied if it is set bevor the painter is used. Here a snipped:
QGraphicsPixmapItem *
drawGraphicsPixmapItem(const QRectF &rect)
{
auto pixmap = new QPixmap(rect.size().toSize());
pixmap->fill("lightGrey");
auto painter = new QPainter(pixmap);
// set render hints bevor drawing with painter
painter->setRenderHints(QPainter::Antialiasing);
QPen pen;
pen.setColor("black");
pen.setWidth(3);
painter->setPen(pen);
QRectF rectT = rect;
rectT.adjust(pen.widthF()/2,pen.widthF()/2,-pen.widthF()/2,-pen.widthF()/2);
QPainterPath circlePath;
circlePath.addEllipse(rectT);
circlePath.closeSubpath();
painter->fillPath(circlePath,QBrush("green"));
painter->drawPath(circlePath);
auto pixmapItem = new QGraphicsPixmapItem(*pixmap);
pixmapItem->setCacheMode(
QGraphicsItem::CacheMode::DeviceCoordinateCache,
pixmap->size() );
return pixmapItem;
}

Related

set QGraphicsScene dynamically in QGraphicsView with OpenGL Viewport

I have a problem, i'm developing a graphical program under Windows, there are few QGraphicsScene and one QGraphicsView that it is possible to change the Scenes in runtime with a lot of graphics items, the problem is when I use Qwidget viewport everything works but when I switch to OpenGL viewport when I change the scene the content of previous scene still appear on the QGraphicsView and the contents of new Scene appear too.
what is the problem ? is it the changing Scenes method best solution or should I change the method ?
here is the code to setup View
m_viewPort = new QOpenGLWidget (this);
QSurfaceFormat format;
format.setProfile(QSurfaceFormat::CoreProfile);
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setSamples(4);
m_viewPort->setFormat(format);
ui->gV->setViewport(m_viewPort);
ui->gV->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
//ui->gV->setCacheMode(QGraphicsView::CacheBackground);
ui->gV->setRenderHints(QPainter::Antialiasing| QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform| QPainter::TextAntialiasing);
ui->gV->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->gV->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
ui->gV->setTransformationAnchor(QGraphicsView::NoAnchor);
ui->gV->setAutoFillBackground(false);
ui->gV->setAttribute(Qt::WA_OpaquePaintEvent, true);
ui->gV->setAttribute(Qt::WA_NoSystemBackground, true);
resize(boardBaseSize);
here is code to set the new scene to the view
void GlScreenBoard::setShowScene(QGraphicsScene *scene, QString programName)
{
scene->setSceneRect(boardSceneRectBase);
ui->gV->setScene(scene);
}
another problem is when I set Graphics view CacheMode to CacheBackground the OpenGL viewport disables !! and the painter in QGraphicsScene returns to Raster !

Tiled image in QGraphicsView foreground

I'm working on a Qt application made with a main QGraphicsView.
This view can show and switch between differents QgraphicsScenes. This application needs to always have an overlay in front of each scenes, so the best way to do this overlay is by setForegroundBrush() method of QGraphicsView.
But my overlay is a tiled-image, where I could edit the opacity and the scale of the source image.
Here's the code written in my QGraphicsView class constructor :
QString imgPath("path/to/image.png");
QPixmap map(imgPath);
QPainter painter(this);
QRectF zone(0,0,map.width(),map.height());
painter.drawPixmap(zone,map,zone);
QBrush brush = painter.brush();
brush.setStyle(Qt::TexturePattern);
setForegroundBrush(brush);
But doesn't work, nothing is shown.
I tested a simple QBrush with a QPixmap and works fine, but I need to use QPainter to be able to edit the opacity of my image.
Finally I think the easiest way to have a tiled image in QGraphicsView foreground is by reimplmenting the drawForeground(QPainter *painter, const QRectF &rect).
void Frontend::drawForeground(QPainter *painter, const QRectF &rect){
float alpha = 0.15;
float scale = 2;
QString imgPath("path/to/image.png");
QPixmap img(imgPath);
painter->scale(scale,scale);
painter->setOpacity(alpha);
painter->drawTiledPixmap(rect,img);
}
You cannot paint on the widget outside of its paintEvent method. Perhaps you wanted to have the painter work on the pixmap (painter(&map)) instead of the widget (painter(this))?
You could also add an overlay by:
Painting it in the reimplemented paintEvent of your derived scene, making sure that you paint not on the scene, but on its viewport(). There are convenience methods that are called by the view's paintEvent, such as drawBackground and drawForeground.
Painting it in a generic QWidget overlay.
I have several answers that demonstrate how to get overlays over widgets in generaly, and also on scene views.

How can I show/hide background drawing on QGraphicsScene or QGraphicsView?

I would like to have certain things drawn on QGraphicsScene, but not be QGraphicsItem (it would interfere with the processing of the QGraphicsItem collection).
Example: a scene bounding rectangle, a grid
I am overriding the drawBackground(QPainter *painter, const QRectF &rect) for that purpose. (I should subclass the scene... )
void MyView::showHideBounds()
{
m_showBackgroundBounds = !m_showBackgroundBounds;
// can't triger an update ???
update(); // neither does anything
viewport()->update();
}
void MyView::drawBackground(QPainter *painter, const QRectF &rect)
{
QPen pen;
if(m_showBackgroundBounds)
pen = QPen(QColor(0, 0, 0), 10, Qt::PenStyle(Qt::SolidLine));
else
pen = QPen(QColor(255, 255, 255), 10, Qt::PenStyle(Qt::SolidLine));
painter->setPen(pen);
painter->drawRect(QRect(QPoint(-scene()->sceneRect().size().toSize().width()/2,
-scene()->sceneRect().size().toSize().height()/2),
scene()->sceneRect().size().toSize()));
}
I would like the option to show/hide either the bounding rectangle or the grid.
The only thing I can think of is paint over them with the color of the background brush ? Is there any other option ?
As I have written it above, it works - except I need user action on items (or a zoom or some other scene changing action) to trigger refresh, or call an update... (the function showHideBounds doesn't - not sure how to make it force a refresh)
I would call the drawBackground from the showHideBounds function - but I don't know how to get the painter
[Also, the drawBackground seems to be drawn automatically... how can I give it the rect argument it needs ? (it seems if I draw the rect it does draw the scene rectangle but I only see the right and bottom edges)]
In order to redraw a particular section of scene, you can call
QGraphicsScene->invalidate(rect_to_redraw, Backgroundlayer)
Note that if drawBackground(*painter, rect) paints over area outside rect, it will not update automatically. In that case invalidate has to be called with appropriate rect parameters.

Using QGraphicsScene ItemAt() to detect QGraphicsLineItem

I have a QGraphicsScene which stores QGraphicsLinesItems and QGraphicsRectItems.
I am using the QGraphicsScene method to itemsAt() and I pass through x and y co ordiantes to return the QGraphicsItem and I use the following:
QPointF getItemPos= this->mapToScene(this->mapFromGlobal(QCursor::pos()));
QGraphicsItem *itm = scene->itemAt(getItemPos.x(),getItemPos.y());
QGraphicsLineItem *lineItm;
QGraphicsRectItem *rectItm;
if((lineItm = dynamic_cast<QGraphicsLineItem*>(itm))){
// do stuff with as_pnedge
qDebug("Line");
}else if((rectItm = dynamic_cast<QGraphicsRectItem*>(itm))){
// do stuff with as_pnitem
qDebug("Rect");
}
else
{
qDebug("Select Item");
}
The issue I am having is that QGraphicsRectItem is returned fine and can be detected. But no matter where I click around the QGraphicsLineItems it can never detect and return the item. Any assistance would be very much appreciated.
If your line uses a cosmetic pen (width of zero), it means that the shape() method will return a zero width QPainterPath (source code, search for "qt_graphicsItem_shapeFromPath").
You will have to derive from QGraphicsLineItem and reimplement shape() to clamp the minimum pen width for QPainterPathStroker to something reasonable.

Moving a QPixmap to the corner of a QGraphicsScene

I have a QGraphicsView and a QGraphicsScene connected like this:
graphicsScene->setSceneRect(this->graphicsView->rect());
graphicsView->setScene(this->Scene);
Then I load an image and add it to the scene:
QPixmap pixmap;
pixmap.load(fileName);
pixmap = pixmap.scaled(this->graphicsView->size());
QGraphicsPixmapItem* item = this->Scene->addPixmap(pixmap);
Now, as described in the documentation, the image corner is at (0,0), which is not the corner of the graphicsScene. I know I can position the resulting pixmap by doing:
item->setPos(this->Scene->sceneRect().x(), this->Scene->sceneRect().y());
However, I can't seem to make sense of the coordinates of the rect's of the scene or the view. Can anyone explain how I would move the pixmap to the corner of the scene/view?
Thanks,
David
EDIT: Here is the full form constructor. The QGraphicsView was created in Qt Designer and is inside of a GridLayout:
Form::Form(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
QGraphicsScene* scene = new QGraphicsScene;
scene->setSceneRect(this->graphicsView->rect());
this->graphicsView->setScene(scene);
QPixmap pixmap;
pixmap.load("image.png");
pixmap = pixmap.scaled(this->graphicsView->size());
scene->addPixmap(pixmap);
}
I also tried this:
QGraphicsScene* scene = new QGraphicsScene;
this->graphicsView->setScene(scene);
QPixmap pixmap;
pixmap.load("/home/doriad/glasses.jpg");
QGraphicsPixmapItem * item = scene->addPixmap(pixmap);
this->graphicsView->fitInView (item);
but the image appears tiny, rather than filling up the view like I would expect. Can anyone explain this?
The full project and image are available here: daviddoria.com/Uploads/qt/QPixmapPosition
Don't worry about scaling the pixmap yourself or even translating it, let the view do it for you.
Use graphicsView->fitInView(pixmap); but you should read the documentation for :
Qt's Graphics View Framework
void QGraphicsView::setSceneRect (QRectF )
void QGraphicsView::translate ( qreal dx, qreal dy )
void QGraphicsView::fitInView ( const QGraphicsItem * item, ... )
The way that QGraphicsScene and QGraphicsView interact is that you can have a single scene with at least one or more views.
A good example I like to think of is a zoomed in view of part of a map with a mini view of the entire map in the corner. There are two views, one of part of the map and one of the entire map, with one scene, the map itself.
So you put items in your scene and all the items in the scene are drawn relative in size to each other. The "scene rect" of your view, by default, scales to fit the items in the view until one unit in the scene is one pixel in the view or until it needs to zoom out to fit all the items in your scene.
If you call fitInView(someItem) it should scale your view of the scene so that the item specified fills it up and translates the view so that it is centered. If you need to translate or scale it more use the translate or scale functions in QGraphicsView.
When you are jumping between coordinate systems of your scene and view with your QRect's or QPoint's, use the helper functions: mapToScene and mapFromScene from QGraphicsView.
Try this:
QGraphicsScene* scene = new QGraphicsScene;
scene->setSceneRect(graphicsView->sceneRect());
QPixmap pixmap;
pixmap.load("/home/doriad/glasses.jpg");
pixmap=pixmap.scaledToWidth(this->graphicsView->width());
QGraphicsPixmapItem * item = scene->addPixmap(pixmap);
graphicsView->setScene(scene);
By default, the pixmap will be at (0,0) in the scene, and the scene will be at (0,0) in the view. QWidgets are sized by pixels. If you had a QGraphicsView the size of the screen and the resolution is 1440 x 900, you can position objects in that view from (0,0) or the top left corner of the screen, to (1440,900) the bottom right corner of the screen. Most QGraphicsItems are placed with reference to their top left corner. So placing a pixmap at (0,0) aligns the top left corner of the pixmap with the top left corner of the scene it's placed in. If your pixmap 'hangs off the bottom' of your view, try using:
pixmap=pixmap.scaledToHeight(this->graphicsView->height());
If you use the function:
this->graphicsView->fitInView (item);
The graphicsView will only scroll to the point where your item fits in the view.
I downloaded David's code and ran it two ways. Click the links to see the results.
1) with pixmap=pixmap.scaledToHeight(this->graphicsView->height());
2) with pixmap=pixmap.scaledToHeight(200);
I don't know enough to explain why this is happening, but I thought it would be a useful data point.
It turns out the problem was that the GraphicsView was in a layout. In my example, the resizing of the image was done in the Form constructor. Apparently this is before the layout takes its shape? I moved the code to a pushButton and when I click it the image is sized how I would expect.
I got the image to stay sized to the GraphicsView in the Layout by subclassing QGraphicsView and reimplementing :
class CustomGraphicsView: public QGraphicsView
{
Q_OBJECT
{
void resizeEvent ( QResizeEvent * event )
{
emit resized();
}
signals:
void resized();
}
Then I connect this resized() signal to a slot that simply calls this->View->fitInView (this->ImageToTraceItem);
David

Resources