Text from QPainter much nicer than from QPainterPath - c++

I want to draw text using QPainter, and I want to use QPainterPath first (because ultimately I want to rotate the text in all sorts of ways). However, I find that the text produced by QPainterPath is much uglier than the text produced by QPainter.
The following code:
void MyWidget::paintEvent(QPaintEvent* /*event*/) {
QFont font;
font.setStyleHint(QFont::Times, QFont::PreferAntialias);
font.setPointSize(30);
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::black);
painter.setFont(font);
painter.drawText(10, 40, "Hello World");
QPainterPath textPath;
textPath.addText(10, 100, font, "Hello world");
painter.drawPath(textPath);
painter.end();
}
produces the following result:
The former is clearly much cleaner and nicer, especially in smaller fonts. What should I do to get the same result from QPainterPath?
I'm producing the above results on a Windows 7 computer, with Qt 5.0.

According to the Qt documentation for adding text to a QPainterPath: -
Adds the given text to this path as a set of closed subpaths created
from the font supplied.
So there is a conversion going on here, which is why it doesn't look the same. If you need to rotate text, you could try rotating the QPainter before rendering and then restoring it afterwards. Alternatively, if you can use QGraphicsView and QGraphicsDisplay instead of just rendering onto the widget, there is the class QGraphicsTextItem which may help.
But overall, it's the conversion to the set of closed subpaths that is responsible for the different output of text quality.

The two fonts don't look the same because you're adding extra contours to you QPainterPath text.
The following piece of code should give good results :
QFont font;
font.setStyleHint(QFont::Times, QFont::PreferAntialias);
font.setPointSize(30);
QPainter painter;
painter.begin(this);
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
painter.setFont(font);
// painter text color is modified by setPen()
painter.setPen(Qt::white);
painter.drawText(10, 40, "Hello World 1");
QPainterPath textPath;
textPath.addText(10, 100, font, "Hello World 2");
// painter path text color is modified by setBrush()
painter.setBrush(Qt::white);
// setPen(Qt::white) add extra white contour on text path (what you did)
painter.setPen(Qt::white);
painter.drawPath(textPath);
QPainterPath textPath2;
textPath2.addText(10, 160, font, "Hello World 3");
// painter path text color is modified by setBrush
painter.setBrush(Qt::white);
// setPen(Qt::NoPen) avoid extra contours for QPainter Path
painter.setPen(Qt::NoPen);
painter.drawPath(textPath2);
painter.end();
I admit that QPainterPath text "Hello World 3" is a little bit uglier that QPainterText "Hello World 1", but the result is still better than "Hello World 2"

I would beg to differ with the above answers and suggest that both the addText/drawPath and the drawText approaches do the same thing and that there likely is not material 'conversion'.
As noted by Mehdi-Antoine - using drawText gives a mid sized text weight, while using addText/drawPath with both a Pen and Brush gives a heavy weight, while using addText/drawPath with only the fill gives a light weight.
Note than the QPen value has a width, defaulting to 1, and if you apply this pen to stroke the outline of the text with a width of 1, the text will appear very heavily as demonstrated by Medhi-Antoine's example.
However - you can achieve effectively identical results to drawText by using the addText/drawPath approach simply by adjusting the weight of the Pen utilized. For a font size of 30 points, Arial, setting a Pen width of 0.2 and then painting using the addText/drawPath approach seems to create something identical to the drawText approach.
It seems that the drawText method takes the colour of the Pen, then uses that to do a fill, then apply a stroke at a specific thickness.
If you're going to use the drawPath method, you'll have to provide bot the Fill and the Pen, and importantly, to adjust the width of the pen appropriately.

Related

Why QWidget::Mask based on QPixmap works fine, but Bitmap doesn't

