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
This commit is contained in:
parent
a6d29add93
commit
137a6a1705
2 changed files with 122 additions and 14 deletions
|
@ -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 ®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<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(
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue