how to draw text in QQuickItem - qt

I've been searching the internet how to draw or render text on QQuickItem but to no avail. I opt not to use QQuickPaintedItem which uses QPainter paint() function. Aside from that there is also a known issue of QQuickPaintedItem on iOS retina display devices where the display is blurred and edges were not sharp.
Please advise any possible work-around on this.

Since QtDeclarative has been deprecated already, I opt not to use
QQuickPaintedItem which uses QPainter paint() function
That statement doesn't make a lot of sense. QtDeclarative is QtQuick1, QQuickPaintedItem is part of the QtQuick2 module and has nothing to do with QtDeclarative. Furthermore, even though it uses QPainter it is still hardware accelerated using OpenGL.
Overloading a custom QQuickItem to draw text in it manually, without assistance from QPainter or any other similar class will be a very complex task.
A QQuickItem is basically the class behind QML's Item element. QML also has a Text element. QML has been designed for rapid UI development, it is entirely pointless to draw the text manually. You don't need any C++ for this, only QML:
Item {
Text {
text: "something"
}
}
Take a look at the Text element and its properties, you can specify font, color and whatnot. You can directly use the element as a source for graphics effects too.

You can use QPainter to draw on a QImage like a canvas. Then you can turn this into a QSGTexture that can be assigned to a QSGSimpleTextureNode.
Here is an excerpt of code I recently wrote to do this:
QColor color("steelblue");
QRect rect(0,0,aw, ah);
for(auto ch: m_charList){
QImage canvas(rect.width(), rect.height(), QImage::Format_RGBA8888);
canvas.fill(QColor("transparent"));
QPainter painter(&canvas);
QFont font = painter.font();
//font.setPixelSize(48);
font.setPixelSize(rect.width());
font.setBold(true);
painter.setFont(font);
painter.setPen(color);
QRect bounding = QRect(0, 0, rect.width(), rect.height());
painter.drawText(0, 0, rect.width(), rect.height(), Qt::AlignCenter, ch, &bounding);
QSGTexture *texture = this->window()->createTextureFromImage(canvas);
m_textureList[ch] = texture;
}
The repo with the full working code in context is here.
There are MANY reasons to want to render like this. You can do rotations in 3D space to QSGNodes for one.

Related

How to efficiently change a very small part of a very large QPixmap?

Drawing on a widget in Qt is simple. First you create a QPixmap, assign a QPainter to it, draw with the QPainter, and at the end set the pixmap to the widget.
For example,
QPixmap pixmap(ui->label->width(), ui->label->height());
pixmap.fill(background_color);
QPainter painter(&pixmap)
painter.setpen(foreground_color);
//painter.drawLine(....) etc.
ui->label->setPixmap(pixmap);
So far so good.
However, what if the pixmap is very large, and I have to change a very small portion of it very often? In my case I profiled it to be my bottleneck. Instead of creating a new pixmap and copying the old onto it, we can load the existing pixmap and only make the necessary changes to it. However, it is also still very slow.
Pixmap pixmap = *(ui->label->pixmap()); // takes almost zero time
QPainter painter(&pixmap); // takes up 40% of the time
//draw stuff with painter // takes almost zero time
ui->label->setPixmap(pixmap); // takes up 60% of the time
Even if I re-use the painter so that I don't have to create it again every time, I still have to call setPixMap(pixmap), otherwise the image is not refreshed.
Is there a way around it?
Must I embed an OpenGL window or something similar, or is there a way with just using native Qt classes? Note, that the change in the image is very small and takes up an insignificant amount of time, it's the redrawing of the whole pixmap which is time-consuming. As I don't know beforehand where I'll be making the changes, splitting the pixmap up onto multiple small labels would be very complicated.
If you're using a large QLabel, then it would be better using a QGraphicsScene and QGraphicsView.
The scene allows you to add items, and the view is a widget which is like a window looking into the scene.
You could start by adding a QGraphicsPixmapItem, but I think you'd be better off deriving from QGraphicsItem, storing a QPixmap and rendering the image in its paint function.
Here's an example of the derived class header
class MyImage : public QGraphicsItem
{
public:
MyImage(QGraphicsItem* parent);
QRectF boundingRect();
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
protected:
void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent * event)
private:
QPixmap m_pixmap;
}
In the paint function, use the QPainter to draw m_pixmap;

How to change QPushButton icon/text spacing with stylesheets?

