QPainter.drawText italic writes outside of the rectangle - qt

I'm using QPainter::drawText to... well... draw some text, but I have an issue when using fonts in italic where the text is written outside of the given QRectF.
Here is a sample code:
void TestApplication::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QRectF rect(100,100,100,100);
QRectF necessary;
QFont font("Times New Roman", 30);
font.setItalic(true);
painter.setFont(font);
painter.fillRect(rect, QColor(0,255,0));
painter.drawText(rect, Qt::AlignLeft|Qt::AlignVCenter, "ff",&necessary);
}
With this, the output produced is:
As you can see, the first f is written outside of the defined QRectF. If i write enough text the same will happen in the right hand side. I already tried adding Qt::TextDontClip to the flags but it produced no effect.
Can somebody help me?
EDIT:
As explained in the answer this is the expected behavior. I've been able to work around it by using leftBearing (the explanation is in the answer) like so:
void TestApplication::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QRectF rect(20,20,100,100);
QRectF textRect;
QFont font("Times New Roman", 40,0,true);
font.setItalic(true);
painter.setFont(font);
QFontMetrics fontMetrics(font);
textRect = rect.adjusted(-fontMetrics.leftBearing('f'), 0, fontMetrics.leftBearing('f'), 0);
painter.fillRect(rect, QColor(0,255,0));
painter.drawText(textRect, Qt::AlignLeft|Qt::AlignTop, "ff");
}
Producing the expected result:

The simplest way to deal with this it to reduce the Rect before drawing the text, if the text is italiced: -
QRectF rect(100,100,100,100);
QRectF textRect = rect.adjusted(8, 0, -8, 0);
painter.drawText(textRect, Qt::AlignLeft|Qt::AlignVCenter, "ff",&necessary);
As the Qt documentation states, when passing in the boundingRect rect as the last argument: -
The boundingRect (if not null) is set to the what the bounding rectangle should be in order to enclose the whole text.
So, it's not going to necessarily be bound by the rect you pass in.
Note that the QFontMetrics class allows you to get the boundingRect required for rendering a string: -
QFont font("Times New Roman", 30);
QFontMetrics fontMetrics(font);
fontMetrics.boundingRect("ff");
However, the documentation for QFontMetrics states: -
Note that the bounding rectangle may extend to the left of (0, 0), e.g. for italicized fonts, and that the width of the returned rectangle might be different than what the width() method returns.
So what you are seeing is expected behaviour.

Related

Storing the results of QPainter rendering into a QPixmap instance

I want to apply blur effect to a gradient generated by GRadialGradient and rendered by QPainter.Looks like for such graphical effects I have to provide a pixmap and a QGraphicsScene and then call the ->render() method but I couldn't find any ways to add a QPainter directly into any subclass of QGraphicsItem.
So is there any way to do that?
I think converting the QPainter render results to QPixmap can solve the problem.But I don't know how.And I don't know how the performance of converting then applying the blur effect in real-time would be.
Here's an excerpt from what I've written so far:
void MainWindow::paintEvent(QPaintEvent *event){
Q_UNUSED(event);
QRadialGradient grad(QPoint(this->width()/2,this->height()/2) , 50);
grad.setSpread(QGradient::RepeatSpread);
grad.setColorAt(0 , QColor(0,0,0));
grad.setColorAt(1 , QColor(100,100,100));
QPainter paint(this);
paint.setRenderHint(QPainter::Antialiasing , true);
QRectF r1(0,0,this->width(),this->height());
paint.drawRect(r1);
QBrush brush(grad);
paint.fillRect(r1 , brush);
...
...
}
And here's the results:
Thanks.
Your question is clearly an XY problem, instead of asking about the underlying problem: How to display a QGraphicsItem that shows the circular gradient + blur effect, questions about an attempted solution: convert to QPixmap.
In this case, the simplest thing is to use a QGraphicsRectItem where you set the gradient as brush and the effect is applied to that item:
QGraphicsBlurEffect *effect = new QGraphicsBlurEffect(this);
effect->setBlurRadius(100);
QRadialGradient grad(QPoint(50, 50), 50);
grad.setSpread(QGradient::RepeatSpread);
grad.setColorAt(0 , QColor(0, 0, 0));
grad.setColorAt(1 , QColor(100, 100, 100));
QGraphicsRectItem *item = new QGraphicsRectItem(0, 0, 100, 100);
item->setBrush(QBrush(grad));
item->setGraphicsEffect(effect);

How do I add a border to a rounded QPixmap?

I'm trying to show a rounded avatar QPixMap with a white border around it. However, I have no clue as to how I could add the border... Is it even possible?
This is the function I have to draw the avatar image.
void AccountDropDownMenu::setAvatar(
const QByteArray& bytes)
{
QPixmap new_avatar;
new_avatar.loadFromData(bytes);
new_avatar = new_avatar.scaledToHeight(40, Qt::SmoothTransformation);
QBitmap map(new_avatar.size());
map.fill(Qt::color0);
QPainter painter(&map);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::color1);
painter.setPen(QPen(Qt::white, 10));
painter.drawRoundedRect(
m_avatar_label->x(),
m_avatar_label->y(),
new_avatar.width(),
new_avatar.height(),
45,
45);
new_avatar.setMask(map);
avatar_label->setPixmap(new_avatar);
}
Update
Thanks to dtech I was able to get the desired output using the following updated function.... Although it's a bit pixly (the border).
void AccountDropDownMenu::setAvatar(
const QByteArray& bytes)
{
QPixmap new_avatar;
new_avatar.loadFromData(bytes);
new_avatar = new_avatar.scaledToHeight(40, Qt::SmoothTransformation);
QBitmap map(new_avatar.size());
map.fill(Qt::color0);
QPainter painter(&map);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::color1);
painter.drawRoundedRect(
QRectF(
avatar_label->x(),
avatar_label->y(),
new_avatar.width(),
new_avatar.height()),
40,
40);
new_avatar.setMask(map);
QPainter painter2(&new_avatar);
painter2.setRenderHint(QPainter::Antialiasing);
painter2.setPen(QPen(Qt::white, 1));
painter2.drawRoundedRect(
QRectF(
avatar_label->x(),
avatar_label->y(),
new_avatar.width(),
new_avatar.height()),
40,
40);
avatar_label->setPixmap(new_avatar);
}
In Qt you draw fills with a brush, but outlines are drawn with a QPen.
I haven't used QPainter in a long time, but IIRC, the default pen is zero width, which would explain why you aren't getting anything - you are not setting a pen.
Also, you don't need "another" rounded rectangle, you can apply fill and outline to the same geometry, as demonstrated in this answer.
Update:
Your updated code sets a mask, which sets an alpha channel. That cuts away from what you already have, it could not possibly add anything. You have to paint on the pixmap. Simply use new_avatar as the paint device - QPainter painter(&new_avatar); and get rid of the rest.

Text is not antialiased while using QPainter::drawText()?

While i'm trying to draw text using QPainter::drawText() the text is not antialiased (comparing to MS word)
void TextLabel::paintEvent(QPaintEvent*) {
QPainter p(this);
p.setRenderHint(QPainter::TextAntialiasing);
QFont font;
font.setFamily("Roboto medium");
font.setPointSize(32);
font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias);
p.setPen(_brush);
p.setFont(font);
p.drawText(rect(), Qt::AlignLeft , _text);
}
Qt Doc says:
QPainter::TextAntialiasing -> Indicates that the engine should antialias text if possible
Is this impossible ?
What should i do ?
The word one:
The Qt one :
Seems it's an issue Qt has on Window OS (font rendering) and work with some fonts >=48pt and doesn't work with some other.
Issue : https://bugreports.qt.io/browse/QTBUG-40052
We hope they will fix it in the near future.
You can draw with QPainterPath it's more expensive but still helps :
void TextLabel::paintEvent(QPaintEvent*) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::black);
QFont font;
font.setPointSize(38);
font.setFamily("Roboto");
painter.setFont(font);
painter.drawText(0, 60, "Google");
QPainterPath textPath;
textPath.addText(0, 140, font, "Google");
painter.drawPath(textPath);
}
Roboto # 38pt :
Try painting via a QImage -- the QPainter::TextAntialiasing is more likely to be honoured that way.
QImage image(size(), QImage::Format_ARGB32_Premultiplied);
{
QPainter p(&image);
p.setRenderHint(QPainter::TextAntialiasing);
QFont font;
font.setFamily("Roboto medium");
font.setPointSize(16);
font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias);
p.setPen(_brush);
p.setFont(_font);
p.drawText(rect(), Qt::AlignLeft , _text);
}
QPainter p(this);
p.drawImage(rect(), image);
Note: if this works then the QImage used should probably be a private class member rather than recreating it every time paintEvent is invoked.

Use Clipping in Qt

Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?
The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).
My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.
If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?
The used Qt version is Qt 4.8.6
My questions are:
Is it possible to achieve what I want with the mentioned strategy?
Is it possible in any way to clip all the children, too?
Is my strategy appropriate or is it a bad Idea to solve it that way?
Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?
Additional Information
I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:
This works.
/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry()
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setPen(QColor(255, 0, 0));
painter.setBrush(QColor(255, 0, 0));
painter.setOpacity(1);
painter.drawRect(rect);
}
But the following doesn't change anything:
/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry();
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setRenderHint(QPainter::Antialiasing);
painter.setOpacity(1);
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.
The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:
case PE_Widget:
if (w && !rule.hasDrawable()) {
QWidget *container = containerWidget(w);
if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
&& (container == w || !renderRule(container, opt).hasBackground())) {
//we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
// (this may happen if we have rules like :focus)
p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
}
break;
}
As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:
if (q->testAttribute(Qt::WA_StyledBackground)) {
painter->setClipRegion(rgn);
QStyleOption opt;
opt.initFrom(q);
q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}
Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.
To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:
QPixmap pixmap(widget->size());
widget->render(&pixmap);
And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.

Draw a multi-colored line in Qt

What I'm trying to achieve is the following: I have a QGraphicsScene with a QGraphicsPixmapItem shown in it. The pixmap has multiple colors and I need to draw a line across the pixmap that must be visible and recognizable in every single point.
My idea is to draw a line where every pixel has the negative (complementary) color of the pixmap's relative pixel. So I thought about subclassing QGraphicsItem and reimplement the paint() method to draw a multi-colored line.
However I'm stuck because I don't know how I can retrieve the pixel information of the pixmap from the paint function, and even if I found out, I can't think of a way to draw the line in this way.
Could you give me some advice on how to proceed?
You can use QPainter's compositionMode property to do something like this pretty easily, without having to read the source pixel colors.
Simple sample QWidget with a custom paintEvent implementation, which you should be able to adapt to your item's paint method:
#include <QtGui>
class W: public QWidget {
Q_OBJECT
public:
W(QWidget *parent = 0): QWidget(parent) {};
protected:
void paintEvent(QPaintEvent *) {
QPainter p(this);
// Draw boring background
p.setPen(Qt::NoPen);
p.setBrush(QColor(0,255,0));
p.drawRect(0, 0, 30, 90);
p.setBrush(QColor(255,0,0));
p.drawRect(30, 0, 30, 90);
p.setBrush(QColor(0,0,255));
p.drawRect(60, 0, 30, 90);
// This is the important part you'll want to play with
p.setCompositionMode(QPainter::RasterOp_SourceAndNotDestination);
QPen inverter(Qt::white);
inverter.setWidth(10);
p.setPen(inverter);
p.drawLine(0, 0, 90, 90);
}
};
This will output something like the following image:
Experiment with the other composition modes to get more interesting effects.

Resources