Screenshot capture of dual monitor in Qt 5 - c++

In the context of a Qt application, I'm using the following code snippet for taking a screenshot of full desktop:
QDesktopWidget* dw = QApplication::desktop();
QPixmap pixmap = QPixmap::grabWindow(dw->winId(), 0, 0,
dw->width(), dw->height());
pixmap.save(name, "JPG", screenshot_quality);
This approach works pretty well in Linux and Windows and with dual monitor, independently of screen's resolutions; that is, it works still if the two monitors are working with different resolutions. However, with Qt 5 I get the following run-time warning:
static QPixmap QPixmap::grabWindow(WId, int, int, int, int) is deprecated, use QScreen::grabWindow() instead. Defaulting to primary screen.
So I reviewed the Qt 5 doc and I wrote this:
QScreen * screen = QGuiApplication::primaryScreen();
QPixmap pixmap = screen->grabWindow(0);
pixmap.save(name, "JPG", screenshot_quality);
But this approach does not capture the second screen.
So I searched a little more and, according to this thread, Taking Screenshot of Full Desktop with Qt5, I designed the screenshot capture as follows:
QScreen * screen = QGuiApplication::primaryScreen();
QRect g = screen->geometry();
QPixmap pixmap = screen->grabWindow(0, g.x(), g.y(), g.width(), g.height());
pixmap.save(name, "JPG", screenshot_quality);
Unfortunately, this does not work too.
What catches my attention is that the method with Qt 4 works well. Since I imagine there must be some way to make it in Qt 5.
So, my question is how can be done with Qt 5?
EDIT: This is the way as I solved:
QPixmap grabScreens()
{
QList<QScreen*> screens = QGuiApplication::screens();
QList<QPixmap> scrs;
int w = 0, h = 0, p = 0;
foreach (auto scr, screens)
{
QRect g = scr->geometry();
QPixmap pix = scr->grabWindow(0, g.x(), g.y(), g.width(), g.height());
w += pix.width();
h = max(h, pix.height());
scrs.append(pix);
}
QPixmap final(w, h);
QPainter painter(&final);
final.fill(Qt::black);
foreach (auto scr, scrs)
{
painter.drawPixmap(QPoint(p, 0), scr);
p += scr.width();
}
return final;
}
Thanks to #ddriver!

Naturally, QGuiApplication::primaryScreen() will give you a single screen.
You could use QList<QScreen *> QGuiApplication::screens() to get all screens associated with the application, take screenshots for all of them, then create another blank image, size it according to how you want to compose the screens, and manually compose into a final image using QPainter.
QPixmap grabScreens() {
auto screens = QGuiApplication::screens();
QList<QPixmap> scrs;
int w = 0, h = 0, p = 0;
foreach (auto scr, screens) {
QPixmap pix = scr->grabWindow(0);
w += pix.width();
if (h < pix.height()) h = pix.height();
scrs << pix;
}
QPixmap final(w, h);
QPainter painter(&final);
final.fill(Qt::black);
foreach (auto scr, scrs) {
painter.drawPixmap(QPoint(p, 0), scr);
p += scr.width();
}
return final;
}

Also, you can use primary screen's (desktop) virtual geometry and capture entire desktop without additional loops and calculations:
QRect desktopGeometry = qApp->primaryScreen()->virtualGeometry();
QPixmap desktopPixmap = qApp->primaryScreen()->grabWindow(qApp->desktop()->winId(), desktopGeometry.x(), desktopGeometry.y(), desktopGeometry.width(), desktopGeometry.height());
See also: QDesktopWidget
Update:
At the moment, QApplication::desktop() and QDesktopWidget are marked as obsolete by the Qt for some reason, so for new projects it's recommended to use approach with screen enumerating. Anyway, for old and current Qt versions this solution has to work as expected.

Related

Drawing border around image QT.

I am having a few issues with this code to draw a border around an image in QT, can anyone tell me what i am missing:
void imageLabel::paintEvent(QPaintEvent *event)
{
QLabel::paintEvent(event);
if (!m_qImage.isNull())
{
QImage qImageScaled = m_qImage.scaled(QSize(width(),height()),Qt::KeepAspectRatio,Qt::FastTransformation);
double dAspectRatio = (double)qImageScaled.width()/(double)m_qImage.width();
int iX = m_iX*dAspectRatio;
int iY = m_iY*dAspectRatio;
int iWidth = m_iWidth*dAspectRatio;
int iHeight = m_iHeight*dAspectRatio;
QPainter qPainter(this);
qPainter.drawImage(0,0,qImageScaled);
qPainter.setBrush(Qt::NoBrush);
qPainter.setPen(Qt::red);
qPainter.drawRect(iX,iY,iWidth,iHeight);
}
}
You can use QFrame to simplify the task of adding a frame around a widget like QLabel.
In QtCreator, simply select the label and scroll down until you see the turquoise section of the properties editor and play with the values there.
The result looks like this:
Hope this helps you along!

