QPainter set QBrush to drawText in QT/C++ - c++

I posted before about text not displaying - I got that working but by a slightly different method - setting a stylesheet(around the constructor). The thing is - I am trying to set up a fadeout by setting the brush color to a calculated and decreasing fraction of the total color at initial display(#bbbbbb). What I see though because I use the stylesheet is the text appearing for 10 seconds (as that's the planned fadeout cycle) and then it disappears without fading out. But without the stylesheet setting color: #bbbbbb;, the text will not show. Here is my code for that text:
if((getFrame() >= 0) && (getFrame() < 10))
{
int iColorComponent = ((10 - getFrame()) / 10) * 0xbb;
painter.setBrush(QBrush(QColor(iColorComponent, iColorComponent, iColorComponent), Qt::SolidPattern));
painter.drawText(245, 330, 125, 15, Qt::AlignCenter, tr("Ready"));
}
getFrame() returns a qint64 so it initializes as -1 and when things start then, it is set to 0. There's a QTimer firing off every 250 ms and in that method it increments the frame field and calls update(). This is inside my paintEvent() protected method where this drawText is. I'm using QT 5.15 and C++ 11 if any of that makes a difference.
Any thoughts?

qt use QPen for drawing text. Set a pen instead of brush for QPainter:
painter.setPen(QPen(QColor(iColorComponent, iColorComponent, iColorComponent), Qt::SolidPattern));
painter.drawText(245, 330, 125, 15, Qt::AlignCenter, tr("Ready"));

Related

Qt QPainter in millimetres instead of inches

I have a QPrinter that prints A4 either directly to a physical printer or a PDF. Now I'd like to use QPainter to draw in millimetres, but the current coordinate system seems to be the width and height of an A4 in inches times the resolution of the printer.
8.26 inch x 1200 res = 9912
11.69 inch x 1200 res = 14028
I have tried the following but text just ended up huge.
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(QRect(0, 0, page.width(), page.height()));
How do I change this so my QPainter can draw to 210 x 297 mm instead of the above system?
This is on Windows 10 and with Qt 5.10.
I tested this method on X11 (ubuntu linux) PDF print, using ScreenResolution printer mode:
painter.begin(printer);
int log_w = 210;
int log_h = 297;
painter.setWindow(0, 0, log_w, log_h);
int phys_w = printer->width();
int phys_h = printer->height();
painter.setViewport(0, 0, phys_w, phys_h);
Basically, set your logical size in mm using the painter window, and give the painter's viewport the printer's physical size.
This line should print a rectangle around the page with a border of 10 mm:
painter.drawRect(10, 10, log_w - 20, log_h -20);
Text should work accordingly. This code should print the word Ok at the top left corner of the rectangle:
QFont font = painter.font();
font.setPointSize(10); //1 cm height
painter.setFont(font);
painter.drawText(10, 20, "Ok");
painter.end();
Using HighResolution printer mode, font size must be set using
font.setPixelSize(10); //1 cm height
and a QPen must be set to the painter:
QPen pen(Qt::black);
pen.setWidthF(0.2);
painter.setPen(pen);
painter.drawRect(10, 10, log_w - 20, log_h - 20);
About loss of device dependency using setPixelSize, I'm aware that here is stated:
It is possible to set the height of characters shown on the screen to
a specified number of pixels with setPixelSize(); however using
setPointSize() has a similar effect and provides device independence.
but I think it refers to screen only, given that here is stated:
When rendering text on a QPrinter device, it is important to realize
that the size of text, when specified in points, is independent of the
resolution specified for the device itself. Therefore, it may be
useful to specify the font size in pixels when combining text with
graphics to ensure that their relative sizes are what you expect.
I think that you are looking for the QTransform class, according to the official doc:
The QTransform class specifies 2D transformations of a coordinate
system. A transformation specifies how to translate, scale, shear,
rotate or project the coordinate system, and is typically used when
rendering graphics.
You can initialise your custom transform class:
QTransform transform = QTransform::fromScale(painter.device()->physicalDpiX() / scale, painter.device()->physicalDpiY() / scale);
A think that this could be helpfull, the number of dots per militmeter:
const int dot_per_millimeter = qRound(qApp->primaryScreen()->physicalDotsPerInch() / 25.40);
Customise then your scale & apply it using a QPainter:
QPainter painter(parent);
painter.setWorldTransform(transform, false);
Your approach is correct. Here is an example of how to set up a printer/painter pair. I don't fiddle around with the transformation matrix since it's sufficient to specify a window/viewport pair. I don't even specify the viewport explicitly, since it is automatically set to the metrics of the paint device (in this case the QPrinter object).
#include <QPrinter>
#include <QPainter>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QPrinter printer(QPrinter::PrinterResolution);
printer.setOrientation(QPrinter::Portrait);
printer.setPageSize(QPageSize(QPageSize::A4));
printer.setResolution(300 /*dpi*/);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("ellipse.pdf");
QPainter painter(&printer);
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(page.toRect());
// Draw a 5mm thick ellipse across the whole page.
painter.setPen(QPen(Qt::black, 5.0));
painter.drawEllipse(0, 0, 210, 297);
return 0;
}
It is hard to tell what goes wrong in your case without seeing the rest of the code

