Search code examples
qttransformqpainterqimage

Applying QTransform on QPainter - how to find translations


I would like to apply certain transformations on a QImage - and because of performance restrictions, I would like to apply these transformations at the time of rendering if possible.

The transformations I need - rotating 90, 180, 270 degrees, and vertical mirror.

I am rendering a QGraphicsScene to a QImage.
I would like the result to be rotated (0/90/180/270) or mirrored vertically.

My original code was easy:

QImage image = QImage(wOutput, hOutput, QImage::Format_Mono);
image.fill(QColor(Qt::white).rgb());
QPainter painter;
painter.begin(&image);
outputScene->render(&painter);
painter.end();

To rotate, I thought it would be effective to rotate the QPainter before painting - that way I do not have to perform additional transformations post process. (I am planning for a device with very restricted memory and speed, and Qt4.8)

It should work... but in addition to rotation I must also add a translation, and I can't figure out how much.

Without translation I am only getting blank images.

Also, rotating to 90/-90 I get smaller images. So I need to scale... by how much ?

My code that tries to transform (rotate and mirror) an image:

#include <QApplication>
#include <QGraphicsScene>
#include <QImage>
#include <QPainter>
#include <QTransform>

QImage badProcessScene(QGraphicsScene *s, int orientation, bool mirror);
QImage wantProcessScene(QGraphicsScene *s, int orientation, bool mirror);

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QGraphicsScene* s = new QGraphicsScene(-20, -100, 800, 600);
    s->addText("abcd", QFont("Arial", 20));
    s->setBackgroundBrush(Qt::red);

    // orientation = -90, 0, 90, 180;
    int orientation = -90;
    // vertical mirror true/false
    bool mirror = true;

    // what I am trying to do
    QImage im = wantProcessScene(s, orientation, mirror);
    im.save("test.bmp");

    // what I would like to come out like (Qt version)
    QImage im1 = badProcessScene(s, orientation, mirror);
    im1.save("test1.bmp");

    a.exit();
    return 0;
}

// Bad version, though functional - typical approach, of rotating images after being rendered
QImage badProcessScene(QGraphicsScene *s, int orientation, bool mirror)
{
    int wOutput = s->width(), hOutput = s->height();

    QImage image = QImage(wOutput, hOutput, QImage::Format_ARGB32_Premultiplied);
    image.fill(QColor(Qt::white).rgb());

    QPainter painter;
    painter.begin(&image);
    s->render(&painter);
    painter.end();

    if(mirror)
        image = image.mirrored(0, 1);
    image = image.transformed(QMatrix().rotate(orientation));

    return image;
}

// Desired but needs adjustments 
QImage wantProcessScene(QGraphicsScene* s, int orientation, bool mirror)
{
    int wOutput = s->width(), hOutput = s->height();

    // translation
    int wTr = wOutput, hTr = hOutput;
    // coefficients of transformation matrix
    qreal m11 = 1, m12 = 0, m21 = 0, m22 = 1, m31 = 0, m32 = 0;
    switch(orientation)
    {
    //case 0: break;
    case -90: wTr = hOutput; hTr = wOutput; m11 = 0; m12 = -1; m21 = 1; m22 = 0; 
              m31 = x?; m32 = x?; break;  
    case 180: wTr = wOutput; hTr = hOutput; m11 = -1, m22 = -1; 
             m31 = x?; m32 = x?; break;  
    case 90: wTr = hOutput; hTr = wOutput; m11 = 0; m12 = 1; m21 = -1; m22 = 0; 
              m31 = x?; m32 = x?; break;  
    }

    QImage image = QImage(wTr, hTr, QImage::Format_ARGB32_Premultiplied);
    image.fill(QColor(Qt::white).rgb());

    QPainter painter;
    painter.begin(&image);

    QTransform painterTransform;
    if(mirror)
    {
        // I have seen that negative scaling actually flips
        m22 *= -1;
        // I am guessing on shifts...
        switch(orientation)
        {
        case 0: m31 = x?; m32 = x?; break;  
        case 180: m31 = x?; m32 = x?; break; 
        case 90: m31 = x?; m32 = x?; break;
        case -90: m31 = x?; m32 = x?; break; 
        }
    }

    painterTransform.setMatrix(m11, m12, 0, m21, m22, 0, m31, m32, 1);

    painter.setTransform(painterTransform);
    s->render(&painter);
    painter.end();

    return image;
}

Note in the code above - I have 2 functions:

  • what I am trying to do, by rotating the QPainter before rendering (wantProcessScene)
  • one that does Qt "transforms" - which iterate through image pixels and move them after the image was already rendered - and on very large images, on memory and CPU limited devices is extremely slow; It works as I said in code and it is not acceptable. (badProcessScene)

I have not been able to figure out a formula for translation and scaling (because for 90/-90 it seems result is smaller) on rotating 180/90/-90.

As for mirror, I hope my idea of resizing by -1 works - but I have the same issue of determining shifts.

With incorrect shifts, I just see a white image. As I get this to work, 90/-90 rotations seem to result in a smaller image (that fits the larger size into the smaller, keeping aspect ratio).

Please help me figure out how to perform these transformations. I would love a matrix that determines translations (and scaling to reset images back to same size) based on angle - not weird angles just these.


Solution

  • I am posting the function to perform both rotations and mirror, by rotating the QPainter.

    I could not get an actual formula - most was trial and error and redrawing an object over and over, with its rotation.

    Once I figured out what the coefficients were, it become somewhat logical.

    For the resizing issue - the solution was to keep the "target" to the original rendering rectangle.

    QImage processScene(QGraphicsScene* s, int orientation, bool mirror)
    {
        int wOutput = s->width(), hOutput = s->height();
        QRect target(0, 0, wOutput, hOutput);
    
        // translation
        int wTr = wOutput, hTr = hOutput;
    
        // coefficients of transformation matrix
        qreal m11 = 1, m12 = 0, m21 = 0, m22 = 1, m31 = 0, m32 = 0;
        switch(orientation)
        {
        //case 0: break;
        case -90: wTr = hOutput; hTr = wOutput; m11 = 0; m12 = -1; m21 = 1; m22 = 0;
            m32 = wOutput; break;
        case 180: wTr = wOutput; hTr = hOutput; m11 = -1, m22 = -1;
            m31 = wOutput; m32 = hOutput; break;
        case 90: wTr = hOutput; hTr = wOutput; m11 = 0; m12 = 1; m21 = -1; m22 = 0;
            m31 = hOutput; break;
        }
    
        if(mirror)
        {
            switch(orientation)
            {
            case 0: m22 = -1; m32 = hOutput; break;
            case 180: m22 = 1; m32 = 0; break;
            case 90: m21 = 1; m31 = 0; break;
            case -90: m21 = -1; m31 = hOutput; break;
            }
        }
    
        QTransform painterTransform;
        painterTransform.setMatrix(m11, m12, 0, m21, m22, 0, m31, m32, 1);
    
        QImage image = QImage(wTr, hTr, QImage::Format_Mono);
        image.fill(QColor(Qt::white).rgb());
    
        QPainter painter;
        painter.begin(&image);
        painter.setTransform(painterTransform);
        s->render(&painter, target);
        painter.end();
    
        return image;
    }