PDA

View Full Version : QGraphicsPixmapItem position and size question



d_stranz
24th January 2013, 02:15
I am building a QGraphicsScene that contains many thousands of small images. The scene has one direct child, a QGraphicsRectItem, that serves as the parent for all other items added to the scene. This rect item has world coordinates (0, 0) - (50, 4000).

The scene is intended to visualize many small blobs resulting from segmentation of an image. (Imagine many rice grains scattered on a surface). Each blob has some dimension in the world coordinate space. Since the input data has a discrete character (for example, the x-axis is divided into 3600 units, the y axis into 400,000 units), I create a QImage to represent each blob, where the dimensions of the image are so many units of x and so many units of y (typically 10 x 20 pixels).

The pixels in each blob image are colored according to the z value of the data at that point. In 3D, the blobs look roughly cone-shaped.

I create the QImage, set the pixel colors, convert it to a pixmap, then create the QGraphicsPixmapItem and set its offset to the upper left coordinate of the underlying data for the blob. So far so good.

The problem is this: When the scene is displayed, the pixmaps are displayed in the correct position on screen, but they are stretched horizontally off to the right side of the scene.

8624

If I replace these image items with QGraphicsRectItem instances of the right size and position, I get the results shown in image 2, which has the small rects in the correct positions in the plot.

8623

So apparently there is some scaling needed to tell the pixmap item how to map a width and height in pixel dimensions onto the world dimensions. But I don't know how to specify that; QGraphicsItem has a "scale" method, but it is single-valued, and I need to specify a different scale factor for x and y dimensions.

Can anyone give some help here?

wysota
25th January 2013, 13:57
Can you prepare a minimal compilable example reproducing the problem? Pixmap items should be doing any scaling by default, you should be getting an item of the same size as the underlying image. Maybe it's the offset that is the problem? Have you tried not setting it and see if it has influence on the result?

d_stranz
25th January 2013, 22:38
I tried every variation I could think of yesterday inside of my project code, including parenting the pixmap item inside a QGraphicsRectItem, setting the offset, not setting the offset, setting ItemIgnoresTransform on / off, replacing my colored pixmap with a sold black one, setting a scale transform from dimension in pixels to dimension in world coordinates (and vice versa). Nothing seems to make any difference. There is always one of two outcomes: I see the rect item on the screen and no pixmap, or I see the rect and the pixmap, but the pixmap has completely incorrect size. If I set the brush to Qt::NoBrush for the rect item, I do get unfilled rects, so I presume the pixmaps are not being hidden by some fill in the rect.

I think my problem might be that in my graphics library, I am doing a separate transformation of the graphics items within the region of the plot bounded by the axes (my "canvas"). This is probably replacing the scaling I do when I first create the pixmaps and messing it up.

I am going to back off today and do a minimal example to try to debug my way through it. If I get it to work there, then I will go backwards to see what the differences are between the minimal version and the x-y data plot version shown in my screenshots.

If after a day or so I still can't figure it out, I will go to Option B, which is to make a background QImage of all the blobs, and overlay that with QGraphicsPolygonItems to provide outlines. The whole purpose of using pixmap items is for object detection so I can select them or pop up tool hints with identifying information when the user hovers over a blob. Using the overlay will let me do that, but then I have the headache of keeping the image zoom and graphics view zoom synchronized.

d_stranz
26th January 2013, 19:30
OK, I have attached a small example. It basically creates a scene containing a QGraphicsRectItem. Inside that rect item are a few QGraphicsPixmapItems, all the same pixel size, but at random positions. The meat of it is this:



CPixmapItem::CPixmapItem(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi( this );

// srand( time( 0 ) );

double width = 1000;
double height = 800;

double worldWidth = 50;
double worldHeight = 4000.0;

mpScene = new QGraphicsScene( this );
mpScene->setSceneRect( 0.0, 0.0, worldWidth, worldHeight );
mpScene->setBackgroundBrush( Qt::lightGray );

mpView = new QGraphicsView;
mpView->setScene( mpScene );

QGraphicsRectItem * pRect = new QGraphicsRectItem( 10.0, 100.0, 40.0, 1000.0 );
pRect->setBrush( Qt::white );

mpScene->addItem( pRect );

double rectWidth = pRect->boundingRect().width();
double rectHeight = pRect->boundingRect().height();

long pixWidth = 4;
long pixHeight = 10;
int nItems = 10;

#define RCOLOR ( int( 255 * float( rand() ) / float( RAND_MAX ) ) )

for ( int nItem = 0; nItem < nItems; ++nItem )
{
QImage image( pixWidth, pixHeight, QImage::Format_ARGB32 );
QColor color( RCOLOR, RCOLOR, RCOLOR );
image.fill( color );

QGraphicsPixmapItem * pPixMap = new QGraphicsPixmapItem( QPixmap::fromImage( image ), pRect );

double x = rectWidth * double( rand() ) / double( RAND_MAX );
double y = rectHeight * double( rand() ) / double( RAND_MAX );

// pPixMap->setParentItem( pRect );

pPixMap->setOffset( x, y );
// pPixMap->setPos( x, y );

// mpScene->addItem( pPixMap );
}
setCentralWidget( mpView );
resize( long( width ), long( height ) );

mpView->fitInView( pRect->boundingRect() );
}


And you get this screenshot as a result:

8630

What I do not understand is what is happening between lines 41 - 51. All of these scenarios give an identical result on screen:

1. Remove "pRect" from the construction of "pPixMap" (that is, create it parentless) and uncomment line 51 to add it as a direct child of the scene.

2. Remove "pRect" from the construction, but uncomment line 46 to set the rect as its parent.

3. Comment line 48, uncomment line 49 using scenario 1.

