5

I am making an application where I need to draw figures (i.e. rectangles) above a picture and resize the whole scene. I'm using QGraphicsView and QGraphicsScene. I can't figure out why but when I was using fitIntoView() I was unable to draw figures on top of a picture (they probably were drawn behind the picture or outside the bounds).

Now I'm using QPixmap.scaled() to make the image fit in the QGraphicsScene. It works fine except when I need to display a big image and then zoom it in. Because I scaled the image to fit, it became smaller and when I call QGraphicsView.scale() the image scales as small one and I can't find a way to "scale back" the image to original size.

In the zoom part of code I tried to replace pixmap with its not scaled copy but I can't scale it with the same proportions as my figures scaling. Any help would be appreciated!


Here I load the image and scale it

QPixmap pix;
pix = pixmap->scaled(wid, hei,Qt::KeepAspectRatio, Qt::SmoothTransformation);
scn = new QGraphicsScene(pw);
pw->setScene(scn);
p = new QGraphicsPixmapItem(pix);
p->setPixmap(pix);
scn->addItem(p);

I draw figures like this

rect = new QGraphicsRectItem(x,y,w,h);
rect->setPen(QPen(getColor(), 1, Qt::SolidLine, Qt::FlatCap));
scn->addItem(rect);

And here's how I zoom

pw->scale(scaleFactor_,scaleFactor_);

Update: Now I have a QWidget with kind of toolbar on the top, the rest of free space is filled with a custom QGraphicsView. This is a derived class for grabbing mouse events, there's nothing more but overrides of event handlers. I'm adding PainterGraphicsView in my widget's constructor:

pw = new PainterGraphicsView();
ui->gridLayout_3->addWidget(pw);

I'm recreating the scene every time I load a new image into it:

scn = new QGraphicsScene(pw);
pw->setScene(scn);
QRect r = QRect(0,0, pw->width()-5, pw->height()-5);
scn->setSceneRect(r);

After I loaded an image I can draw figures on top of it, it looks like this:

enter image description here

And the scaled contents look like this:

enter image description here

As you can see, figures scaled together with a pixmap and this is exactly how I need it to work. The only thing I have to fix is the low quality of scaled pixmap. As I already said, I tried to replace pixmap with the original one in my ZoomIn method, but I can't do it saving figures' position.

void DrawerWidget::on_btZoom_clicked()
{
    p->setPixmap(pix); //p is QGraphicsPixmapItem, pix is an original pixmap
    p->setScale(0.5); //how can I scale the pixmap together with figures?? How to calculate the required scale?
    pw->scale(scaleFactor_,scaleFactor_);
}

enter image description here


Update 2:

A short explanation on my drawing figures method: (though I personally don't think that this is the issue, I'm interested is there a way to scale QGraphicsPixmapItem using setScale() to fit in QGraphicsView size)

I'm getting start_ and end_ coordinates from mouse events arguments (QMouseEvent *e)

 end_=pw->mapToScene(e->pos());
 start_=pw->mapToScene(e->pos());

I add the figure on mousepress:

rect = new QGraphicsRectItem(start_.x(),start_.y(),1,1);
scn->addItem(rect);

And I redraw the figure on mouse move

rect->setRect(Graphics::GetFigure(start_.x(),start_.y(),end_.x(),end_.y()));

GetFigure is a method for calculating the figure's rect, here's main part of it:

 static QRectF GetFigure(double startX, double startY, double finalX, double finalY)
    {
      QRectF shape;
      shape.setX(startX);
      shape.setY(startY);
      shape.setWidth(qFabs(finalX - startX));
      shape.setHeight(qFabs(finalY - startY));
      return shape;
   }
lena
  • 1,181
  • 12
  • 36
  • I can't understand why you scale the QPixmap. If you want to scale the image and maintain the resolution you can scale the QGraphicsPixmapItem instead. – Fabio Feb 29 '16 at 11:03
  • Hi @Fabio! Thank you for the response! I scale the QPixmap because I can scale it to fit in width and height, and with the QGraphicsPixmapItem I have to calculate the scale rate somehow which I don't know how... :( – lena Feb 29 '16 at 11:14
  • It is okay with the image smaller then QGraphicsScene size, but I need to make large image smaller. As I understood I need to use scale from 0 to 1 butI have a strange result. I just tried `setScale(0.5)` and I expected QGraphicsPixmapItem to have twice less size, but I only got a square containing a part of original image inside. What can I miss here? – lena Feb 29 '16 at 11:16
  • Sorry but it's not very clear. We need more code and maybe also some screenshot. – Fabio Feb 29 '16 at 15:36
  • @Fabio please take a look at my edits, I tried to make it more clear – lena Mar 01 '16 at 06:55
  • 1
    I'm suspicious that your real problem is that you can't change `QGraphicsView::scale` without causing your overlays to appear off-screen because you're not transforming your points between coordinate spaces correctly when placing your items based on mouse events. – cgmb Mar 01 '16 at 07:12
  • @cgmb I do not transform anything, right. It is still unclear how to make zoom correctly: calling `QGraphicsView.scale()` is convinient, because all contents of `QGraphicsView` are scaled at once. If I'll take @ddriver 's solution and scale `QGraphicsPixmapItem`, I'd also have to scale every figure on the scene (and doing transformations as well), please correct me if I wrong. I feel that I'm missing something, unfortunately I spent a lot of time searching with no significant results – lena Mar 01 '16 at 07:21
  • @cgmb ` void DrawerWidget::onMousePress(QMouseEvent *e) { start_=pw->mapToScene(e->pos()); end_=pw->mapToScene(e->pos()); } ` I capture signals from my subclassed `QGraphicsView` and draw figures on mouse move using `start_` and `end_` points – lena Mar 01 '16 at 07:35
  • Err... wait, and is `DrawerWidget` a subclass of `QGraphicsView`? – cgmb Mar 01 '16 at 08:13
  • @cgmb No, subclass of `QGraphicsView` is `PainterGraphicsView`, which I add to `DrawerWidget` inside the code. The variable is `pw`. Sorry, the code I provided is a bit chaotic, I can't provide all the code, there is too many lines already. I'm preparing an edit now about how I draw figures – lena Mar 01 '16 at 08:16
  • `mapToScene` transforms mouse events in `QGraphicsView`'s coordinate space into `QGraphicsScene` space. Each widget has their own coordinate space, so you'd first need to transform from `DrawingWidget`'s coordinate space into `QGraphicsView` coordinate space and then use `mapToScene` to transform into `QGraphicsScene` space. – cgmb Mar 01 '16 at 08:17
  • Maybe try `pw.mapToScene(pw.mapFromGlobal(e->globalPos()))` – cgmb Mar 01 '16 at 08:22
  • @cgmb please see Edit 2 – lena Mar 01 '16 at 08:25
  • I think you're right. That `setRect` is not the issue. It's sometimes a little confusing because there's both the x,y offset from the rect (which is in item-space) and the pos from the graphicsitem itself (which is in scene-space), but in your case I think the result should be identical, so that's fine. – cgmb Mar 01 '16 at 08:34
  • @cgmb I have another suspicion: I'm playing now one more time with `fitInView`. If image size is less than scene, I'm able to see a figure above, but I just found that if the image is bigger I can draw inside a very small rect in the centre of scene. Maybe all this time I used `fitInView` in the wrong way? I tried pass there `scn->itemsBoundingRect()` or `QGraphicsPixmapItem` but it seems not corrent – lena Mar 01 '16 at 08:43