I'm fairly new to Qt's method of stylesheets and I am looking to adjust the spacing between the icon and text on a QPushButton.
This is the gap I'm referring to: http://imageshack.us/scaled/thumb/593/4kem.png (stackoverflow won't let me post pics yet)
QPushButton {
qproperty-icon: theme_url("/button_action/add");
qproperty-iconSize: 14px;
}
Is there a parameter I can use to adjust this space? I've tried margin, padding, spacing? Perhaps there is a different selector that I can use to just grab the icon?
Actually with QPushButton you are out of luck. You can find a complete list of working options for styling push buttons in the qt documentation ( I am assuming you use qt5, but it is available for other versions as well).
What is often recommended if you want more control over the icon is to exchange your QPushButtons with QToolButtons wich are exactly the same except that they have extra features to make them compatible with toolbars -and- they have more options for icon placement. But this might not work for you because as you can see they are styled the exact same way as QPushButtons. But do look into the "toolButtonStyle" parameter which allows you to move the icon around.
Next on the list comes changing the artwork. If all you want is more space then just add the extra empty pixels to the artwork. This really hurts the perfectionist's mind but is effective.
Next on the list comes subclassing QPushButton and overriding the paint yourself. Sounds more daunting than it is. Subclassing in Qt has become a habit already and it works great. Here is a tutorial i googled in a jiffie (for 4.8 but should be about the same for 5) on the matter of subclassing. It even covers overriding the paint event.
Hope this was helpful.
At least with Qt5.5 (this may change in the future, I hope), besides overriding the paint event, which is complex and in my opinion messy for just a simple thing, there's no inherent Qt way to move that icon a few pixels here or there (small adjustments). If you remove the icon and use a stylesheet to set the background to your icon in your resource URL, you can use background-position property in the stylesheet, but unfortunately it only accepts words like center, left, etc., not pixel adjustments.
Here's how I resolved this problem. I created transparent PNGs that were slightly larger than I needed, where I had like a 4px margin around the icon itself inside the PNG. Then, I just moved the icon inside that canvas and reloaded it into the project resources file, and then mapped it again with the icon property in the designer for the QPushButton, and that worked. I now have icons that line up properly on the left with the text beside it. I can even widen the gap between icon and text.
As of Qt5.7 I don't know of any built-in functionality for the icon/text spacing.
What I usually do is give the image some more space to the right and set the icon size of the button accordingly.
Solution:
Resize your icon file to 32*16 px but leave its content as is.
Call setIconSize(QSize(32, 16)) on your button.
Result: Now there's 16 pixel empty space between its icon and text. 32 pixel is just my example, you can of course set the additional size to something that suites your style.
If you want to apply that on all your button elements simply create a tiny subclass that does it automatically in the constructor. There's really no need to override the paintEvent method for this.
Here is a workaround way to achieve that 'space between text and image':
QPushButton{
border:0px;
image: /*your url*/;
padding-top:16px;
image-position:top;
text-align:bottom;
padding-bottom:10px;
}
since qss doesn't support relative size, you need to implement resize() to change stylesheet when size changing to get a fixed space between text and image.
Here's an opportunistic approach to customize the spacing between text and icon. There's currently no regular way to change the width, so we need to add a transparent area to the right of the icon which is applied to paint device.
Firstly, add a new member variable "m_spacing"(double) and initialize it to 0.0 in constructor, as a factor of expanding the width.
Then, override the "paintEvent", "sizeHint" and "minimumSizeHint".
class CPushButton : public QPushButton {
Q_OBJECT
public:
double spaceRatio() const;
void setSpaceRatio(double ratio);
protected:
double m_spacing;
void paintEvent(QPaintEvent *event) override;
......
QSize CPushButton::sizeHint() const {
QSize sz = QPushButton::sizeHint();
int offset = iconSize().width() * m_spacing;
return QSize(sz.width() + offset, sz.height());
}
QSize CPushButton::minimumSizeHint() const {
QSize sz = QPushButton::minimumSizeHint();
int offset = iconSize().width() * m_spacing;
return QSize(sz.width() + offset, sz.height());
}
void CPushButton::paintEvent(QPaintEvent *event) {
QSize sz = iconSize();
QPixmap tmp = icon().pixmap(sz); // Get the pixmap to apply with right size
sz.rwidth() *= 1 + m_spacing; // Multiply width
QPixmap exp(sz); // Expended
exp.fill(Qt::transparent);
QPainter painter(&exp);
painter.drawPixmap(QRect(QPoint(), tmp.size()), tmp);
QStylePainter p(this); // From Qt source
QStyleOptionButton option; // From Qt source
initStyleOption(&option); // From Qt source
option.icon = QIcon(exp); // Change to real icon
option.iconSize = sz; // Change to real size
p.drawControl(QStyle::CE_PushButton, option); // From Qt source
Q_UNUSED(event)
}
Now you can create a CPushButton instance and set the spacingRatio to 0.1 or other positive real. Visually, it does increase the spacing between text and icon.
The simplest way to go for it is to create your own class inheriting from the QPushButton and override the PaintEvent and then manually place the pixmap of the icon where ever you want.
Best way to solve this problem is adding some blank space in the beginning of QPushButton text. it works well for me!
See here

QT - overriding QToolButton paint event

I have own widget MyToolButton which inherit from QToolButton. In paintEvent I want draw only "background" from original QToolButton. I mean, I need draw only style (clicked, hover, etc) but without text and image. These things I want draw by my self (with custom formatting etc). When I call QToolButton::paintEvent(ev) then everything is painted. How to exclude image and text from painting? I supose that I should use drawControl(), drawPrimitive() and drawComplexControl() but can't understands all these states
Source code of void QToolButton::paintEvent(QPaintEvent *) is:
QStylePainter p(this);
QStyleOptionToolButton opt;
initStyleOption(&opt);
p.drawComplexControl(QStyle::CC_ToolButton, opt);
QStylePainter::drawComplexControl calls QStyle::drawComplexControl, which is a pure virtual function, and its implementation depends on a style, you are using. What you need is to go to a source code of concrete impementation (like QWindowsXPStyle or QGtkStyle), find drawComplexControl() implementation and use it in your paintEvent. For a simple control like QPushButton, you can easy rewrite its drawing quite style-independently. Redrawing QToolButton would be a bit more tricky task.
A good choice would be using source code of QCommonStyle.

Render Image into subclassed QDeclarativeItem using OpenGL

I am trying to perform several image manipulations using OpenGL ES 2.0 and display the output into a subclassed QDeclarativeItem which will then be used within my QML GUI.
I read through the answers found here: http://developer.qt.nokia.com/forums/viewthread/4109
and succeeded in drawing a red-to-blue colored rectangle within my QML GUI by overriding the paint() methode of the QDeclarativeItem:
void GLDeclarativeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->beginNativePainting();
glBegin(GL_QUADS);
glColor3ub(0,0,255);
glVertex2d(0, 0);
glVertex2d(0, height());
glColor3ub(255,0,0);
glVertex2d(width(), height());
glVertex2d(width(), 0);
glEnd();
painter->endNativePainting();
}
However, what I am trying to achieve is to draw the image which will be handled within my custom QGLWidget as the content of the above described QDeclarativeItem (instead of the red-to-blue colored content).
Within my custom QGLWidget I am using:
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
for drawing, which worked fine. However, I can not find the conversion between the drawing within my GLWidget and my GLDeclarativeItem. All the things I have tried so far just gave me a rectangle without any content at all.
Thanks for your help!
I did a similar thing, but solved differently: I used an overlay.
I simply put the OpenGL rendering in its own widget which is placed on top of the QDeclarativeView.
This works quite well, although you cannot draw ontop of the OpenGL rendering. If you should really need to, stack over another QML view with a translucent background.
Good luck.

