PDA

View Full Version : transform() vs pos() for QGraphicsItem



aalexei
27th April 2009, 02:55
Hi all, I'm new QT so please excuse the following if it's obvious.

There seems to be two item-based coordinate systems for QGraphics items (at least for translations) and they seem to be independent and have a cumulative effect. One you can access via setPos(), moveBy() etc the other via translate() etc by setting the transform matrix.

I've read in other posts that translate() is only provided for "completeness" :confused: and we should use pos() and setPos(), and in the API docs that translate and moveBy are "conceptually separate" :confused:. I'm of a mathematical bent and I like affine transforms. I want to set up complex nested transformations which just amounts to matrix multiplication for the affine coordinate system, but it would be a real pain to have to set all movements in a separate co-ordinate system from scaling and rotations ...

Here is an example (it's pyqt but should be clear)...

I've created an item in a scene, initially the scenePos and pos are the same:


>>> self.scenePos()
PyQt4.QtCore.QPointF(10.0, 10.0)
>>> self.pos()
PyQt4.QtCore.QPointF(10.0, 10.0)


the translation shows 0,0:


>>> t=self.transform();t.dx();t.dy()
0.0
0.0


now translate it to the origin in scene coords:


>>> self.translate(-10,-10)
>>> t=self.transform();t.dx();t.dy()
-10.0
-10.0
>>> self.scenePos()
PyQt4.QtCore.QPointF(0.0, 0.0)
>>> self.pos()
PyQt4.QtCore.QPointF(10.0, 10.0)

you'll notice that the translation and pos coordinate systems are working against each other to have no net effect in the scene coordinates

Now we can move it back with moveBy() which affects the pos() coordinate system not the translation coordinate system:


>>> self.moveBy(10,10)
>>> self.scenePos()
PyQt4.QtCore.QPointF(10.0, 10.0)
>>> self.pos()
PyQt4.QtCore.QPointF(20.0, 20.0)
>>> t=self.transform();t.dx();t.dy()
-10.0
-10.0


I guess my question boils down to the following: since the affine transform coordinate system is much nicer and richer than the separate movement coordinate system, is it OK to base my code just on the former and always have the pos() at (0,0) or will this bite me later with things like boundingRect() shape() etc ?

wysota
4th May 2009, 13:10
Treat "pos" as an anchor of child item's origin to its parent's coordinate system. If you don't set a position explicitely, child's origin will be placed at the origin of its parent's. translate() affects the way how the item is drawn and interacted with. You can replace "pos" with it but be aware that you won't be able to recover the item's whereabouts in any meaningful way from the transformation matrix.

"pos" is easier to use than translate() and it gives a different effect when used together with scaling or rotating. But you don't have to change the initial position - you can stay with (0;0) and use the transformation matrix only.

aalexei
9th May 2009, 06:22
I've recoded my app to use just the transformation matrix and leave pos() set at [0,0]. There where a couple of gotcha's..


the default implementation of mouseMoveEvent() uses the pos() coordinate system, so this has to be re-implemented using translation (including dealing with other selected items).
moveBy() uses the pos() coordinate system
translate/rotate/scale uses the translate() coordinate system
the mapToXXX functions take into account both pos() and translate() coordinate systems


l think the pos() coordinate system is totally redundant, and it's a pain carrying around two coordinate systems for each item in a scene, esp when they are rotated and scaled about arbitrary points, or nested.

It is easier to use pos() for movement, but only because of a couple of helper functions such as setPos() and pos() - these can be easily implemented to use the transform matrix. e.g. the items [0,0] point in it's parent frame (what pos() returns for the pos() coordinate system) will be [transform().dx(),transform.dy()]

I doubt the pos() coordinate system will be joined to the translate() one anytime soon, but the docs could be made a lot clearer on describing these two coordinate systems and how to do things like rotate about some point in the scene when both the translation and the position are not at the origin.

wysota
9th May 2009, 09:12
l think the pos() coordinate system is totally redundant,
Unfortunately for you most users of GraphicsView would say otherwise.


and it's a pain carrying around two coordinate systems for each item in a scene, esp when they are rotated and scaled about arbitrary points, or nested.
I think you must be doing something strange. pos() help with rotations and scaling, not makes it more difficult.


It is easier to use pos() for movement, but only because of a couple of helper functions such as setPos() and pos() - these can be easily implemented to use the transform matrix. e.g. the items [0,0] point in it's parent frame (what pos() returns for the pos() coordinate system) will be [transform().dx(),transform.dy()]
It's not that easy. If you do translate-rotate-translate then you won't be able to tell the position of each point in the item relative to the parent item unless you know you actually did perform rotation (and what it was and when it was).


I doubt the pos() coordinate system will be joined to the translate() one anytime soon,
I can assure you it won't. translate() is only for completeness with GraphicsView, pos() is more intuitive and easier to use so it will always be preferred over pure maths.


but the docs could be made a lot clearer on describing these two coordinate systems and how to do things like rotate about some point in the scene when both the translation and the position are not at the origin.

To me, if you are walking into pure math, you're stepping out of the "average person" world and since that moment you are on your own. If you do use translate(), I'd assume you knew what you were doing so proper matrix operations should be easy for you.

aalexei
9th May 2009, 14:15
Unfortunately for you most users of GraphicsView would say otherwise.

You can simply re-implement pos() and moveBy() to use the items transform matrix. Most users would never notice. It only bites you if you've had to write code to take both into account.


I think you must be doing something strange. pos() help with rotations and scaling, not makes it more difficult.

In what way? It's just another coordinate system that the affine one (the one that let's you do scaling and rotation) sits on top of. Why add that complexity? It seems completely unnecessary.