Target: make my widget rounded, using QWidget::setMask
At first, I want to make a mask using QRegion, but later I found that, make a rounded Rect based on region is a not simple way.
So I decided to use QBitmap.
QBitmap pixmap(size());//create image
QPainter pixmapPainter(&pixmap);
QPainterPath path;
path.addRoundedRect(0, 0, width(), height(), m_borderRadius, m_borderRadius);//fill rounded rect
pixmapPainter.fillPath(path, Qt::color1);
QWidget::setMask(pixmap.mask());
Result is:
But when I change my type to QPixmap and add the fill method
QBitmap pixmap(size());//
pixmap.fill(Qt::transparent);//
QPainter pixmapPainter(&pixmap);
pixmapPainter.setRenderHint(QPainter::Antialiasing, true);
QPainterPath path;
path.addRoundedRect(0, 0, width(), height(), m_borderRadius, m_borderRadius);//
pixmapPainter.fillPath(path, Qt::white);
QWidget::setMask(pixmap.mask());
It shows me:
What's wrong with my first code?
As the comment by G.M. explains, the bitmap needs to be cleared first.
Also, a widget mask is not really the best choice here. A widget mask is intended for defining the areas where your widget will take mouse input. On most window systems, it will also cause transparency. But the edges will be aliased and not pretty. In order to make a non-rectangular widget with nice edges, one should use translucent widget background and anti-aliased painting - in addition to the widget mask for the mouse input.
This is shown and explained in Qt's shapedclock example.

What might I be doing wrong to draw text with QPainter in QT 5 using C++?

In my paintEvent method for a custom widget for a game I'm writing, After calling the various model objects' render() methods to render them to the widget, I am trying to render the "Hi-Score" text. Here's the general code for just the text drawing:
painter.fillRect(event->rect(), QColor(0, 0, 0));
painter.drawImage(QRectF(event->rect().x(), event->rect().y() + 30, 512, 512), getGameBoardImage());
//...rendering other model components
painter.setBrush(QBrush(QColor(255, 255, 255)));
//painter.setFont(getGameFont());
painter.setFont(QFont("Times New Roman", 16, QFont::Bold));
painter.drawText(0, 0, "HI-SCORE");
I 'was' trying to draw the text in a custom font loaded from resource (I found an answer on here for that) but it wouldn't even display, even with a white brush. I thought maybe it was because it was because I didn't 'set' a font, but setting it to Times New Roman doesn't display anything either. What might I be doing wrong? As you can see the background is a black background with the game board painted on top but leaving a small buffer at the top and bottom. Do I need to do something special to display the text? Please don't suggest using a QLabel because I am trying to keep it all in one widget if possible. If I must, I will split the Hi-Score and 1-Ups into 2 other label sets with specialty fonts.
you code look ok, but you are drawing at 0,0 which is the top left corner of the widget canvas AND the text is actually there but not visible...
draw instead at
painter.drawText(margin, y+margin, "HI-SCORE");
where y is the high of the font used to draw the text and margin is you know a little margin border to make it look better something like 5 units
update
you can get the value of the text you are painting doing somthing like
QFont font("times", 25);
QFontMetrics fm(font);
int pixelsW{fm.width("HI-SCORE")};
int pixelsH{fm.height()};

Qt SVG Renderer Not Rendering At Set Size?

