[Qt-interest] Unable to get sensible coordinate mappings
Andrew Hodgkinson
ahodgkinson at endurancetech.co.uk
Mon May 18 12:39:44 CEST 2009
(I asked this on qt-embedded-interest a few days ago but was asked to try
here instead - sorry if you're on both lists and get this twice!).
I have a QT Embedded 4.5.1 application running on Linux but its behaviour
with respect to local (item) and scene (device) coordinates is very
strange. I would appreciate any advice, documentation pointers etc. that
anyone can offer - I'm new to Qt and may have misunderstood how the
coordinate system works.
An instance of a QGraphicsView derived class is created by main(). This
goes on to read the available redraw area in what I hope is a reasonably
future-proof / device-independent way:
QDesktopWidget * desktop = qApp->desktop();
int screenNum = desktop->screenNumber();
QRect geometry = desktop->availableGeometry( screenNum );
QSize size( geometry.width(), geometry.height() );
The code will run on a digital TV receiver, so there is no concept of a
WIMP-style environment in the GUI. Traditional window tools are
meaningless. Accordingly, the tools and frame are hidden and the view's
window is resized to fill the screen.
setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
setWindowFlags ( Qt::FramelessWindowHint );
resize ( size );
A QGraphicsScene is constructed.
QRectF bounds( 0, 0, geometry.width(), geometry.height() );
scene = new QGraphicsScene( bounds, this );
scene->setItemIndexMethod( QGraphicsScene::NoIndex );
setScene( scene );
So far, so good. I believe I now have an object providing a view onto a
scene with a coordinate system that is not scaled, so the scene's logical
coordinates map 1:1 to pixels on the device. The scene covers the whole
of the device's display area.
The QGraphicsView derivative now wants to draw things in the scene it has
created. It instantiates a derivative of QGraphicsItem and adds it to the
scene with "scene->addItem". The trouble starts here. The purpose of the
class is to display a banner across the screen's width at about 1/3 of
its height. A nominal resolution of 1280x720 is chosen; if the device is
actually operating in a higher (e.g. 1920x1080) or lower (e.g. 640x480)
resolution, the UI will scale.
To achieve this, a hard-coded banner area is specified:
QRect bannerArea = QRect( 0, 0, 1280, 720 / 3 );
At instantiation the class can't do more than this as it needs to know
the painter context for scaling coordinates - thus, in its implementation
of the "paint()" method, it first makes sure that the range of item
coordinates available for painting fall exactly within the banner area
defined earlier.
painter->setWindow( bannerArea );
Thus item coordinates now extend from (0,0) at the top left to (1279,719)
at the bottom right inclusive. If I took no further action, anything
drawn by the painter would be stretched to fill the device screen because
I've not modified the viewport. This is undesirable. I'd like to maintain
aspect ratio on the assumption of square(ish) pixels.
To do this, I get the scene rectangle, then define a 10% overscan safe
area at all edges.
QRectF safeSceneRect = scene()->sceneRect();
qreal sceneSafeW = safeSceneRect.width() / 10;
qreal sceneSafeH = safeSceneRect.height() / 10;
safeSceneRect.adjust
(
sceneSafeW,
sceneSafeH,
-sceneSafeW,
-sceneSafeH
);
The 'safe' version of the scene rectangle has its height adjusted to
match its width, scaled by the "bannerArea" aspect ratio (dodgy code
formatting below => avoid e-mail wordwrap!).
safeSceneRect.setHeight
(
( safeSceneRect.width() * bannerArea.height() )
/ bannerArea.width()
);
Finally, a viewport is set.
painter->setViewport
(
safeSceneRect.x(),
safeSceneRect.y(),
safeSceneRect.width(),
safeSceneRect.height()
);
If I now encourage the banner to be redrawn, then it *almost* works. The
confusion is in the missing link - as a QGraphicsItem derived class, I
must implement the "boundingRect" virtual function. Since the Qt graphics
documentation insists that all coordinates are in item coordinates, I
would expect this to read as follows (ignoring possible adjustments for
pen width, which all depends on exactly how the banner paint code chooses
to redraw within its available coordinate space):
QRectF SomeClassName::boundingRect() const
{
return bannerArea; // Well, a QRectF equivalent of this QRect
// is actually needed, but you get the idea!
}
This fails. In 640x480, it works OK. In 1280x720, the bottom edge of the
banner is chopped off. In 1920x1080, only the top left corner of the
banner shows up. It quickly becomes apparent that I only get to see a
1280x720 rectangle anchored at the top left of the scene - that is, the
returned value from boundingRect() is apparently being interpreted as
scene coordinates, NOT item coordinates. If I hard-code a rectangle of
0,0,1920,1080 then the 1920x1080 device works fine; similarly,
hard-coding 0,0,1280,720 stops the bottom edge of 1280x720 being clipped.
I *do* call "prepareGeometryChange()" immediately after initialising
"bannerArea" just to make sure that Qt knows that the return value from
"boundingRect" for the item has changed.
http://doc.trolltech.com/4.5/qgraphicsitem.html#boundingRect
The above does not explicitly state that item coordinates are in use
although it is very heavily implied; introductory text also says that it
should be [1]:
http://doc.trolltech.com/4.5/qgraphicsitem.html#details
"All of an item's geometric information is based on its local
coordinate system. The item's position, pos(), is the only function
that does not operate in local coordinates, as it returns a position
in parent coordinates."
Further, there's a function specifically designed to return the bounding
rectangle in scene coordinates ("sceneBoundingRect()", which would be
rather pointless if "boundingRect" already did that.
I cannot get the class to map coordinates, deepening the mystery. Any
attempt to map item to scene coordinates just returns item-like values
back; if I move the item with setPos() then the so-called scene
coordinates show an equivalent translation, but are still scaled in a
1280-wide range, regardless of actual scene size. For example:
QRectF sceneBoundingRect = mapRectToScene
(
bannerArea.x(),
bannerArea.y(),
bannerArea.width(),
bannerArea.height()
);
The values of sceneBoundingRect match bannerArea regardless of scene size.
I don't understand this. My scene coordinates are not scaled by the view
and the QGraphicsItem derivative merely sets its own local coordinate
scope and a viewport. The viewport, of course, *is* set in scene
coordinates and debug output shows that these are the expected values
depending on the device size. So, scene()->sceneRect() is the 'right
size', but mapping any item rectangle to scene coordinates with e.g.
mapRectToScene() returns nonsensical results.
Anyone able to explain this? Many thanks! :-)
Footnote:
[1] You will note that the example implementation of 'boundingRect'
given in the method documentation is correct, while the example
given in the 'details' introduction section is incorrect. It adds
pen width divided by 2 to the width / height - should not divide
by two there.
--
TTFN, Andrew Hodgkinson
Find some electronic music at: Photos, wallpaper, software and more:
http://pond.org.uk/music.html http://pond.org.uk/
More information about the Qt-interest-old
mailing list