Jul 09

Horizontal and Vertical flip transformations of a QGraphicsItem in Qt QGraphicsView

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 plain 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: –

Matrix

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 whats 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);
}