It's not that easy. If you do translate-rotate-translate then you won't be able to tell the position of each point in the item relative to the parent item unless you know you actually did perform rotation (and what it was and when it was).

You've lost me here ... the items origin in the parent frame will still be at the transform's .dx() and .dy(). try it ... The other points on the item won't be so easy, but to map those to the parent you will still need to use the transform. You can't even do that in the pos() coordinate system as it doesn't support rotations and scaling. If you look at the source for mapToParent(), this is exactly what it does:


return transform().map(point) + d_ptr->pos;



I can assure you it won't. translate() is only for completeness with GraphicsView, pos() is more intuitive and easier to use so it will always be preferred over pure maths.

They do the same thing. There is nothing that you can do with pos() and family, that you can't do with translate() and the transform. That is the whole point of affine transformations - you can do any 2-D graphics transformation with them. It just seems pointless to carry around another coordinate system for no apparent reason. Hardly pure maths :D



To me, if you are walking into pure math, you're stepping out of the "average person" world and since that moment you are on your own. If you do use translate(), I'd assume you knew what you were doing so proper matrix operations should be easy for you.

translate() does the same thing as moveBy(). they just work on two different co-ordinate systems and each item in a view has both. If you want to do something like rotate an item about a point in the scene you have to take into account both - this is what the mapToXXX set of functions have to do. Now, you can simplify your life by keeping translate() at [0,0] but then you lose the ability to do affine transforms in a nice way eg you can't just multiply them etc. Alternatively you can keep pos() at (0,0). If they both do the same thing one is redundant - may as well keep the more powerful one and ditch the other.

wysota
9th May 2009, 15:46
You can simply re-implement pos() and moveBy() to use the items transform matrix. Most users would never notice. It only bites you if you've had to write code to take both into account.

There is a special flag - ItemIgnoresTransformations - if would be useless if pos() was implemented using the transformation matrix.

I'm also not sure if it is possible to retrieve the position from the matrix if other operations have been applied to it in the meantime. Remember you can apply any changes to the matrix, including shearing and applying the identity matrix. With pos() you have a simple way of knowing the offset between the item and its parent.


Why add that complexity? It seems completely unnecessary.
What seems and what is are two different things.


They do the same thing. There is nothing that you can do with pos() and family, that you can't do with translate() and the transform.
Not really, see ItemIgnoresTransformations.

