QPixmap paint performance issue - c++

I need to recreate the Windows 7 theme where the application window header is transparent and displays blurred screen contents. My idea was to capture the screen contents and display them blurred in the header. For that reason I extended QQuickPaintedItem.
Here is the header:
class DesktopImage : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(int desktopX READ desktopX WRITE setDesktopX NOTIFY desktopXChanged)
Q_PROPERTY(int desktopY READ desktopY WRITE setDesktopY NOTIFY desktopYChanged)
public:
explicit DesktopImage(QQuickItem *parent = nullptr);
void paint(QPainter *painter) override;
int desktopX() const;
void setDesktopX(int desktopX);
int desktopY() const;
void setDesktopY(int desktopY);
signals:
void desktopXChanged();
void desktopYChanged();
private:
void grabScreensContent();
private:
QPixmap mScreensContent;
int mDesktopX;
int mDesktopY;
};
the grabScreensContent() method do as the name suggest. The paint() method is implemented as follows:
void DesktopImage::paint(QPainter *painter)
{
QRectF target(0, 0, width(), height());
QRectF source(mDesktopX, mDesktopY, width(), height());
painter->drawPixmap(target, mScreensContent, source);
}
on the QML side, I use the type as follows:
DesktopContent {
id: desktop
desktopX: window.x
desktopY: window.y
width: parent.width
height: parent.height
}
as you can see the desktopX (desktopY) properties are bound to the window x (window y) properties so that when the user moves the window the portion of the background that needs to be drawn is properly fetched. However the drawing is not as fluid as one might expect. Here is the result:
Can someone suggest an performance improvement?

Take care of renderTarget by setting it to FramebufferObject. This basically should make it as efficient as normal QML rendering yet you can use QPainter which is sometimes conveninent.
DesktopImage::DesktopImage(QQuickItem *parent)
{
// this setting is not default
this->setRenderTarget(QQuickPaintedItem::FramebufferObject);
}
Also, only operate with the limited screen area if you don't need the whole screen. This may or may not help depending on the platform / implementation but I always limit the scope on the target first and then position the source within it. The low level painting is not necessarily very intelligent and may expect many changes across the whole area. So we should rather specify the minimum target area and just alter it (this case works like that).
void DesktopImage::paint(QPainter *painter)
{
QRectF target(mDesktopX, mDesktopY, width(), height()); // now limited
QRectF source(0, 0, width(), height()); // now within smaller target
painter->drawPixmap(target, mScreensContent, source);
}

Related

Translate screen drawing to another part of the screen