Improve performance custom progress bar animation

I am looking to have a customized progress bar whose progress changes via a custom animation. I will have quite a number of instances of this widget and all of them should run smoothly and fast.
My first attempt was to use a regular QProgressBar, customize it by using a stylesheet and then using QPropertyAnimation to animate the change in status.
This works fine but is extremely slow. Say, I start my animation at a value of 0% and go up to 50% and want this to be accomplished during a duration of 500 msec. It is not smooth at all, but there are three clearly distinguishable steps. If I drop the stylesheet, it will work smoothly enough.
Well, what seems to work fine is using a derived class of QProgressBar, it is much faster than using stylesheets, although I have to custom-adjust the width and height:
void ColorBar::paintEvent(QPaintEvent *pe)
{
QRect region = pe->rect();
QPainter painter(this);
QColor borderColor;
borderColor.setNamedColor("#a0a0a0");
QColor lightColor = QColor(255, 255, 255);
QColor darkColor = QColor(225, 225, 225);
int barHeight = static_cast<int>(height() * 1. / 4. + 0.5);
QRect drawRect(0, static_cast<int>(height() / 2. - barHeight / 2. + 0.5), width() * .9 * value() / maximum(), barHeight);
QLinearGradient g(drawRect.topLeft(), drawRect.bottomLeft());
g.setColorAt(0., lightColor);
g.setColorAt(1., darkColor);
painter.setPen(QPen(borderColor));
painter.setBrush(QBrush(g));
painter.drawRect(drawRect);
}
Animating this bar is then straightforward and fast:
QPropertyAnimation* x = new QPropertyAnimation(percentageBar, "value");
x->setStartValue(percentageBar->value());
x->setEndValue(newValue);
x->setDuration(500);
x->start();
Still open for feedback or better solutions!

Scaling items and rendering

I am making a small game in C++11 with Qt. However, I am having some issues with scaling.
The background of my map is an image. Each pixel of that image represents a tile, on which a protagonist can walk and enemies/healthpacks can be.
To set the size of a tile, I calculat the maximum amount like so (where imageRows & imageCols is amount of pixels on x- and y-axis of the background image):
QRect rec = QApplication::desktop()->screenGeometry();
int maxRows = rec.height() / imageRows;
int maxCols = rec.width() / imageCols;
if(maxRows < maxCols){
pixSize = maxRows;
} else{
pixSize = maxCols;
}
Now that I have the size of a tile, I add the background-image to the scene (in GameScene ctor, extends from QGraphicsScene):
auto background = new QGraphicsPixmapItem();
background->setPixmap(QPixmap(":/images/map.png").scaledToWidth(imageCols * pixSize));
this->addItem(background);
Then for adding enemies (they extend from a QGraphicsPixMapItem):
Enemy *enemy = new Enemy();
enemy->setPixmap(QPixmap(":/images/enemy.png").scaledToWidth(pixSize));
scene->addItem(enemy);
This all works fine, except that on large maps images get scaled once (to a height of lets say 2 pixels), and when zooming in on that item it does not get more clear, but stays a big pixel. Here is an example: the left one is on a small map where pixSize is pretty big, the second one has a pixSize of pretty small.
So how should I solve this? In general having a pixSize based on the screen resolution is not really useful, since the QGrapicsScene is resized to fit the QGraphicsView it is in, so in the end the view still determines how big the pixels show on the screen.
MyGraphicsView w;
w.setScene(gameScene);
w.fitInView(gameScene->sceneRect(), Qt::KeepAspectRatio);
I think you might want to look at the chip example from Qt (link to Qt5 but also works for Qt4).
The thing that might help you is in the chip.cpp file:
in the paint method:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
where painter is simply a QPainter and option is of type QStyleOptionGraphicsItem. This quantity gives you back a measure of the current zoom level of your QGraphicsView and thus as in the example you can adjust what is being drawn at which level, e.g.
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
[...]
if (lod >= 2) {
QFont font("Times", 10);
font.setStyleStrategy(QFont::ForceOutline);
painter->setFont(font);
painter->save();
painter->scale(0.1, 0.1);
painter->drawText(170, 180, QString("Model: VSC-2000 (Very Small Chip) at %1x%2").arg(x).arg(y));
painter->drawText(170, 200, QString("Serial number: DLWR-WEER-123L-ZZ33-SDSJ"));
painter->drawText(170, 220, QString("Manufacturer: Chip Manufacturer"));
painter->restore();
}
Does this help?