Optimized Line drawing in QT

I am new to QT. i am working on the Graphics.
i am using QWidget for drawing graphics(For drawing graphics in QWidget paint event). i need to draw background and foreground graphics. Background is fixed graphics. foregrounds i am drawing lines.
Each 100 millisecond i need to draw 20points. This drawing time is 8 sec. Total i need to draw 1600 points (total points represents the contentious line).
i am using QTimer to invoke this drawing in each 100ms. first few drawing drawn very fast. in the middle of the drawing it's become slow.
the problem is i need to draw all the foreground and background in each 100ms.
Please help me to fix the problem. if any one have sample code please provide. Thanks in advance.
Is there any way to draw only partial area ie. only particular modified region of the graphics?
QPainter-drawing can be very slow without hardware support. Using QGraphicsView won't help if all lines are visible, since it internally uses QPainter anyway.
If you just have to draw 20 new points (or lines) per update and per update background gets cleared so you have to render everything again, there are few things you could try:
1) Disable background autofill. See: QWidget::autoFillBackground
Add something like this to your widget init:
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setAttribute(Qt::WA_NoSystemBackground, true);
Now on the first update render background and first lines. For next updates just skip rendering background and render only new lines.
2) Use double buffering. For example, create QImage of the size of your widget.
.h
private:
QImage m_targetImage;
.cpp
// constructor
m_targetImage = QImage(width(), height(), QImage::Format_ARGB32);
// paint event
// draw to image
QPainter p;
p.begin(&m_targetImage);
static bool firstUpdate = true;
if (firstUpdate)
{
// draw background)
p.drawImage(...);
firstUpdate = false;
}
// draw latest lines
p.drawLines(....);
p.end();
// draw image in widget paint
QPainter painter;
painter.begin(this);
painter.drawImage(0, 0, m_targetImage);
painter.end();
3) Use QGLWidget if possible. Inherit your widget from QGLWidget instead of QWidget. This method doesn't work on all platforms and speed increase might not be enough. Also using OpenGL brings all kind of new problems.

Resources