aalexei
10th May 2009, 02:39
There is a special flag - ItemIgnoresTransformations - if would be useless if pos() was implemented using the transformation matrix.

Can't see how this wouldn't be trivial to implement. If the flag is set, only change the translation part of the affine transform. Just as for ItemIsMovable - you just need to switch the behaviour of the code if the flag is set.


I'm also not sure if it is possible to retrieve the position from the matrix if other operations have been applied to it in the meantime. Remember you can apply any changes to the matrix, including shearing and applying the identity matrix. With pos() you have a simple way of knowing the offset between the item and its parent.

It's absolutely possible. Look at the source to mapToXXX functions.

wysota
11th May 2009, 17:46
Can't see how this wouldn't be trivial to implement. If the flag is set, only change the translation part of the affine transform.

You can always set an arbitrary transform on the item and this would affect the functionality. It would probably also not work with nested items (but I'm not sure how ItemIgnoresTransformations works in this case, so I won't say I'm sure of that). Besides operating on matrices is more expensive than just operating on a QPoint so if you can have 90% of the functionality for the cost of 30%, I don't see why not do it and only have a slowdown with the remaining 10%.

But I won't argue with you - if you think you are right then reimplement appropriate routines in Qt and check if everything works as it used to before. If it does, send a patch to Qt Software with your arguments and maybe they will agree with you.

bootchk
13th November 2012, 11:09
It is not just setPos(), but also setScale() and setRotation(), and setTransformations(), that do not affect the separate transform() matrix. (e.g. transform() returns the same matrix before and after a call to setScale().)

I won't argue which is the best approach for the API, but I will say that the Qt approach is not conventional: most graphics programming texts approach use a single transform matrix that a rotation operation would change.

And the Qt documentation must be read VERY carefully. For example: "The scale is combined with the item's rotation(), transform() and transformations() to map the item's coordinate system to the parent item." Why doesn't it mention pos()? Then you should follow the link to "Transformations" to learn what "combine" means. And "scale a transformation" is QTransform.scale(), not the same thing as calling "setScale()" on a graphic item. QTransform.scale() is a verb setter, QGraphicsItem.scale() is a noun getter. QTransform is separate from QGraphicsTransform. transform() is not the same as transformations(), even though both are noun getters with too similar names.

This discussion is related to how to serialize a QGraphicsItem. Do you need to serialize (pos(), scale(), rotation(), transform(), transformations()) or can you somehow get a single matrix representing all of those?

wysota
13th November 2012, 23:34
It is not just setPos(), but also setScale() and setRotation(), and setTransformations(), that do not affect the separate transform() matrix. (e.g. transform() returns the same matrix before and after a call to setScale().)
The methods you mention were introduced in Qt 4.6 thus the discussion in this thread does not take them into consideration.


I won't argue which is the best approach for the API, but I will say that the Qt approach is not conventional: most graphics programming texts approach use a single transform matrix that a rotation operation would change.
Those methods were added to QGraphicsItem as a convenience. Therefore both "old" and "new" ways exist to operate on the item.


Why doesn't it mention pos()?
Because pos() is not part of the "transformation".


Then you should follow the link to "Transformations" to learn what "combine" means. And "scale a transformation" is QTransform.scale(), not the same thing as calling "setScale()" on a graphic item. QTransform.scale() is a verb setter, QGraphicsItem.scale() is a noun getter. QTransform is separate from QGraphicsTransform. transform() is not the same as transformations(), even though both are noun getters with too similar names.
Again, some classes and methods are a later addition to Qt. Since you can't remove something that's already defined in an earlier release, some functionality is "doubled".


This discussion is related to how to serialize a QGraphicsItem. Do you need to serialize (pos(), scale(), rotation(), transform(), transformations()) or can you somehow get a single matrix representing all of those?
The attributes you mention are not part of the object definition thus this is not about serializing the item but rather the scene (as in getting a snapshot of current mappings of items in the scene). There is no built-in mechanism for that, you need to query all the attributes you are interested in on your own.