Is it possible to make it so that all drawing to an area "A" is translated to an area "B"?
For example drawing to the area(0,0)(100,100) and have it appear in area(200,200)(300,300).
The question is actually tagged with windows and graphics. This might have been targeted to Win32 and GDI (where I've unfortunately nearly no experience). So, the following might be seen as proof of concept:
I couldn't resist to implement the idea / concept using QWindow and QPixmap.
The concept is:
open a window fullscreen (i.e. without decoration)
make a snapshot and store it internally (in my case a )
display the internal image in window (the user cannot notice the difference)
perform a loop where pixmap is modified and re-displayed periodically (depending or not depending on user input).
And this is how I did it in Qt:
I opened a QWindow and made it fullscreen. (Maximum size may make the window full screen as well but it still will have decoration (titlebar with system menu etc.) which is unintended.)
Before painting anything, a snapshot of this window is done. That's really easy in Qt using QScreen::grabWindow(). The grabbed contents is returned as QPixmap and stored as member of my derived Window class.
The visual output just paints the stored member QPixmap.
I used a QTimer to force periodical changes of the QPixmap. To keep the sample code as short as possible, I didn't make the effort of shuffling tiles. Instead, I simply scrolled the pixmap copying a small part, moving the rest upwards, and inserting the small stripe at bottom again.
The sample code qWindowRoll.cc:
#include <QtWidgets>
class Window: public QWindow {
private:
// the Qt backing store for window
QBackingStore _qBackStore;
// background pixmap
QPixmap _qPixmap;
public:
// constructor.
Window():
QWindow(),
_qBackStore(this)
{
showFullScreen();
}
// destructor.
virtual ~Window() = default;
// disabled:
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
// do something with pixmap
void changePixmap()
{
enum { n = 4 };
if (_qPixmap.height() < n) return; // not yet initialized
const QPixmap qPixmapTmp = _qPixmap.copy(0, 0, _qPixmap.width(), n);
//_qPixmap.scroll(0, -n, 0, n, _qPixmap.width(), _qPixmap.height() - n);
{ QPainter qPainter(&_qPixmap);
qPainter.drawPixmap(
QRect(0, 0, _qPixmap.width(), _qPixmap.height() - n),
_qPixmap,
QRect(0, n, _qPixmap.width(), _qPixmap.height() - n));
qPainter.drawPixmap(0, _qPixmap.height() - n, qPixmapTmp);
}
requestUpdate();
}
protected: // overloaded events
virtual bool event(QEvent *pQEvent) override
{
if (pQEvent->type() == QEvent::UpdateRequest) {
paint();
return true;
}
return QWindow::event(pQEvent);
}
virtual void resizeEvent(QResizeEvent *pQEvent)
{
_qBackStore.resize(pQEvent->size());
paint();
}
virtual void exposeEvent(QExposeEvent*) override
{
paint();
}
// shoot screen
// inspired by http://doc.qt.io/qt-5/qtwidgets-desktop-screenshot-screenshot-cpp.html
void makeScreenShot()
{
if (QScreen *pQScr = screen()) {
_qPixmap = pQScr->grabWindow(winId());
}
}
private: // internal stuff
// paint
void paint()
{
if (!isExposed()) return;
QRect qRect(0, 0, width(), height());
if (_qPixmap.width() != width() || _qPixmap.height() != height()) {
makeScreenShot();
}
_qBackStore.beginPaint(qRect);
QPaintDevice *pQPaintDevice = _qBackStore.paintDevice();
QPainter qPainter(pQPaintDevice);
qPainter.drawPixmap(0, 0, _qPixmap);
_qBackStore.endPaint();
_qBackStore.flush(qRect);
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// setup GUI
Window win;
win.setVisible(true);
// setup timer
QTimer qTimer;
qTimer.setInterval(50); // 50 ms -> 20 Hz (round about)
QObject::connect(&qTimer, &QTimer::timeout,
&win, &Window::changePixmap);
qTimer.start();
// run application
return app.exec();
}
I compiled and tested with Qt 5.9.2 on Windows 10. And this is how it looks:
Note: On my desktop, the scrolling is smooth. I manually made 4 snapshots and composed a GIF in GIMP – hence the image appears a bit stuttering.

QT 5.7 QPainter line aligment

I am working with QT 5.7 and C++.
At the moment I try to get used to draw my own widgets with the QPainter class.
But I noticed a problem I couldn't solve.
I try to draw a border line extactly at the widget border but if I do so:
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.setBrush(Qt::cyan);
QBrush brush(Qt::black);
QPen pen(brush, 2);
painter.setPen(pen);
painter.drawRect(0, 0, size().width() - 1, size().height() - 1);
painter.end();
}
The Line is at the bottom and right site bigger than the others:
And before someone is telling me I have to remove the two -1 expressions,
you should know if I do this and also set the pen width to 1 there is no line anymore at the bottom and right side.
I think this artifact is caused by the "line aligment".
QT tries to tint the the pixels near the logical lines defined by the rectangle but actually because finally all have to be in pixels it has to decide.
If I am right, why there is no method to set the line aligment of the pen like in GDI+?
And how I can solve this?
Everything depends on whether you want the entire pen's width to be visible or not. By drawing the rectangle starting at 0,0, you're only showing half of the pen's width, and that makes things unnecessarily complicated - never mind that the line appears too thin. In Qt, the non-cosmetic pen is always drawn aligned to the middle of the line. Qt doesn't let you change it: you can change the drawn geometry instead.
To get it right for odd line sizes, you must give rectangle's coordinates as floating point values, and they must be fall in the middle of the line. So, e.g. if the pen is 3.0 units wide, the rectangle's geometry will be (1.5, 1.5, width()-3.0, width()-3.0).
Here's a complete example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/widget-pen-wide-38019846
#include <QtWidgets>
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth)
qreal m_penWidth = 1.0;
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.setPen({Qt::black, m_penWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
p.setBrush(Qt::cyan);
qreal d = m_penWidth/2.0;
p.drawRect(QRectF{d, d, width()-m_penWidth, height()-m_penWidth});
}
public:
explicit Widget(QWidget * parent = 0) : QWidget{parent} { }
qreal penWidth() const { return m_penWidth; }
void setPenWidth(qreal width) {
if (width == m_penWidth) return;
m_penWidth = width;
update();
}
QSize sizeHint() const override { return {100, 100}; }
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget top;
QVBoxLayout layout{&top};
Widget widget;
QSlider slider{Qt::Horizontal};
layout.addWidget(&widget);
layout.addWidget(&slider);
slider.setMinimum(100);
slider.setMaximum(1000);
QObject::connect(&slider, &QSlider::valueChanged, [&](int val){
widget.setPenWidth(val/100.0);
});
top.show();
return app.exec();
}
#include "main.moc"

