ScreenShotEffect: Add a shouldReturnNativeSize argument to screenshotFullscreen

Summary:
Add a shouldReturnNativeSize to screenshotFullscreen so that KWin can export
screenshot that are screen pixel accurate.

Useful for spectacle to be able to do rectangular selection kind of screenshot.

Test Plan:
Example of a top screen using a 1.25 scale ratio being export in native resolution
{F8255144}
(The top screen has a bigger size than its virtual geometry and next screen doesn't overlap)

Example of the same screen in virtual resolution:
{F8255146}

Reviewers: #kwin, davidedmundson, bport, zzag, apol

Reviewed By: #kwin, davidedmundson

Subscribers: broulik, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D29122
icc-effect-master
Méven Car 2020-04-22 11:31:46 +02:00
parent a6d29add93
commit 137a6a1705
2 changed files with 122 additions and 14 deletions

View File

@ -31,12 +31,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QPainter>
#include <QMatrix4x4>
#include <xcb/xcb_image.h>
#include <QPoint>
#include <KLocalizedString>
#include <KNotification>
#include <unistd.h>
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 &region, 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<ComparableQPoint, ComparableQPoint> 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<ComparableQPoint, QImage>::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(

View File

@ -29,6 +29,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QObject>
#include <QImage>
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<ComparableQPoint, QImage> m_cacheOutputsImages;
bool m_captureCursor = false;
bool m_nativeSize = false;
enum class WindowMode {
NoCapture,
Xpixmap,