Reimplementing QStyledItemDelegate::paint - How to obtain subelement coordinates?

A QTreeView is rendered with the help of a custom QStyledItemDelegate::paint method. The intention is to add graphical elements to the nodes, e.g. to draw (and fill) a box around the item texts. The tree items may have check boxes, or not.
The Ruby code below achieves the goal, except that I cannot obtain the coordinates of the text element. An empirical offset (x=29; y=4) serves as a workaround. The super method draws the text on top of the box.
How can I obtain the coordinates of the text element?
Is this the right approach at all, or do I have to use drawText and drawControl instead of calling the superclass paint method? In that case, how do you control the layout of the sub elements?
(This question is not Ruby specific. Answers containing C++ are welcome.)
class ItemDelegate < Qt::StyledItemDelegate
def paint(painter, option, index)
text = index.data.toString
bg_color = Qt::Color.new(Qt::yellow)
fg_color = Qt::Color.new(Qt::black)
offset = Qt::Point.new(29,4)
painter.save
painter.translate(option.rect.topLeft + offset)
recti = Qt::Rect.new(0, 0, option.rect.width, option.rect.height)
rectf = Qt::RectF.new(recti)
margin = 4
bounding = painter.boundingRect(rectf, Qt::AlignLeft, text)
tbox = Qt::RectF.new(Qt::PointF.new(-margin,0), bounding.size)
tbox.width += 2*margin
painter.fillRect(tbox, bg_color)
painter.drawRect(tbox)
painter.restore
super
end
end
Edit: Please find a self-contained example here in this Gist.
I had the same problem in C++. Unfortunately, the workaround on option.rect.* properties seems to be the only way to find the text coords.
Here the paint method of my delegate:
void ThumbnailDelegate::paint(QPainter *p_painter, const QStyleOptionViewItem &p_option, const QModelIndex &p_index) const
{
if(p_index.isValid())
{
const QAbstractItemModel* l_model = p_index.model();
QPen l_text_pen(Qt::darkGray);
QBrush l_brush(Qt::black, Qt::SolidPattern);
/** background rect **/
QPen l_pen;
l_pen.setStyle(Qt::SolidLine);
l_pen.setWidth(4);
l_pen.setBrush(Qt::lightGray);
l_pen.setCapStyle(Qt::RoundCap);
l_pen.setJoinStyle(Qt::RoundJoin);
p_painter->setPen(l_pen);
QRect l_border_rect;
l_border_rect.setX(p_option.rect.x() + 5);
l_border_rect.setY(p_option.rect.y() + 5);
l_border_rect.setWidth(p_option.rect.width() - 16);
l_border_rect.setHeight(p_option.rect.height() - 16);
QPainterPath l_rounded_rect;
l_rounded_rect.addRect(QRectF(l_border_rect));
p_painter->setClipPath(l_rounded_rect);
/** background color for hovered items **/
p_painter->fillPath(l_rounded_rect, l_brush);
p_painter->drawPath(l_rounded_rect);
/** image **/
QPixmap l_pixmap = bytearrayToPixmap(l_model->data(p_index, ImageRole).toByteArray()).scaled(150, 150, Qt::KeepAspectRatio);
QRect l_img_rect = l_border_rect;
int l_img_x = (l_img_rect.width()/2 - l_pixmap.width()/2)+l_img_rect.x();
l_img_rect.setX(l_img_x);
l_img_rect.setY(l_img_rect.y() + 12);
l_img_rect.setWidth(l_pixmap.width());
l_img_rect.setHeight(l_pixmap.height());
p_painter->drawPixmap(l_img_rect, l_pixmap);
/** label **/
QRect l_txt_rect = p_option.rect;
l_txt_rect.setX(l_border_rect.x()+5);
l_txt_rect.setY(l_border_rect.y() + l_border_rect.height() -20);
l_txt_rect.setHeight(20);
l_txt_rect.setWidth(l_txt_rect.width()-20);
QFont l_font;
l_font.setBold(true);
l_font.setPixelSize(12);
p_painter->setFont(l_font);
p_painter->setPen(l_text_pen);
QString l_text = l_model->data(p_index, TextRole).toString();
p_painter->drawText(l_txt_rect, Qt::ElideRight|Qt::AlignHCenter, l_text);
}
else
{
qWarning() << "ThumbnailDelegate::paint() Invalid index!";
}
}
I am not skilled on Ruby but, as you can see, I am using drawPath, drawPixmap and drawText.
Here is the result:
I think it is better to avoid invoking paint from the superclass, since it should be done automatically by Qt and you may break something on the UI lifecycle.

Qt 5, get the mouse position in a screen

