diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index 1730b88212..140377dae0 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -31,12 +31,33 @@ along with this program. If not, see . #include #include #include +#include #include #include #include +class ComparableQPoint : public QPoint +{ +public: + ComparableQPoint(QPoint& point): QPoint(point.x(), point.y()) + {} + + ComparableQPoint(QPoint point): QPoint(point.x(), point.y()) + {} + + ComparableQPoint(): QPoint() + {} + + // utility class that allows using QMap to sort its keys when they are QPoint + // so that the bottom and right points are after the top left ones + bool operator<(const ComparableQPoint &other) const { + return x() < other.x() || y() < other.y(); + } +}; + + namespace KWin { @@ -159,10 +180,62 @@ static xcb_pixmap_t xpixmapFromImage(const QImage &image) void ScreenShotEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) { m_cachedOutputGeometry = data.outputGeometry(); - m_cachedScale = data.screenScale(); + // When taking a non-nativeSize fullscreenshot, pretend we have a uniform 1.0 ratio + // so the screenshot size will match the virtualGeometry + m_cachedScale = m_nativeSize ? data.screenScale() : 1.0; effects->paintScreen(mask, region, data); } +/** + * Translates the Point coordinates in m_cacheOutputsImages keys from the virtualGeometry + * To a new geometry taking into account the real size of the images (virtualSize * dpr), + * moving the siblings images on the right bottom of the image as necessary + */ +void ScreenShotEffect::computeCoordinatesAfterScaling() +{ + // prepare a translation table that will store oldPoint -> newPoint in the new coordinates + QMap translationMap; + for (auto i = m_cacheOutputsImages.keyBegin(); i != m_cacheOutputsImages.keyEnd(); ++i) { + translationMap.insert(*i, *i); + } + + for (auto i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) { + const auto p = i.key(); + const auto img = i.value(); + const auto dpr = img.devicePixelRatio(); + if (!qFuzzyCompare(dpr, 1.0)) { + // must update all coordinates of next rects + const int deltaX = img.width() - (img.width() / dpr); + const int deltaY = img.height() - (img.height() / dpr); + + // for the next images on the right or bottom + // thanks to ComparableQPoint + for (auto i2 = m_cacheOutputsImages.constFind(p); + i2 != m_cacheOutputsImages.constEnd(); ++i2) { + + const auto point = i2.key(); + auto finalPoint = translationMap.value(point); + + if (point.x() >= img.width() + p.x() - deltaX) { + finalPoint.setX(finalPoint.x() + deltaX); + } + if (point.y() >= img.height() + p.y() - deltaY) { + finalPoint.setY(finalPoint.y() + deltaY); + } + // update final position point with the necessary deltas + translationMap.insert(point, finalPoint); + } + } + } + + // make the new coordinates effective + for (auto i = translationMap.keyBegin(); i != translationMap.keyEnd(); ++i) { + const auto key = *i; + const auto img = m_cacheOutputsImages.take(key); + m_cacheOutputsImages.insert(translationMap.value(key), img); + } +} + void ScreenShotEffect::postPaintScreen() { effects->postPaintScreen(); @@ -295,17 +368,45 @@ void ScreenShotEffect::postPaintScreen() return; } img.setDevicePixelRatio(m_cachedScale); - if (m_multipleOutputsImage.isNull()) { - m_multipleOutputsImage = QImage(m_scheduledGeometry.size(), QImage::Format_ARGB32); - m_multipleOutputsImage.fill(Qt::transparent); - } - QPainter p; - p.begin(&m_multipleOutputsImage); - p.drawImage(intersection.topLeft() - m_scheduledGeometry.topLeft(), img); - p.end(); + + m_cacheOutputsImages.insert(ComparableQPoint(m_cachedOutputGeometry.topLeft()), img); + m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { - sendReplyImage(m_multipleOutputsImage); + + // Recompute coordinates + if (m_nativeSize) { + computeCoordinatesAfterScaling(); + } + + // find the output image size + int width = 0; + int height = 0; + QMap::const_iterator i; + for (i = m_cacheOutputsImages.constBegin(); i != m_cacheOutputsImages.constEnd(); ++i) { + const auto pos = i.key(); + const auto img = i.value(); + + width = qMax(width, pos.x() + img.width()); + height = qMax(height, pos.y() + img.height()); + } + + QImage multipleOutputsImage = QImage(width, height, QImage::Format_ARGB32); + + QPainter p; + p.begin(&multipleOutputsImage); + + // reassemble images together + for (i = m_cacheOutputsImages.begin(); i != m_cacheOutputsImages.end(); ++i) { + auto pos = i.key(); + auto img = i.value(); + // disable dpr rendering, we already took care of this + img.setDevicePixelRatio(1.0); + p.drawImage(pos, img); + } + p.end(); + + sendReplyImage(multipleOutputsImage); } } else { @@ -334,10 +435,11 @@ void ScreenShotEffect::sendReplyImage(const QImage &img) QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); } m_scheduledGeometry = QRect(); - m_multipleOutputsImage = QImage(); m_multipleOutputsRendered = QRegion(); m_captureCursor = false; m_windowMode = WindowMode::NoCapture; + m_cacheOutputsImages.clear(); + m_cachedOutputGeometry = QRect(); } QString ScreenShotEffect::saveTempImage(const QImage &img) @@ -494,7 +596,7 @@ QString ScreenShotEffect::screenshotFullscreen(bool captureCursor) return QString(); } -void ScreenShotEffect::screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor) +void ScreenShotEffect::screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor, bool shouldReturnNativeSize) { if (!calledFromDBus()) { return; @@ -509,6 +611,7 @@ void ScreenShotEffect::screenshotFullscreen(QDBusUnixFileDescriptor fd, bool cap return; } m_captureCursor = captureCursor; + m_nativeSize = shouldReturnNativeSize; showInfoMessage(InfoMessageMode::Screen); effects->startInteractivePositionSelection( diff --git a/effects/screenshot/screenshot.h b/effects/screenshot/screenshot.h index f04de9ba40..cfb080f618 100644 --- a/effects/screenshot/screenshot.h +++ b/effects/screenshot/screenshot.h @@ -29,6 +29,7 @@ along with this program. If not, see . #include #include +class ComparableQPoint; namespace KWin { @@ -98,8 +99,9 @@ public Q_SLOTS: * * @param fd File descriptor into which the screenshot should be saved * @param captureCursor Whether to include the mouse cursor + * @param shouldReturnNativeSize Whether to return an image according to the virtualGeometry, or according to pixel on screen size */ - Q_SCRIPTABLE void screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor = false); + Q_SCRIPTABLE void screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor = false, bool shouldReturnNativeSize = false); /** * Saves a screenshot of the screen identified by @p screen into a file and returns the path to the file. * Functionality requires hardware support, if not available a null string is returned. @@ -151,14 +153,17 @@ private: void showInfoMessage(InfoMessageMode mode); void hideInfoMessage(); bool isTakingScreenshot() const; + void computeCoordinatesAfterScaling(); + EffectWindow *m_scheduledScreenshot; ScreenShotType m_type; QRect m_scheduledGeometry; QDBusMessage m_replyMessage; QRect m_cachedOutputGeometry; - QImage m_multipleOutputsImage; QRegion m_multipleOutputsRendered; + QMap m_cacheOutputsImages; bool m_captureCursor = false; + bool m_nativeSize = false; enum class WindowMode { NoCapture, Xpixmap,