Qt Toolkit - The Coordinate System

| Classes - Annotated - Tree - Functions - Home - Structure

# The Coordinate System

A paint device in Qt is a drawable 2D surface. QWidget, QPixmap, QPicture and QPrinter are all paint devices. A QPainter is an object to draw on such surfaces.

The default coordinate system of a paint device has its origin at the top left corner. X increases to the right and Y increases downwards. The unit is one pixel on pixel-based devices and one point on printers.

## An Example

The illustration below shows a strongly magnified portion of the top left corner of a paint device.

The rectangle and the line were drawn by this code (with the grid added and colors touched up afterwards):

        void MyWidget::paintEvent( QPaintEvent * )
        {
            QPainter p( this );
            p.setPen( lightGray );
            p.drawRect( 1,2, 5,4 );
            p.setPen( darkGray );
            p.drawLine( 9,2, 7,7 );
        }

Note that all of the pixels drawn by drawRect() are inside the size specified (5*4 pixels). This is different from some toolkits; in Qt the size you specify exactly encompasses the the pixels drawn. This applies to all the relevant function in QPainter.

Similarly, the drawLine() call draws both endpoints of the line, not just one.

Here are the classes that relate most closely to the coordinate system:

- QPoint is a single 2D point in the coordinate system. Most functions in Qt that deal with points can accept both a QPoint argument and two ints, for example QPainter::drawPoint().
- QSize is a single 2D vector. Internally, QPoint and QSize are the same, but a point is not the same as a size, so both classes exist. Again, most functions accept both a QSize and two ints, for example QWidget::resize().
- QRect is a 2D rectangle. Most functions accept both a QRect and four ints, for example QWidget::setGeometry().
- QRegion is an arbitrary set of points, including all the normal set operations, e.g. QRegion::intersect(), and also a less usual function to return a list of rectangles whose union is equal to the region. QRegion is used e.g. by QPainter::setClipRegion(), QWidget::repaint() and QPaintEvent::region().
- QPainter is the class that paints. It can paint on any device with the same code. There are differences between devices, QPrinter::newPage() is a good example, but QPainter works the same way on all devices.
- QPaintDevice is a device on which QPainter can paint. There are two internal devices, both pixel-based, and two external devices, QPrinter and QPicture (which records QPainter commands to a file or other QIODevice, and plays them back). Other devices can be defined.

## Transformations

While Qt's default coordinate system is as described above, QPainter supports arbitrary transformations.

This transformation engine is a three-step pipeline, closely following the model outlined in books such as Foley & Van Dam and the OpenGL Programming Guide. We refer to those for an in-depth coverage; here we give just a brief overview and an example.

The first step uses the world transformation matrix. Use this matrix to orient and position your objects in your model. Qt provides methods such as QPainter::rotate(), QPainter::scale(), QPainter::translate() and so on to operate on this matrix.

QPainter::save() and QPainter::restore() save and restore this matrix. You can also use QWMatrix objects, QPainter::worldMatrix() and QPainter::setWorldMatrix() to store and use named matrices.

The second step uses the window. The window describes the view boundaries in model coordinates. The matrix positions the objects and QPainter::setWindow() positions the window, deciding what coordinates will be visible. (If you have 3D experience, the window is what's usually called projection in 3D.)

The third step uses the viewport. The viewport too describes the view boundaries, but in device coordinates. The viewport and the windows describe the same rectangle, but in different coordinate systems.

On-screen, the default is the entire QWidget or QPixmap where you are drawing, which is mostly suitable. For printing this function is vital, since very few printers even can print on the entire paper.

So in short, each object to be drawn is transformed into model coordinates using QPainter::worldMatrix(), then clipped by QPainter::window(), and finally positioned on the drawing device using QPainter::viewport().

It is perfectly possible to do without one or two of the stages. If, for example, your goal is to draw something scaled, then using just QPainter::scale() makes perfect sense. If your goal is to use a fixed-size coordinate system, just QPainter::setWindow() is perfect. And so on.

Here is a short example that uses all three mechanisms: the function that draws the clock face in the aclock/aclock.cpp example. We recommend compiling and running the example before you read any further. In particular, try resizing the window to different shapes.

        void AnalogClock::drawClock( QPainter *paint )
        {
            paint->save();

First off, we save the painter's state, so that the calling function is guaranteed not to be disturbed by the transformations we're going to use.

            paint->setWindow( -500,-500, 1000,1000 );

We set the model coordinate system we want: A 1000*1000 window where 0,0 is in the middle.

            QRect v = paint->viewport();
            int d = QMIN( v.width(), v.height() );

The device may not be square and we want the clock to be, so we find its current viewport and compute its shortest side.

            paint->setViewport( v.left() + (v.width()-d)/2,
                                v.top() + (v.height()-d)/2, d, d );

Then we set a new square viewport, centered in the old one.

We're now done with our view. From this point in, when we draw in a 1000*1000 area around 0,0, what we draw will show up in the biggest possible square that'll fit in the output device.

Time to start drawing.

            time = QTime::currentTime();
            QPointArray pts;

Since we'll draw a clock, we'll need to know the time. pts is just a utility variable to hold some points.

Next comes three drawing blocks, one for the hour hand, one for the minute hand and finally one for the clock face itself. First we draw the hour hand:

            paint->save();
            paint->rotate( 30*(time.hour()%12-3) + time.minute()/2 );

We save the painter and then rotate it so that one axis points along the hour hand.

            pts.setPoints( 4, -20,0,  0,-20, 300,0, 0,20 );
            paint->drawPolygon( pts );

We set pts to a four-point polygon that looks like the hour hand at three o'clock, and draw it. Because of the rotation, it's drawn pointed in the right direction.

            paint->restore();

We restore the saved painter, undoing the rotation. We could also call rotate( -30 ) but that might introduce rounding errors, so it's better to use save() and restore(). Next, the minute hand, drawn almost the same way:

            paint->save();
            paint->rotate( (time.minute()-15)*6 );
            pts.setPoints( 4, -10,0, 0,-10, 400,0, 0,10 );
            paint->drawPolygon( pts );
            paint->restore();

The only differences are how the rotation angle is computed and the shape of the polygon.

The last part to be drawn is the clock face itself.

            for ( int i=0; i<12; i++ ) {
                paint->drawLine( 440,0, 460,0 );
                paint->rotate( 30 );
            }

Twelve short hour lines at thirty-degree intervals. At the end of that, the painter is rotated in a way which isn't very useful, but we're done with painting so that doesn't matter.

            paint->restore();
        }

The final line of the function restores the painter, so that the caller won't be bothered by all the transformations we've done.