I am trying to display text on the top right corner of a QGraphicsPixMapItem. I want the text to maintain a constant size on the screen, regardless of the size of the image, so I am using the ItemIgnoresTransformations flag on the text. However, when I do this, I cannot figure out how to get the correct width of my text item in the QGraphicsPixMapItems coordinate system.
Below is my current attempt:
QGraphicsPixmapItem *pixMapItem = m_GraphicsScene->addPixmap(QPixmap(imagePath));
m_GraphicsScene->setSceneRect(pixMapItem->boundingRect());
m_GraphicsView->FitViewToScene();
QGraphicsSimpleTextItem *nameTextItem = new QGraphicsSimpleTextItem("Item Name", pixMapItem);
QGraphicsSimpleTextItem *priceTextItem = new QGraphicsSimpleTextItem("$0.00", pixMapItem);
nameTextItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
priceTextItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
QFont f = QFont();
f.setPointSize(30);
nameTextItem->setFont(f);
priceTextItem->setFont(f);
priceTextItem->setPos(pixMapItem->boundingRect().width() - priceTextItem->boundingRect().width(), 0);
I believe that the error is in subtracting the width of the priceTextItem, as the relative position changes based on the resolution of the pixmapitem. If correct, the right edge of the text should lie perfectly on the right edge of the image.
The problem is that the coordinate system used to place the text item is that of the pixmap item.
Its origin point will always be the same in logical coordinates relative to the pixmap item, so its "left" will always be correct: its first letter will always be aligned almost in the same place of the image (with slight differences depending on the font and characters).
Since the text item ignores transformations, though, it will look like the text is "shifted", but that's only an "optical illusion": the same would happen if the image doesn't change size, but the text item is scaled, which is fundamentally the same thing (the two transformations are incompatible).
There are at least two possible solutions to this.
This approach uses the override of the item's paint()
, which is the simplest way to be aware of the actual transformations of an item.
Practically, we:
paint()
;transform()
;I cannot really write in C++, but the following python example should be clear enough to function as some form of pseudo-code:
class PixmapItem(QGraphicsPixmapItem):
def __init__(self, pixmap):
super().__init__(pixmap)
self.textItem = QGraphicsSimpleTextItem('Hello world!', self)
self.textItem.setFlag(QGraphicsItem.ItemIgnoresTransformations)
def paint(self, qp, opt, widget=None):
super().paint(qp, opt, widget)
scale = qp.transform().m11()
self.textItem.setX(
self.boundingRect().right()
- self.textItem.boundingRect().width() / scale
)
Note that this approach is probably not 100% safe. Besides the fact that painting functions should never do something that is not strictly related to drawing, there could be issues related to complex transformations (I cannot think of any right now, but I also cannot exclude everything).
In this case, we use an "item-in-the-middle" approach. The concept is to use an empty QGraphicsItem as child of the pixmap item, which will ignore transformations, then add the text item as its own child.
That container will then be placed at the right edge of the parent (the pixmap), so its origin point will always be absolute, no matter the transformation. The actual QGraphicsSimpleTextItem is then created as a child of that container and its x
will be the negative of its bounding rect width. Since the text item will inherit the "no transformation" from the parent, its coordinate system will the same, and consistent to the position of that parent; since the parent has its origin point relatively placed on the pixmap item bounding rect, the text is finally shown at the wanted position.
The above can be achieved using a QGraphicsRectItem used as the "proxy container": the trick is to use a null rect (no size) that will obviously cause no drawing.
As above, a basic python example:
pmItem = scene.addPixmap(QPixmap(path))
container = QGraphicsRectItem(0, 0, 0, 0, pmItem)
container.setX(pmItem.boundingRect().right())
container.setFlag(QGraphicsItem.ItemIgnoresTransformations)
textItem = QGraphicsSimpleTextItem('Hello world!', container)
textItem.setX(-textItem.boundingRect().width())
An alternative that provides reusability is to create a QGraphicsItem subclass, then we will:
boundingRect()
(returning an empty QRectF) and paint()
(which does nothing);x
of the text item to the negative width of its bounding rect;x
to the right of its bounding rect;class AlignedTextItem(QGraphicsItem):
def __init__(self, parent):
super().__init__(parent)
self.setX(parent.boundingRect().right())
self.setFlag(QGraphicsItem.ItemIgnoresTransformations)
self.textItem = QGraphicsSimpleTextItem('Hello world!', self)
self.textItem.setX(-self.textItem.boundingRect().width())
def boundingRect(self):
return QRectF()
def paint(self, qp, opt, widget=None):
pass
class PixmapItem(QGraphicsPixmapItem):
def __init__(self, pixmap):
super().__init__(pixmap)
self.alignedTextItem = AlignedTextItem(self)
# this is not a real override...
def setPixmap(self, pixmap):
super().setPixmap(pixmap)
self.alignedTextItem.setX(self.boundingRect().right())