How do I reuse Qt's QTextCursor in my C++ application?

I am implementing a text editor on Windows using Qt in C++ and am using a QWidget as the surface on which I am displaying text using OpenGL. So far, I have my own layout engine and document model and am able to get the text to display on the widget.
Now I am trying to implement a text cursor that will be used similar to the one Qt provides, but QTextCursor is closely tied to its QTextDocument model and I am not able to subclass it for reuse using my model. Is there any way to reuse just the cursor without the model?
If not, how do I go about implementing a text cursor using Qt?
Note: I did go through the Caret methods that Windows provides here, but am hoping to avoid using them directly.
TL;DR: You can't. It's not a caret.
QTextCursor is an iterator, it has nothing to do whatsoever with the on-screen cursor. You can certainly reuse it if it is useful as an iterator, and iff your own text representation is built on top of QTextDocument. But it's not a caret.
The visible cursor control is provided by the QTextEdit implementation. Recall that QTextEdit is a view for a QTextDocument - it is entirely devoted to the graphical rendering of the text, controlling the visible cursor, etc.
There is no public Qt API that you can use for the caret. The WINAPI caret methods are completely useless when you're using Qt for rendering. You need your own caret implementation. Given that you already have a text representation and a renderer, you presumably have an iterator that works on the text representation, so implementing the caret should be a trivial affair.
The WINAPI caret is very simple and trivial to reimplement with the power of Qt:
class Caret : public QWidget {
Q_OBJECT
Q_PROPERTY(int period READ period WRITE setPeriod)
QPicture m_shape;
BasicTimer m_timer;
int m_period;
void updateSize() {
auto size = m_shape.boundingRect().size();
setFixedSize(size);
resize(size);
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
if (isVisible()) hide(); else show();
}
public:
QPicture defaultShape(int w, int h) {
QPicture pic;
QPainter p(&pic);
p.fillRect(0, 0, w, h, Qt::black);
return pic;
}
Caret(QWidget * parent = 0, const QPicture & pic = defaultShape()) :
QWidget(parent), m_shape(pic), m_period(250) {
setAttribute(Qt::WA_TransparentForMouseEvents);
setAttribute(Qt::WA_TranslucentBackground);
m_timer.start(m_period);
updateSize();
}
void setShape(const QPicture & pic) {
m_shape = pic;
updateSize();
update();
}
void setPeriod(int period) {
if (period < 1) {
m_timer.stop();
if (m_period > 0) show();
} else
m_timer.start(period);
m_period = period;
}
int period() const { return m_period; }
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
auto painter = QPainter(this);
painter.fillRect(rect(), Qt::transparent);
painter.drawPicture(QPoint(), m_shape);
}
};

