KWin
Loading...
Searching...
No Matches
screenshot.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org>
6 SPDX-FileCopyrightText: 2010 Nokia Corporation and /or its subsidiary(-ies)
7 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11#include "screenshot.h"
13
14#include "core/output.h"
15#include "core/pixelgrid.h"
16#include "core/rendertarget.h"
17#include "core/renderviewport.h"
19#include "opengl/glplatform.h"
20#include "opengl/glutils.h"
21
22#include <QPainter>
23
24namespace KWin
25{
26
28{
29 QPromise<QImage> promise;
30 ScreenShotFlags flags;
31 EffectWindow *window = nullptr;
32};
33
35{
36 QPromise<QImage> promise;
37 ScreenShotFlags flags;
38 QRect area;
39 QImage result;
40 QList<Output *> screens;
41};
42
44{
45 QPromise<QImage> promise;
46 ScreenShotFlags flags;
47 Output *screen = nullptr;
48};
49
50static void convertFromGLImage(QImage &img, int w, int h, const OutputTransform &renderTargetTransformation)
51{
52 // from QtOpenGL/qgl.cpp
53 // SPDX-FileCopyrightText: 2010 Nokia Corporation and /or its subsidiary(-ies)
54 // see https://github.com/qt/qtbase/blob/dev/src/opengl/qgl.cpp
55 if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
56 // OpenGL gives RGBA; Qt wants ARGB
57 uint *p = reinterpret_cast<uint *>(img.bits());
58 uint *end = p + w * h;
59 while (p < end) {
60 uint a = *p << 24;
61 *p = (*p >> 8) | a;
62 p++;
63 }
64 } else {
65 // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB
66 for (int y = 0; y < h; y++) {
67 uint *q = reinterpret_cast<uint *>(img.scanLine(y));
68 for (int x = 0; x < w; ++x) {
69 const uint pixel = *q;
70 *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
71 | (pixel & 0xff00ff00);
72
73 q++;
74 }
75 }
76 }
77
78 QMatrix4x4 matrix;
79 // apply render target transformation
80 matrix *= renderTargetTransformation.inverted().toMatrix();
81 // OpenGL textures are flipped vs QImage
82 matrix.scale(1, -1);
83 img = img.transformed(matrix.toTransform());
84}
85
90
92 : m_dbusInterface2(new ScreenShotDBusInterface2(this))
93{
94 connect(effects, &EffectsHandler::screenAdded, this, &ScreenShotEffect::handleScreenAdded);
95 connect(effects, &EffectsHandler::screenRemoved, this, &ScreenShotEffect::handleScreenRemoved);
96 connect(effects, &EffectsHandler::windowClosed, this, &ScreenShotEffect::handleWindowClosed);
97}
98
100{
101 cancelWindowScreenShots();
102 cancelAreaScreenShots();
103 cancelScreenScreenShots();
104}
105
106QFuture<QImage> ScreenShotEffect::scheduleScreenShot(Output *screen, ScreenShotFlags flags)
107{
108 for (const ScreenShotScreenData &data : m_screenScreenShots) {
109 if (data.screen == screen && data.flags == flags) {
110 return data.promise.future();
111 }
112 }
113
115 data.screen = screen;
116 data.flags = flags;
117
118 data.promise.start();
119 QFuture<QImage> future = data.promise.future();
120
121 m_screenScreenShots.push_back(std::move(data));
122 effects->addRepaint(screen->geometry());
123
124 return future;
125}
126
127QFuture<QImage> ScreenShotEffect::scheduleScreenShot(const QRect &area, ScreenShotFlags flags)
128{
129 for (const ScreenShotAreaData &data : m_areaScreenShots) {
130 if (data.area == area && data.flags == flags) {
131 return data.promise.future();
132 }
133 }
134
136 data.area = area;
137 data.flags = flags;
138
139 const QList<Output *> screens = effects->screens();
140 for (Output *screen : screens) {
141 if (screen->geometry().intersects(area)) {
142 data.screens.append(screen);
143 }
144 }
145
146 qreal devicePixelRatio = 1.0;
147 if (flags & ScreenShotNativeResolution) {
148 for (const Output *screen : std::as_const(data.screens)) {
149 if (screen->scale() > devicePixelRatio) {
150 devicePixelRatio = screen->scale();
151 }
152 }
153 }
154
155 data.result = QImage(area.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
156 data.result.fill(Qt::transparent);
157 data.result.setDevicePixelRatio(devicePixelRatio);
158
159 data.promise.start();
160 QFuture<QImage> future = data.promise.future();
161
162 m_areaScreenShots.push_back(std::move(data));
163 effects->addRepaint(area);
164
165 return future;
166}
167
168QFuture<QImage> ScreenShotEffect::scheduleScreenShot(EffectWindow *window, ScreenShotFlags flags)
169{
170 for (const ScreenShotWindowData &data : m_windowScreenShots) {
171 if (data.window == window && data.flags == flags) {
172 return data.promise.future();
173 }
174 }
175
177 data.window = window;
178 data.flags = flags;
179
180 data.promise.start();
181 QFuture<QImage> future = data.promise.future();
182
183 m_windowScreenShots.push_back(std::move(data));
184 window->addRepaintFull();
185
186 return future;
187}
188
189void ScreenShotEffect::cancelWindowScreenShots()
190{
191 m_windowScreenShots.clear();
192}
193
194void ScreenShotEffect::cancelAreaScreenShots()
195{
196 m_areaScreenShots.clear();
197}
198
199void ScreenShotEffect::cancelScreenScreenShots()
200{
201 m_screenScreenShots.clear();
202}
203
204void ScreenShotEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
205{
206 m_paintedScreen = screen;
207 effects->paintScreen(renderTarget, viewport, mask, region, screen);
208
209 for (ScreenShotWindowData &data : m_windowScreenShots) {
210 takeScreenShot(&data);
211 }
212 m_windowScreenShots.clear();
213
214 for (int i = m_areaScreenShots.size() - 1; i >= 0; --i) {
215 if (takeScreenShot(renderTarget, viewport, &m_areaScreenShots[i])) {
216 m_areaScreenShots.erase(m_areaScreenShots.begin() + i);
217 }
218 }
219
220 for (int i = m_screenScreenShots.size() - 1; i >= 0; --i) {
221 if (takeScreenShot(renderTarget, viewport, &m_screenScreenShots[i])) {
222 m_screenScreenShots.erase(m_screenScreenShots.begin() + i);
223 }
224 }
225}
226
227void ScreenShotEffect::takeScreenShot(ScreenShotWindowData *screenshot)
228{
229 EffectWindow *window = screenshot->window;
230
232 QRectF geometry = window->expandedGeometry();
233 qreal devicePixelRatio = 1;
234
235 if (window->hasDecoration() && !(screenshot->flags & ScreenShotIncludeDecoration)) {
236 geometry = window->clientGeometry();
237 } else if (!(screenshot->flags & ScreenShotIncludeShadow)) {
238 geometry = window->frameGeometry();
239 }
240
241 if (screenshot->flags & ScreenShotNativeResolution) {
242 if (const Output *screen = window->screen()) {
243 devicePixelRatio = screen->scale();
244 }
245 }
246
247 bool validTarget = true;
248 std::unique_ptr<GLTexture> offscreenTexture;
249 std::unique_ptr<GLFramebuffer> target;
251 offscreenTexture = GLTexture::allocate(GL_RGBA8, QSizeF(geometry.size() * devicePixelRatio).toSize());
252 if (!offscreenTexture) {
253 return;
254 }
255 offscreenTexture->setFilter(GL_LINEAR);
256 offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
257 target = std::make_unique<GLFramebuffer>(offscreenTexture.get());
258 validTarget = target->valid();
259 }
260 if (validTarget) {
261 d.setXTranslation(-geometry.x());
262 d.setYTranslation(-geometry.y());
263
264 // render window into offscreen texture
266 QImage img;
268 RenderTarget renderTarget(target.get());
269 RenderViewport viewport(geometry, devicePixelRatio, renderTarget);
270 GLFramebuffer::pushFramebuffer(target.get());
271 glClearColor(0.0, 0.0, 0.0, 0.0);
272 glClear(GL_COLOR_BUFFER_BIT);
273 glClearColor(0.0, 0.0, 0.0, 1.0);
274
275 QMatrix4x4 projection;
276 projection.ortho(QRect(0, 0, geometry.width() * devicePixelRatio, geometry.height() * devicePixelRatio));
277 d.setProjectionMatrix(projection);
278
279 effects->drawWindow(renderTarget, viewport, window, mask, infiniteRegion(), d);
280
281 // copy content from framebuffer into image
282 img = QImage(offscreenTexture->size(), QImage::Format_ARGB32);
283 img.setDevicePixelRatio(devicePixelRatio);
284 glReadnPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(),
285 static_cast<GLvoid *>(img.bits()));
287 convertFromGLImage(img, img.width(), img.height(), renderTarget.transform());
288 }
289
290 if (screenshot->flags & ScreenShotIncludeCursor) {
291 grabPointerImage(img, geometry.x(), geometry.y());
292 }
293
294 screenshot->promise.addResult(img);
295 screenshot->promise.finish();
296 }
297}
298
299bool ScreenShotEffect::takeScreenShot(const RenderTarget &renderTarget, const RenderViewport &viewport, ScreenShotAreaData *screenshot)
300{
301 if (!effects->waylandDisplay()) {
302 // On X11, all screens are painted simultaneously and there is no native HiDPI support.
303 QImage snapshot = blitScreenshot(renderTarget, viewport, screenshot->area);
304 if (screenshot->flags & ScreenShotIncludeCursor) {
305 grabPointerImage(snapshot, screenshot->area.x(), screenshot->area.y());
306 }
307 screenshot->promise.addResult(snapshot);
308 screenshot->promise.finish();
309 return true;
310 } else {
311 if (!screenshot->screens.contains(m_paintedScreen)) {
312 return false;
313 }
314 screenshot->screens.removeOne(m_paintedScreen);
315
316 const QRect sourceRect = screenshot->area & m_paintedScreen->geometry();
317 qreal sourceDevicePixelRatio = 1.0;
318 if (screenshot->flags & ScreenShotNativeResolution) {
319 sourceDevicePixelRatio = m_paintedScreen->scale();
320 }
321
322 const QImage snapshot = blitScreenshot(renderTarget, viewport, sourceRect, sourceDevicePixelRatio);
323 const QRect nativeArea(screenshot->area.topLeft(),
324 screenshot->area.size() * screenshot->result.devicePixelRatio());
325
326 QPainter painter(&screenshot->result);
327 painter.setRenderHint(QPainter::SmoothPixmapTransform);
328 painter.setWindow(nativeArea);
329 painter.drawImage(sourceRect, snapshot);
330 painter.end();
331
332 if (screenshot->screens.isEmpty()) {
333 if (screenshot->flags & ScreenShotIncludeCursor) {
334 grabPointerImage(screenshot->result, screenshot->area.x(), screenshot->area.y());
335 }
336 screenshot->promise.addResult(screenshot->result);
337 screenshot->promise.finish();
338 return true;
339 }
340 }
341
342 return false;
343}
344
345bool ScreenShotEffect::takeScreenShot(const RenderTarget &renderTarget, const RenderViewport &viewport, ScreenShotScreenData *screenshot)
346{
347 if (m_paintedScreen && screenshot->screen != m_paintedScreen) {
348 return false;
349 }
350
351 qreal devicePixelRatio = 1.0;
352 if (screenshot->flags & ScreenShotNativeResolution) {
353 devicePixelRatio = screenshot->screen->scale();
354 }
355
356 QImage snapshot = blitScreenshot(renderTarget, viewport, screenshot->screen->geometry(), devicePixelRatio);
357 if (screenshot->flags & ScreenShotIncludeCursor) {
358 const int xOffset = screenshot->screen->geometry().x();
359 const int yOffset = screenshot->screen->geometry().y();
360 grabPointerImage(snapshot, xOffset, yOffset);
361 }
362
363 screenshot->promise.addResult(snapshot);
364 screenshot->promise.finish();
365
366 return true;
367}
368
369QImage ScreenShotEffect::blitScreenshot(const RenderTarget &renderTarget, const RenderViewport &viewport, const QRect &geometry, qreal devicePixelRatio) const
370{
371 QImage image;
372
374 const auto screenGeometry = m_paintedScreen ? m_paintedScreen->geometry() : effects->virtualScreenGeometry();
375 const QSize nativeSize = renderTarget.transform().map(
376 snapToPixelGrid(scaledRect(geometry, devicePixelRatio))
377 .translated(-snapToPixelGrid(scaledRect(screenGeometry, devicePixelRatio)).topLeft())
378 .size());
379 image = QImage(nativeSize, QImage::Format_ARGB32);
380
381 const auto texture = GLTexture::allocate(GL_RGBA8, nativeSize);
382 if (!texture) {
383 return {};
384 }
385 GLFramebuffer target(texture.get());
386 if (renderTarget.texture()) {
389 binder.shader()->setColorspaceUniformsToSRGB(renderTarget.colorDescription());
390 QMatrix4x4 projectionMatrix;
391 projectionMatrix.scale(1, -1);
392 projectionMatrix *= renderTarget.transform().toMatrix();
393 projectionMatrix.scale(1, -1);
394 projectionMatrix.ortho(QRect(QPoint(), nativeSize));
395 binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix);
396 renderTarget.texture()->render(viewport.mapToRenderTargetTexture(geometry), infiniteRegion(), nativeSize);
397 } else {
398 target.blitFromFramebuffer(viewport.mapToRenderTarget(geometry));
400 }
401 glReadPixels(0, 0, nativeSize.width(), nativeSize.height(), GL_RGBA,
402 GL_UNSIGNED_BYTE, static_cast<GLvoid *>(image.bits()));
404 convertFromGLImage(image, nativeSize.width(), nativeSize.height(), renderTarget.transform());
405 }
406
407 image.setDevicePixelRatio(devicePixelRatio);
408 return image;
409}
410
411void ScreenShotEffect::grabPointerImage(QImage &snapshot, int xOffset, int yOffset) const
412{
413 if (effects->isCursorHidden()) {
414 return;
415 }
416
417 const PlatformCursorImage cursor = effects->cursorImage();
418 if (cursor.image().isNull()) {
419 return;
420 }
421
422 QPainter painter(&snapshot);
423 painter.setRenderHint(QPainter::SmoothPixmapTransform);
424 painter.drawImage(effects->cursorPos() - cursor.hotSpot() - QPoint(xOffset, yOffset), cursor.image());
425}
426
428{
429 return (!m_windowScreenShots.empty() || !m_areaScreenShots.empty() || !m_screenScreenShots.empty())
430 && !effects->isScreenLocked();
431}
432
434{
435 return 0;
436}
437
438void ScreenShotEffect::handleScreenAdded()
439{
440 cancelAreaScreenShots();
441}
442
443void ScreenShotEffect::handleScreenRemoved(Output *screen)
444{
445 cancelAreaScreenShots();
446
447 std::erase_if(m_screenScreenShots, [screen](const auto &screenshot) {
448 return screenshot.screen == screen;
449 });
450}
451
452void ScreenShotEffect::handleWindowClosed(EffectWindow *window)
453{
454 std::erase_if(m_windowScreenShots, [window](const auto &screenshot) {
455 return screenshot.window == window;
456 });
457}
458
459} // namespace KWin
460
461#include "moc_screenshot.cpp"
Representation of a window used by/for Effect classes.
QRectF clientGeometry() const
Q_SCRIPTABLE void addRepaintFull()
KWin::Output * screen
QRectF frameGeometry() const
void screenAdded(KWin::Output *screen)
Display * waylandDisplay() const
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
void windowClosed(KWin::EffectWindow *w)
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
void screenRemoved(KWin::Output *screen)
Q_SCRIPTABLE void addRepaint(const QRectF &r)
QList< Output * > screens() const
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
PlatformCursorImage cursorImage() const
static bool supported()
static GLFramebuffer * popFramebuffer()
static void pushFramebuffer(GLFramebuffer *fbo)
static std::unique_ptr< GLTexture > allocate(GLenum internalFormat, const QSize &size, int levels=1)
qreal scale() const
Definition output.cpp:455
QRect geometry
Definition output.h:134
OutputTransform inverted() const
Definition output.cpp:69
QMatrix4x4 toMatrix() const
Definition output.cpp:300
int requestedEffectChainPosition() const override
~ScreenShotEffect() override
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen) override
bool isActive() const override
static bool supported()
QFuture< QImage > scheduleScreenShot(Output *screen, ScreenShotFlags flags={})
void setYTranslation(qreal translate)
Definition effect.cpp:106
void setProjectionMatrix(const QMatrix4x4 &matrix)
Definition effect.cpp:327
void setXTranslation(qreal translate)
Definition effect.cpp:101
@ PAINT_WINDOW_TRANSFORMED
Definition effect.h:552
@ PAINT_WINDOW_TRANSLUCENT
Definition effect.h:548
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
glReadnPixels_func glReadnPixels
KWIN_EXPORT QPoint snapToPixelGrid(const QPointF &point)
Definition pixelgrid.h:16
@ ScreenShotIncludeShadow
Include the window shadow.
Definition screenshot.h:29
@ ScreenShotNativeResolution
Take the screenshot at the native resolution.
Definition screenshot.h:28
@ ScreenShotIncludeCursor
Include the cursor.
Definition screenshot.h:27
@ ScreenShotIncludeDecoration
Include window titlebar and borders.
Definition screenshot.h:26
KWIN_EXPORT QRectF scaledRect(const QRectF &rect, qreal scale)
Definition globals.h:243
EffectsHandler * effects
QPromise< QImage > promise
QList< Output * > screens
ScreenShotFlags flags
QPromise< QImage > promise
QPromise< QImage > promise