I have been reviewing and assessing Qt for a small software project. Having only spent a small amount of time with the development environment I have managed to create a small program that allows me to draw 2D graphics. I have been using C++ for many years and for GUI related work I have mostly used MFC. I like C++ for UI’s because it makes programs that are fast and responsive, I plan to post an article comparing .NET, Java and C++ for building desktop applications in the near future.

Back to the library evaluation, one of the things I needed to be able to do was to flip a vector graphic item either horizontally or vertically around its center axis, meaning after the flip the object should be in the exact same place on the world view as it was before the transformation. There are built in functions for doing rotations around a center point but not this specific function. The 2D graphics system in QGraphicsView/QGraphicsItem uses matrix transforms which are at best very complicated – you need a good understanding of mathematics and a lot of patience to be able to comprehend this staff – I needed to understand this: –

WTF! – does this mean anything to you? No, me either, thank heavens for those bright folk that work this stuff out for us…. if you do need to know more about the maths behind this you check out this document: http://en.wikipedia.org/wiki/Transformation_matrix which from what I can tell gives you the explanations, but I have to confess I switched off very quickly.

Having read that document I wanted to give up – too hard – or I am too lazy, either way i got a beer from the fridge and got nowhere – but not being one to give up so easy I decided that instead of learning the maths I would go back to debugging and good old-fashioned trial and error! Actually the Qt library has done most of the hard work, the fact that I can achieve my goal with a little bit of debugging, trial and error is testimony to that.

I did search around for examples and while there were lots of questions about this, there would appear to be very little in the way of answers, perhaps I am just a maths luddite or maybe there are many more people that shy away from the maths involved and say nothing 🙂 In the end I came up with this which appears to work very well, and given there would not appear to be much information out there on this particular requirement, I thought I should make this example available. Having all the individual variables makes for easy debugging, you can see what’s going on. I also included a simple debug function for dumping the transform matrix data.

+1 for Qt – check it out http://qt.nokia.com/products/

void MyGraphicsItem::flipHorizontal() { // Get the current transform QTransform transform(this->transform()); qreal m11 = transform.m11(); // Horizontal scaling qreal m12 = transform.m12(); // Vertical shearing qreal m13 = transform.m13(); // Horizontal Projection qreal m21 = transform.m21(); // Horizontal shearing qreal m22 = transform.m22(); // vertical scaling qreal m23 = transform.m23(); // Vertical Projection qreal m31 = transform.m31(); // Horizontal Position (DX) qreal m32 = transform.m32(); // Vertical Position (DY) qreal m33 = transform.m33(); // Addtional Projection Factor // We need this in a minute qreal scale = m11; // Horizontal flip m11 = -m11; // Re-position back to origin if(m31 > 0) m31 = 0; else m31 = (boundingRect().width() * scale); // Write back to the matrix transform.setMatrix(m11, m12, m13, m21, m22, m23, m31, m32, m33); // Set the items transformation setTransform(transform); } void MyGraphicsItem::flipVertical() { // Get the current transform QTransform transform(this->transform()); qreal m11 = transform.m11(); // Horizontal scaling qreal m12 = transform.m12(); // Vertical shearing qreal m13 = transform.m13(); // Horizontal Projection qreal m21 = transform.m21(); // Horizontal shearing qreal m22 = transform.m22(); // vertical scaling qreal m23 = transform.m23(); // Vertical Projection qreal m31 = transform.m31(); // Horizontal Position (DX) qreal m32 = transform.m32(); // Vertical Position (DY) qreal m33 = transform.m33(); // Addtional Projection Factor // We need this in a minute qreal scale = m22; // Vertical flip m22 = -m22; // Re-position back to origin if(m32 > 0) m32 = 0; else m32 = (boundingRect().height() * scale); // Write back to the matrix transform.setMatrix(m11, m12, m13, m21, m22, m23, m31, m32, m33); // Set the items transformation setTransform(transform); } void dump_transform(QTransform& t, const char* name) { qreal _m11 = t.m11(); // Horizontal scaling qreal _m12 = t.m12(); // Vertical shearing qreal _m13 = t.m13(); // Horizontal Projection qreal _m21 = t.m21(); // Horizontal shearing qreal _m22 = t.m22(); // vertical scaling qreal _m23 = t.m23(); // Vertical Projection qreal _m31 = t.m31(); // Horizontal Position (DX) qreal _m32 = t.m32(); // Vertical Position (DY) qreal _m33 = t.m33(); // Addtional Projection Factor qDebug("================ %s ==================", name); qDebug("m11: %f, m12: %f, m13: %f", _m11, _m12, _m13); qDebug("m21: %f, m22: %f, m23: %f", _m21, _m22, _m23); qDebug("m31: %f, m32: %f, m33: %f", _m31, _m32, _m33); }

This content is published under the Attribution-Noncommercial-Share Alike 3.0 Unported license.

I posted a question regarding this on a forum and someone provided some additional information which may also be useful.

http://qt-project.org/forums/viewthread/18615/

Matrix transformations are easy if you know a couple of basic points. First of all, you multiply (stack up) individual transformations by multiplying matrices together. So, for example, if you want to shift something by 100 pixels, then rotate, then flip, you create individual matrices using relevant constructors or helper methods, and multiply together to get the final transformation. The only gotcha is the order of multiplication: the first transformation in your chain must be the last one you multiply with. So if you do transformations given by matrices A, B, C in that order, then you must multiply C*B*A. Such matrix multiplications are *not* commutative!

As to how a matrix works on a vector, that’s very easy too. The input vector “drops” from the top of the matrix, and comes out on the left. Suppose you have vector V=[x y z t] and matrix M=

[a b c d

e f g h

i j k l

m n o p]

Then the output vector M*V is simply

[a*x+b*y+c*z+t*d

e*x+f*y+g*z+t*h etc.]

I’m showing it for an augmented 3D vector (more on augmentation later), for a 2D vector the matrix is a 3×3 matrix, and the vector is [x y t].

As you see each element of the output vector is obtained by dropping the input vector on respective row on the matrix and performing element-wise multiplication and the summing it up. This element-wise multiplication and summing has a concise name: dot product. The output vector’s elements are dot products of the input vector with one row of the matrix at a time. The first output element is the input vector (dot product) first row, the second output element is the input vector (dot product) second row, etc.

Thus you can immediately see that an identity matrix — a matrix that gives you the same vector back when you multiply by it — is simply a matrix that has ones on the diagonal, and zeroes otherwise. Like so:

[1 0 0 0

0 1 0 0

0 0 1 0

0 0 0 1]

A matrix that does X flip simply has negative -1 in the term that passes x to the output, like so:

[-1 0 0 0

0 1 0 0

0 0 1 0

0 0 0 1]

The question you likely have: what’s the big deal with me using 4 component vector and a 4×4 matrix for what looks like a 3D vector [x y z]? In computer graphics, you often need transformations that would be impossible to do with an NxN matrix, where N is the number of components in the vector. Such a transformation is for example translation or perspective transformation. So what you do is you cheat. You add an extra element with value 1 to the vector. Thus a 3D vector becomes [x y z 1], a 2D vector becomes [x y 1]. That way you can add constants to the output. For example, to translate by 100 in the x direction, and -200 in the y direction, you’d have such a matrix (for 2D):

[1 0 100

0 1 -200

0 0 1]

That’s that, pretty much. If you have any other questions, ask. All you require is some general concepts and 8th grade math. Nothing else.

Hi Kuba,

Thanks for the detailed explanation. I can’t say I fully understand but i will definitely take a bit of time to see if I can get my head around it and try it out. Thank you for posting

Gerry