Qt: Overlapping semitransparent QgraphicsItem

I've been working with QGraphicsView for a while and I am facing a requisite that I am not sure if it can be fulfilled by using this framework.
Putting it as simple as possible, I have 2 overlapping RectItem with a semitransparent QBrush (the same one for both). Is it possible to prevent the overlapping area from becoming more opaque? I just want the whole area to have the same color (this will occur only if both rects are fully opaque, but sometimes that is not the case)
I know it might seem a weird requisite, but the old graphics engine my colleagues used allowed it.
Any ideas?
Qt provides various blend (composition) modes for the QPainter. Deriving your RectItem class from QGraphicsItem or QGraphicsObject, allows you to customise the painting and using the composition modes, create various effects, as demonstrated in the Qt Example.
If you want two semi-transparent items overlapping without changing the colour (assuming their colour is the same), either the QPainter::CompositionMode_Difference mode, or CompositionMode_Exclusion will do this. Here's example code of such an object: -
Header
#ifndef RECTITEM_H
#define RECTITEM_H
#include <QGraphicsItem>
#include <QColor>
class RectItem : public QGraphicsItem
{
public:
RectItem(int width, int height, QColor colour);
~RectItem();
QRectF boundingRect() const;
private:
QRectF m_boundingRect;
QColor m_colour;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
};
#endif // RECTITEM_H
Implementation
#include "rectitem.h"
#include <QPainter>
RectItem::RectItem(int width, int height, QColor colour)
: QGraphicsItem(), m_boundingRect(-width/2, -height/2, width, height), m_colour(colour)
{
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsMovable);
}
RectItem::~RectItem()
{
}
QRectF RectItem::boundingRect() const
{
return m_boundingRect;
}
void RectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->setCompositionMode(QPainter::CompositionMode_Difference);
painter->setBrush(m_colour);
painter->drawRect(m_boundingRect);
}
You can now create two RectItem objects of the same semi-transparent colour and add them to the scene
// assuming the scene and view are setup and m_pScene is a pointer to the scene
RectItem* pItem = new RectItem(50, 50, QColor(255, 0, 0, 128));
pItem->setPos(10, 10);
m_pScene->addItem(pItem);
pItem = new RectItem(50, 50, QColor(255, 0, 0, 128));
pItem->setPos(80, 80);
m_pScene->addItem(pItem);

Which Qt class should I use to represent an image on a rectangle?

I have a self coded rectangle (not using QRect for educational purposes), which looks like so:
class Block {
private: // also has getters and setters for this stuff
int m_x;
int m_y;
uint m_width;
uint m_height;
QColor m_color;
public:
Block(int x = 0, int y = 0, uint w = 64, uint h = 64);
Block(const QColor &color, int x = 0, int y = 0, uint w = 64, uint h = 64);
void paint(QPainter &painter) const
{
painter.fillRect(m_x, m_y, m_width, m_height, m_color);
}
};
Now I'd like to add a support for images, so the block can either have a color or an image (if both provided, image will be used).
The problem is, there are too many classes to represent images (QPixmap, QImage, QIcon) and I have no idea which one should I use.
What are the differences, which one is best suited for simply drawing a resource image into a rectangle?
If you want to display the image on screen, use QPixmap. If you want to modify image, load or save it to file, use QImage.
QIcon is based on QPixmap and provides ability to choose one of many pixmaps based on requested size and state. QIcon is probably not what you want.
From the documentation:
Qt provides four classes for handling image data: QImage, QPixmap, QBitmap and QPicture. QImage is designed and optimized for I/O, and for direct pixel access and manipulation, while QPixmap is designed and optimized for showing images on screen. QBitmap is only a convenience class that inherits QPixmap, ensuring a depth of 1. Finally, the QPicture class is a paint device that records and replays QPainter commands.
The QIcon class provides scalable icons in different modes and states. A QIcon can generate smaller, larger, active, and disabled pixmaps from the set of pixmaps it is given. Such pixmaps are used by Qt widgets to show an icon representing a particular action.