So I am using some SVG files to show graphics in my Qt application. I load the svg file just fine into a QSVgRenderer, but when I actually render the SVG data on screen it is smaller than expected.
To make sure I wasn't doing something odd I do the following inside an empty QDialog PaintEvent function using text only below to show my issue.
QPainter p(this);
QFont f("verdana", 9);
p.setFont(f);
painter.translate(100,100);
painter.setPen("#FF0000");
QRectF br = painter.boundingRect(0,0,0,0, Qt::AlignLeft, "Hello There!);
painter.drawRect(0, -br.height(), br.width(), br.height());
painter.drawText(0,0,"Hello There!");
painter.setPen("#000000");
QByteArray svgData(QString().sprintf(
"<svg><text font-family=\"%s\" font-size=\"%dpt\" fill=\"black\">%s</text></svg>", "verdana", 9, "Hello There!"
).toStdString().c_str())
QSvgRenderer svgr(svgData);
// svgr.setViewBox(QRectF(0,0,br.width(), br.height()); // Tried this line also but did not make any difference.
// svgr.setViewBox(QRectF(0,0,br.width() * 0.68, br.height()); // Sort of makes my svg the proper width, but breaks when using other font styles.
svgr.render(&p, br);
The result I get looks like this:
Here is what it looks like with the drawText line commented out since that line is only showing the bounding rect was calculated properly:
I am looking for it to be like this:
where the "Hello There!" is the SVG item scaled to fit properly into the bounding rect area. I thought I had it working at one point by setting the viewBox to the same size as the bounding rect, but it does not work properly now unless I scale the bounding rect to be smaller.
I also tried setting width and height on the <svg> tag but that did not make a difference.
Am I doing something wrong?

Drawing a scalable QIcon using QPainter

I want to design a new QIcon and want it to look like a fixed text with a rounded rectangle around it
.-----.
| Phy |
`-----ยด
The icon is supposed to scale without the "pixel-blocks" effect when painting on a QPainter that eventually has a scale transformation applied (for example when I paint into a widget that is part of a QGraphicsView with a scale applied on its scene).
Therefor, I have difficulties knowing how I should paint my QIcon. If I do it in the following way, I will paint a QPixmap that always has a fixed amount of pixels, thus introducing the pixel-blocks effect inevitably when the scale is large enough
void MyWidget::drawIcon(QPainter *painter, QPoint pos) {
QPixmap pixmap = icon.pixmap(QSize(22, 22),
isEnabled() ? QIcon::Normal
: QIcon::Disabled,
isChecked() ? QIcon::On
: QIcon::Off);
painter->drawPixmap(pos, pixmap);
}
What I am looking for is a way similar to how QFont with drawText works. Regardless on how large my scale is, when I draw fonts it always looks sharp and I cannot detect individual pixels.
I imagine that I could tell QPainter to paint my icon into a given pixel rectangle, and QPainter transforms the rectangle itself before letting my QIconEngine::paint render the item into a possibly larger rectangle or pixmap. But I see no way how I could do something like this.
Am I just being stupid and not seeing the obvious solution?
I was indeed completely dump. I can just use QIcon::paint and pass it the rectangle. It will correctly delegate the request to the icon engine.
I do this by creating my icons/images as SVG files, and using QSvgRenderer to paint them onto a QPainter. The required classes are in the SVG module.

QTextDocument::drawContents only renders at 96 dpi

I am creating a high-resolution (1200 dpi) PDF document using QPrinter and QPainter. I am trying to draw text at the same resolution using QTextDocument::drawContents. The reason I want to use QTextDocument is because I need to include many tables and formatted text in my document.
My problem is that QTextDocument::drawContents always inserts the text at the screen resolution, which is 96 dpi in my case. All the solutions I have found thus far suggest scaling the text to achieve the correct size. However, this results in low quality text, which I cannot afford.
My question: Is there any way to draw the contents of a QTextDocument at a high resolution?
The code below creates a PDF file with 2 lines of text, one drawn using QPainter::drawText and one drawn using QTextDocument::drawContents. I have used an Arial 8pt font in order to emphasize the problem of the low quality resulting from the scaling.
// Read the screen resolution for scaling
QPrinter screenPrinter(QPrinter::ScreenResolution);
int screenResolution = screenPrinter.resolution();
// Setup the font
QFont font;
font.setFamily("Arial");
font.setPointSize(8);
// Define locations to insert text
QPoint textLocation1(20,10);
QPoint textLocation2(20,20);
// Define printer properties
QPrinter printer(QPrinter::HighResolution);
printer.setOrientation(QPrinter::Portrait);
printer.setPaperSize(QPrinter::A4);
printer.setResolution(1200);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("test.pdf");
// Write text using QPainter::drawText
QPainter painter;
painter.begin(&printer);
painter.setFont(font);
painter.drawText(textLocation1, "QPainter::drawText");
// Write text using QTextDocument::drawContents
QTextDocument doc;
doc.setPageSize(printer.pageRect().size());
QTextCursor cursor(&doc);
QTextCharFormat charFormat;
charFormat.setFont(font);
cursor.insertText("QTextDocument::drawContents", charFormat);
painter.save();
painter.translate(textLocation2);
painter.scale(printer.resolution()/screenResolution, printer.resolution()/screenResolution);
doc.drawContents(&painter);
painter.restore();
painter.end();
The QTextDocument use its own paint device for the layout which is by default at screen resolution.
You can change it with:
doc.documentLayout()->setPaintDevice(&printer);
// just before
doc.setPageSize(printer.pageRect().size());