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

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.

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.

Creating chroma key for qwebview in Qt5

I'm trying to create a chroma key for a qwebview in Qt5. This means I need to make a specific color be transparent (other widgets should be visible through webview's pixels with that color). I've found that it can be done using QPainter::CompositionMode operations, but can't make it work.
For example, I need to make all black pixels of a webview be transparent (the source color should be changed in runtime).
I've reimplemented QWebView::paintEvent in my class (get a part of a code from Qt sources), but don't know what to do next
WebView::paintEvent(QPaintEvent *event) {
if (!page()) return;
QWebFrame *frame = page()->mainFrame();
QPainter painter(this);
painter.setRenderHints(renderHints());
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
frame->render(&painter, event->region());
}
I found a way how to make any source color be white with the following code:
QWebFrame *frame = page()->mainFrame();
QImage source_image(size(), QImage::Format_ARGB32_Premultiplied);
QImage result_image(size(), QImage::Format_ARGB32_Premultiplied);
QPainter imagePainter(&source_image);
imagePainter.setRenderHints(renderHints());
frame->render(&imagePainter, event->region());
imagePainter.end();
QImage mask = source_image.createMaskFromColor(qRgb(0x00,0x00,0x00)); // Source color
QPainter resultPainter(&result_image);
resultPainter.drawImage(source_image.rect(), source_image);
resultPainter.setCompositionMode(QPainter::CompositionMode_Screen);
resultPainter.drawImage(source_image.rect(), mask);
QPainter painter(this);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawImage(0, 0, result_image);
But I don't know how to convert a white color to transparent.
I found a solution, but it consumes a lot of CPU.
First it's required to set
setStyleSheet("QWebView { background: transparent }");
setAttribute(Qt::WA_OpaquePaintEvent, true);
somewhere in WebView's constructor (I just forgot to mention that in the first message). Then reimplement paintEvent:
void WebView::paintEvent(QPaintEvent *event)
{
if (!page())
return;
QWebFrame *frame = page()->mainFrame();
QPainter painter(this);
QColor chroma_color(0, 0, 0); // A color that should be transparent
float opacity_level = 0.9; // WebView opacity
m_render_pixmap.fill(Qt::transparent);
QPainter pixmapPainter(&m_render_pixmap);
pixmapPainter.setRenderHints(renderHints());
frame->render(&pixmapPainter, event->region());
pixmapPainter.end();
m_render_pixmap.setMask(m_render_pixmap.createMaskFromColor(
chroma_color, Qt::MaskInColor));
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setOpacity(opacity_level);
painter.drawPixmap(QPoint(event->rect().left(), event->rect().top()), m_render_pixmap, event->rect());
painter.end();
}
m_render_pixmap is an instance of QPixmap. I don't want to recreate it every time paintEvent is called. I just recreate it on resizeEvent
void WebView::resizeEvent(QResizeEvent *event)
{
QWebView::resizeEvent(event);
m_render_pixmap = QPixmap(size());
}
The code above work great but in my case I want to render a video widget below a webview. So WebView::paintEvent calls about 25 times per second and each call takes about 20-25 ms in windowed mode on my PC. And it takes about 100% of one of CPU cores in a fullscreen mode.

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.

QPainter.drawText italic writes outside of the rectangle

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.

Resources