Insane CPU usage in QT 5.0

I'm having trouble using the QT framework, particularly with the paintEvent of QWidget. I have a QWidget set up, and am overriding the paintEvent of it. I need to render a bunch of rectangles (grid system), 51 by 19, leading to 969 rectangles being drawn. This is done in a for loop. Then I also need to draw an image on each on of these grids. The QWidget is added to a QMainWindow, which is shown.
This works nicely, but it's using up 47% of CPU per window open! And I want to allow the user to open multiple windows like this, likey having 3-4 open at a time, which puts the CPU close to 150%.
Why does this happen? Here is the paintEvent contents. The JNI calls don't cause the CPU usage, commenting them out doesn't lower it, but commenting out the p.fillRect and Renderer::renderString (which draws the image) lowers the CPU to about 5%.
// Background
QPainter p(this);
p.fillRect(0, 0, this->width(), this->height(), QBrush(QColor(0, 0, 0)));
// Lines
for (int y = 0; y < Global::terminalHeight; y++) {
// Line and color method ID
jmethodID lineid = Manager::jenv->GetMethodID(this->javaClass, "getLine", "(I)Ljava/lang/String;");
error();
jmethodID colorid = Manager::jenv->GetMethodID(this->javaClass, "getColorLine", "(I)Ljava/lang/String;");
error();
// Values
jstring jl = (jstring) Manager::jenv->CallObjectMethod(this->javaObject, lineid, jint(y));
error();
jstring cjl = (jstring) Manager::jenv->CallObjectMethod(this->javaObject, colorid, jint(y));
error();
// Convert to C values
const char *l = Manager::jenv->GetStringUTFChars(jl, 0);
const char *cl = Manager::jenv->GetStringUTFChars(cjl, 0);
QString line = QString(l);
QString color = QString(cl);
// Render line
for (int x = 0; x < Global::terminalWidth; x++) {
QColor bg = Renderer::colorForHex(color.mid(x + color.length() / 2, 1));
// Cell location on widget
int cellx = x * Global::cellWidth + Global::xoffset;
int celly = y * Global::cellHeight + Global::yoffset;
// Background
p.fillRect(cellx, celly, Global::cellWidth, Global::cellHeight, QBrush(bg));
// String
// Renders the image to the grid
Renderer::renderString(p, tc, text, cellx, celly);
}
// Release
Manager::jenv->ReleaseStringUTFChars(jl, l);
Manager::jenv->ReleaseStringUTFChars(cjl, cl);
}
The problem you're having is that every time you call a draw function on QPainter, it has an overhead in setting up the painter for what it needs to draw, especially when the pen and brush needs changing. As you're calling the function over 900 times, that's over 900 times that Painter needs to change its internal properties for rendering and would explain why commenting out the drawRect and drawString functions reduce the CPU usage.
To solve this problem you need to batch up all the same types of paint calls, where a type uses the same brush and pen. To do this you can use the class QPainterPath and add the objects that you need with functions such as addRect(..); ensuring you do this outside of the paint function!
If, for example, you were going to draw a chess board pattern, you would create two QPainterPath objects and add all the white squares to one and all the black to another. Then when it comes to drawing use the QPainter function drawPath. This would only require two calls to draw the entire board.
Of-course, if you need each square to be a different colour, then you're still going to have the same problem, in which case I suggest looking to generate an image of what you need and rendering that instead.