4. Comment line 48, uncomment line 49 using scenario 2.

The only difference between the use of setPos() vs. setOffset() is that the bounding rect of the pixmap item changes from (0,0,4,10) with setOffset() to (x,y,4,10) with setPos(). But otherwise, the position on screen does not change.

According to the docs, an item's position coordinates are relative to its parent. Therefore, I would expect that all of the pixmaps should be positioned relative to the rect item in case 2, but they aren't. They seem to be always positioned in scene coordinates.

Note that the size of the pixmap rectangles is correct with respect to the scene - that is, even though they are created with a 4 x 10 pixel size, they are scaled to fill a 4 x 10 scene coordinate rectangle. At least this part is working the way I want it to.

Any ideas what I am doing wrong, or do the relative position rules simply not apply to QGraphicsPixmapItem?

8631

wysota
26th January 2013, 23:49
I think the problem might be line 56. You are calling fitInView() with Qt::IgnoreAspectRatio thus you get aspect ratio of the passed rectangle. Since the rectangle is narrow and high and your view is wide, the scene gets horizontally stretched to match the rect. Try passing Qt::KeepAspectRatio as the second argument to fitInView().

d_stranz
26th January 2013, 23:55
I added the fitInView() call just because I got tired of having to scroll the window each time it came up. The end result is still the same: the pixmap images are not positioned relative to their parent, but instead are relative to the scene.

Screenshot with Qt::KeepAspectRatio - just gives me a tall skinny rectangle, but the images are still wrong.

8634

Edit: OK, here's the error:



QGraphicsRectItem * pRect = new QGraphicsRectItem( 10.0, 100.0, 40.0, 1000.0 );


- giving the rect item a non-zero origin. By adding a call to pRect->setPos() after the pixmap items had been added demonstrated that in fact the images were being positioned relative to the rect, because they move with it when the rect is moved. Changing the origin to (0, 0) fixes everything - all the images are correctly size and positioned.

Now that this seems to be working as desired, I now have to understand why it doesn't work in the context of the larger scene in my data plot from the initial post. I suspect it is because there is an inappropriate transform being applied.

wysota
27th January 2013, 00:07
Ok, I simply didn't understand what the problem was :)

What you get happens because of the bounding rectangle you set on the parent item. The effective "position" of any item is determined by two factors. One is its bounding rectangle, the other is its position. The bounding rectangle contains information about range of coordinates the item is going to paint itself into. The position (QGraphicsItem::setPos()) tells the framework where the item's (0,0) coordinate should be placed in its parent coordinates (or scene coordinates if it doesn't have a parent).

What you did is a very common mistake -- you expressed the "position" of an item (the white rectangle) in its parent's coordinates instead of its own coordinates. Effectively your item is "anchored" to the scene origin (as 0,0 is the default pos of any item) but paints itself offset to the beginning of the bounding rect (10,100). On the other hand all items have the rectangle set as their parent thus they are anchored to (0,0) (again the default pos) of the rectangle coordinates which is (-10, -100) relative to the top left corner of the rectangle. Then you offset each item's pixmap using the width and height of the rectangle as maximum so every pixmap is drawn in the rangle of (0,0) - (rectWidth,rectHeight) coordinates (which match both coordinates of the scene and of the white rect item).

Instead you should set your rectangle bounding rect as (0,0, 40, 1000) (which positions origin of its coordinate space in its top-left corner) and move it to the right by calling setPos(10, 100). Then you should create all your pixmap items and instead of using setOffset(), call setPos() with the position relative to the top-left corner (origin) of the white rectangle (their parent item).

d_stranz
27th January 2013, 00:15
While you were typing, I was coding, but we both arrived at the same result with the same coding solution. Thanks for the help.

This little exercise has made me realize that a lot of the pain I went through trying to develop my data plot (in which all the elements are QGraphicsItem based - the axes, labels, along with everything in the central canvas area) could have been implemented in a much more straightforward manner simply by making the entire thing a QWidget and only the canvas portion the QGraphicsView. I had no end of headaches trying to calculate inverse transformations and offsets so the the axes and labels would remain unchanged while the scale and offset of the canvas region changed with zooming, panning, and axes range.

That's one of the nice things about owning your own code, though. When you finally understand something like this, you can rip it apart and do it again the right way.

wysota
27th January 2013, 02:07
I had no end of headaches trying to calculate inverse transformations and offsets so the the axes and labels would remain unchanged
Wouldn't QGraphicsItem::ItemIgnoresTransformations help here?

d_stranz
28th January 2013, 01:37
Wouldn't QGraphicsItem::ItemIgnoresTransformations help here?

I wrote my original data plot code months ago, and I remember iterations where I tried that without success. As I remember it, when implementing the axis code, the axes and tick marks had to be rotated and translated depending on whether the axis was left, right, top, or bottom, but the tick and axis labels needed to be constant size, independent of scaling in the plot. Likewise, labels within the plot region itself need to be fixed size and not scale as the the plot is zoomed in and out. I ended up implementing the zoom transform for the plot, and then applying the inverse to the labels.

In that design, all of the visual elements in the data plot were QGraphicsItem instances in the scene - axes, labels, titles, as well as data (lines, curves, dots, whatever). That made the scale and position calculations overly complex. In the redesign I will implement tomorrow, nothing will be in the scene except the elements in the data region. Everything else will be plain, vanilla QWidgets, and the data region only will be a QGraphicsView / scene. I can probably do away with my custom QGraphicsScene class and end up with a much more flexible plotting tool as a result.

Why not Qwt? I have used Qwt extensively in other areas, but for this application I need easy selectability of data items. It can be done with Qwt, but it is painful. The G/V architecture makes it easy to detect and select items in a view so I can implement tool tips, turn various classes of items on and off, etc.