2 Answers2

4

The scaling operation is destructive, once you scale down you end up with less data than you started with.

IF you want to be able to restore the original size keep a copy of the original pixmap.

You can create the QGraphicsPixmapItem from the original, non-scaled pixmap and then use QGraphicsPixmapItem::setScale(qreal) to change the size on level graphics item, not on level pixmap data. This way scaling should not be destructive, and you should not lose quality if you scale it up to the original size.

IF you want to scale multiple items (and not run into different transformation origin problems) you have a few options:

  • scale the entire view - that will scale everything
  • add the items as children of a parent item, then scaling the parent will scale its children as well

Item transformations accumulate from parent to child

  • add the items to a group using createItemGroup():

The items are all reparented to the group, and their positions and transformations are mapped to the group

dtech
  • 47,916
  • 17
  • 112
  • 190
  • Yes, I tried this solution before I posted on SO, but I have no clue how to scale the `QGraphicsPixmapItem` together with figures which I draw on top of it. Please see my edited question. – lena Mar 01 '16 at 07:03
  • Using your approach, in my Zoom method I replaced scaled down `QPixmap` with a copy of original one, but scrollbars haven't appeared as they did with `QGraphicsView.scale()` so I'm unable to navigate through the image. What have I missed here? – lena Mar 01 '16 at 12:11
1

I think your problem is that you set the scene rect as the QGraphicsView rect. This is not correct because the window reference system is different from the scene reference system. You don't need to set the scene rect, because the scene rect grows automatically when you add some items. If you want to limit the scene rect to the pixmap rect, you can set the scene rect as the QPixampItem bounding rect.

You don't need to scale the original pixmap. To scale your view, simply call QGrapicsView::scale. You can also call QGrapicsView::fitInView to fit the image in the view.

If you need to add some rects drawn by user (i.e. in window coordinates), you can use QGraphiscView::mapToScene to map the window coordinates to scene coordinates

I suggest to read the documentation about reference systems: The Graphics View Coordinate System

Fabio
  • 2,522
  • 1
  • 15
  • 25
  • I'd prefer to have scene the same size and position as `QGraphicsView`, i.e. fill all free space of a window, is that posible? When I tried `fitInView` method and then tried to draw figures they weren't drawn. Can this also be caused by this coordinates inconsistency? – lena Mar 01 '16 at 07:59
  • I copied my comment about drawing figures: ` void DrawerWidget::onMousePress(QMouseEvent *e) { start_=pw->mapToScene(e->pos()); end_=pw->mapToScene(e->pos()); } ` I capture signals from my subclassed `QGraphicsView` and draw figures on mouse move using `start_` and `end_` points – lena Mar 01 '16 at 07:59
  • Use mapToScene is correct, but it's not clear how you use start_ end end_ . Please post more code. Also be sure to set a correct zValue to the items – Fabio Mar 01 '16 at 08:09
  • It seems correct. You use also mapToScene in the mouseMoveEvent? Have you set a zValue to the items? You can set the QGraphicsPixmaItem zValue to -1 (the defaut is 0, so you are sure that the rects are drawn above the picture and not below). You can also use the constructor of QRectF that take the 2 corners as parameters instead of create a specific function to create the rect ( `QRectF(start_, end_)`) – Fabio Mar 01 '16 at 08:31
  • Also be sure to set a cosmetic pen to the rect items – Fabio Mar 01 '16 at 08:43
  • I use the pen, of course, I just didnt include this in my post – lena Mar 01 '16 at 08:44
  • The pen must be set to cosmetic (setCosmetic(true)) to make its width invariant to the view scaling. And what about the zValue? (see 3 comments above) – Fabio Mar 01 '16 at 10:39
  • I need the pen width to scale too. Or is it a wrong approach? I tried zValue, it doesn't change anything. – lena Mar 01 '16 at 10:44