Qt round rectangle, why corners are different?

I try to draw a round rectangle with drawRoundedRect method directly in a QPixmap (no render engine involve here exept pure Qt one ...), I double check the size of the rectangle versus the size of my pixmap :
Pixmap : QSize(50, 73)
Rectangle: QRect(0,0 48x11)
See plenty of space ...
EDIT: some code
pixmap = QPixmap(50,73); //example size that match my case
QRectF rect(0,0,48,11);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setWorldMatrixEnabled(false);
painter.setPen(QPen()); //no pen
painter.setBrush(QBrush(color));
painter.drawRoundedRect(rect, 2.0, 2.0);
I disabled world transformation ...
I set set transformation to unity ...
I tried several radius (1.0,2.0,3.0,4.0) ...
I change pen width, brush color ...
But it always ends with a rectamgle with 4 diferent corners ! Like that :
I directly ouptut the pixmap to a file to be sure I wasn't scraping it during the display ... same shape.
Anyone know about Qt round rectangle with small radius ? I saw somthing about it a long time ago but I don't remenber how to deal with it !
It looks like you're not using anti-aliasing (i.e. the QPainter::Antialiasing render hint). This is a Qt quirk that occurs without it. From what I've seen/heard, the Qt devs aren't terribly concerned with fixing this (most people want anti-aliasing anyway).
The work-around (besides just using anti-aliasing) is to draw the rect yourself with QPainter::drawLine() and QPainter::drawArc(). You might have to play with numbers until it looks right -- straight calculations tend to come out a pixel or two off. Also, you might find that even with this method the lower right corner is never exactly the same as the other corners.
If you're feeling mildly ambitious, you could try fixing this and submitting a patch to Qt.
Update: Arc drawing results changed in Qt 5. In my experience, it's a big improvement.
I know this is an old problem but for Qt5 users calling setRenderHint(QPainter::Qt4CompatiblePainting); on the QPainter seems to solve the problem.
Edit:
I found a solution for generating a perfect rounded rectangle together with border color and it looks the same as the rounded rectangles used by QPushButton's border for example. This is how I implemented the paintEvent to achieve this:
void MyButtonGroup::paintEvent(QPaintEvent * e)
{
int borderSize = 5;
QColor borderColor = Qt::red;
QColor backgroundColor = Qt::blue;
int borderRadius = 3;
QPen pen;
pen.setWidth(borderSize);
pen.setColor(borderColor);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(pen);
QRectF rect(rect().x() + borderSize / 2,
rect().y() + borderSize / 2,
rect().width() - borderSize,
rect().height() - borderSize);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderSize,
borderSize);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QBrush brush(backgroundColor);
pen.setBrush(brush);
painter.setBrush(brush);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderRadius,
borderRadius);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QWidget::paintEvent(e);
}
I'm posting this because I found it a bit hard to achieve this result:
Try adding half a pixel offset (e.g.: rect.translated(0.5,0.5) ):
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,false);
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0, 2.0 );
I suppose this has to do with the coordinate system placing an integer value between two pixels.
If you draw with antialiasing and use a pen of 1 pixel width then drawing at exact integer coordinates results in lines of 2 pixel width instead.
Only with this 0.5 pixel offset you'll get lines that are exactly 1 pixel wide.
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setBrush(Qt::NoBrush);
painter.setPen( Qt::white );
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0,2.0 );
Best way do draw RoundRect is Path.
http://developer.nokia.com/community/wiki/Qt_rounded_rect_widget
void fillRoundRect(QPainter& painter, QRect r, int radius)
{
painter.setRenderHint(QPainter::Antialiasing,true);
QPainterPath rounded_rect;
rounded_rect.addRoundRect(r, radius, radius);
painter.setClipPath(rounded_rect);
painter.fillPath(rounded_rect,painter.brush());
painter.drawPath(rounded_rect);
}
try to play with render hints
1) disable antiAliasing;
2) enable SmoothPixmapTransform
but still no guarantee that it will help.
I have tried all tips from answers here but nothing works for me. But based on these code snippets I have found following solution:
As default set m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true) and only for rounded rectangles with width%2==0 disable it.
QRect rect = ConvertRectangle(rectangle);
int nPenWidth = m_pPainter->pen().width();
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, false);
m_pPainter->drawRoundedRect(rect, dbRadiusX, dbRadiusY);
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true);