First of all, I'd like to mention that I found that related post How to get the mouse position on the screen in Qt? but it "just didn't work" for me. I made some tests, and the results didn't work as I expected, so I decided to make a new post to talk about the test I made and to find an alternative solution.
That's the code I used to make the test:
QScreen *screen0 = QApplication::screens().at(0);
QScreen *screen1 = QApplication::screens().at(1);
printf("screen0 %s \n", screen0->name().toStdString().c_str());
printf("screen1 %s \n", screen1->name().toStdString().c_str());
// Position on first screen.
QPoint pos0 = QCursor::pos(screen0);
// Position on second screen.
QPoint pos1 = QCursor::pos(screen1);
printf("pos 0: %d, %d \n", pos0.x(), pos0.y());
printf("pos 1: %d, %d \n", pos1.x(), pos1.y());
// Get position without screen.
QPoint pos = QCursor::pos();
printf("pos: %d, %d \n", pos.x(), pos.y());
What I was expecting, is that only one screen would return a valid position, since the cursor is only at one screen, not on both. But it's not the case, the both positions (pos0 and pos1) has the exactly same value, as we can see on the output:
screen0 DVI-D-0
screen1 HDMI-0
pos 0: 1904, 1178
pos 1: 1904, 1178
pos: 1904, 1178
Since the both positions has the same values, I can't know at which screen is the cursor. I don't know if that's a normal behavior or a bug, since the documentation doesn't say what happens when the screen argument isn't the screen where the mouse is.
My idea, is to open/launch an application (executed by a Qt daemon that must detect the selected screen) to the screen where the mouse is. I know that with libX11 it's possible, because I did it in the past, but I need to work with Qt 5, and I can't figure out how to do detect the selected screen with Qt.
I also made other tests, using QApplication and QDesktopWidget classes with no luck.
That's really weird. As a workaround, you could try this:
QPoint globalCursorPos = QCursor::pos();
int mouseScreen = qApp->desktop()->screenNumber(globalCursorPos);
Now you know which screen the cursor is in. Then you could find the cursor position within that screen doing this:
QRect mouseScreenGeometry = qApp->desktop()->screen(mouseScreen)->geometry();
QPoint localCursorPos = globalCursorPos - mouseScreenGeometry.topLeft();
This may seem like a trivial solution, but on my KDE it works (I ran into the same problems originally).
If you want to determine the local mouse coordinates with respect to a widget (this will be in device pixels and relative to the top left corner of the widget I believe) you can use
QWidget::mapFromGlobal(QCursor::pos());
i.e. call this->mapFromGlobal.
To figure out on which screen you are, you can iterate throught QGuiApplication::screens() and check whether the cursor fits in the geometry of the screen.
Here is a more complex example to compute the native cursor position (note the additional work needed to work with High DPI screens):
QPoint getNativeCursorPosition()
{
QPoint pos = cursorPosToNative(QCursor::pos());
// Cursor positions from Qt are calculated in a strange way, the offset to
// the origin of the current screen is in device-independent pixels while
// the origin itself is native!
for (QScreen *screen : QGuiApplication::screens()) {
QRect screenRect = screen->geometry();
if (screenRect.contains(pos)) {
QPoint origin = screenRect.topLeft();
return origin + (pos - origin) * screen->devicePixelRatio();
}
}
// should not happen, but try to find a good fallback.
return pos * qApp->devicePixelRatio();
}
This may work for you?
It did for me
QDesktopWidget *widget = QApplication::desktop();
QPosition globalCursorPosition = widget->cursor().pos();
Sice it seems that it can't be done with Qt (at least with my system configuration, and it seems that also in Windows) I decided to use the libX11 to make that implementation, which works like charm.
It's not an ideal solution because I wanted to only use Qt, but it works.
With QML you can use the properties of the Screen QML Type:
Screen.virtualX : The x coordinate of the screen within the virtual desktop.
Screen.virtualY : The y coordinate of the screen within the virtual desktop.
import QtQuick 2.6
import QtQuick.Window 2.2
console.log("Pos x : " + Screen.virtualX )
console.log("Pos y : " + Screen.virtualY )
This work with single screen as well multi-monitor systems.
I recently ran into a similar problem on Qt 5.15 + Windows + mixed DPI and needed this work around within a QWindow object.
QScreen* primaryScreen = QGuiApplication::primaryScreen();
QScreen* thisScreen = screen();
qreal primaryDPR = primaryScreen->devicePixelRatio();
qreal thisDPR = thisScreen->devicePixelRatio();
qreal scale = thisDPR / primaryDPR;
QPoint pos = scale * QCursor::pos();
I'm unsure if this works on other platforms.

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.

Keep QPixmap copy of screen contents using X11, XDamage, XRender, and other tricks

I'm attempting to solve what I thought would be a very simple problem. I want to keep a QPixmap updated with the entire screen contents. You can get such a pixmap by doing this:
QDesktopWidget *w = QApplication::desktop();
if (w)
{
QRect r = w->screenGeometry();
QPixmap p = QPixmap::grabWindow(w->winId(), 0, 0, r.width(), r.height())
QByteArray bitmap;
}
The problem with this is that QDesktopWidget ends up re-grabbing the entire screen pixmap from the X11 server every time you ask for it, even if nothing has changed.
I need this code to be fast, so I'm trying to do this myself. My starting point was the qx11mirror demo, however, that basically does the same thing. It uses the XDamage extension to work out when something has changed, but instead of using the damaged rectangle information to just update that part of the cached pixmap, it just sets a "dirty" flag, which triggers an entire refresh anyway.
So I'm trying to modify the qx11mirror example to just update the damaged portion of the windows, but I can't seem to get anything to work - all I get is a blank (black) pixmap. The code I'm using is:
void QX11Mirror::x11Event(XEvent *event)
{
if (event->type == m_damageEvent + XDamageNotify)
{
XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>(event);
XWindowAttributes attr;
XGetWindowAttributes(QX11Info::display(), m_window, &attr);
XRenderPictFormat *format = XRenderFindVisualFormat(QX11Info::display(), attr.visual);
bool hasAlpha = ( format->type == PictTypeDirect && format->direct.alphaMask );
int x = attr.x;
int y = attr.y;
int width = attr.width;
int height = attr.height;
// debug output so I can see the window pos vs the damaged area:
qDebug() << "repainting dirty area:" << x << y << width << height << "vs" << e->area.x << e->area.y << e->area.width << e->area.height;
XRenderPictureAttributes pa;
pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets
Picture picture = XRenderCreatePicture(QX11Info::display(),
m_window,
format,
CPSubwindowMode,
&pa);
XserverRegion region = XFixesCreateRegionFromWindow(QX11Info::display(),
m_window, WindowRegionBounding);
XFixesTranslateRegion(QX11Info::display(), region, -x, -y);
XFixesSetPictureClipRegion(QX11Info::display(), picture, 0, 0, region);
XFixesDestroyRegion(QX11Info::display(), region);
//QPixmap dest(width, height);
XRenderComposite(QX11Info::display(), // display
hasAlpha ? PictOpOver : PictOpSrc, // operation mode
picture, // src drawable
None, // src mask
dest.x11PictureHandle(), // dest drawable
e->area.x, // src X
e->area.y, // src Y
0, // mask X
0, // mask Y
e->area.x, // dest X
e->area.y, // dest Y
e->area.width, // width
e->area.height); // height
m_px = dest;
XDamageSubtract(QX11Info::display(), e->damage, None, None);
emit windowChanged();
}
else if (event->type == ConfigureNotify)
{
XConfigureEvent *e = &event->xconfigure;
m_position = QRect(e->x, e->y, e->width, e->height);
emit positionChanged(m_position);
}
}
Can anyone point me in the right direction? The documetnation for XRender, XDamage, and the other X11 extensions is pretty bad.
Reasons for using XRender over XCopyArea
The following text taken from here.
It is perfectly possible to create a GC for a window and use XCopyArea() to copy the contents of the window if you want to use the core protocol, but since the Composite extension exposes new visuals (ones with alpha channels e.g.), there's no guarantee that the format of the source drawable will match that of the destination. With the core protocol that situation will result in a match error, something that won't happen with the Xrender extension.
In addition the core protocol has no understanding of alpha channels, which means that it can't composite windows that use the new ARGB visual. When the source and destination have the same format, there's also no performance advantage to using the core protocol as of X11R6.8. That release is also the first to support the new Composite extension.
So in conclusion there are no drawbacks, and only advantages to choosing Xrender over the core protocol for these operations.
First you need to change the DamageReportLevel in the call to DamageCreate in QX11Mirror::setWindow from DamageReportNotEmpty to XDamageReportBoundingBox.
Next you need to call dest.detach() before the call to XRenderComposite. You don't really need both m_px and dest as member variables though - you can just use m__px.
There is also a missing call to XRenderFreePicture in that example which should go after the call to XRenderComposite:
XRenderFreePicture(QX11Info::display(), picture);
You don't need to duplicate all the code QX11Mirror::pixmap in QX11Mirror::x11Event. Instead change the type of m_dirty from bool to QRect, then have x11Event update m_dirty with the rectangle from the XDamageNotifyEvent,i.e. e->area. Then in QX11Mirror:pixmap rather than checking if m_dirty is true, check if m_dirty is not an empty rectangle. You would then pass the rectangle from m_dirty to XRenderComposite.
Edit:
The dest.detach is the key bit - without that you'll never get it to work.