File indexing completed on 2024-11-10 04:57:07

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2010 Martin Gräßlin <mgraesslin@kde.org>
0006     SPDX-FileCopyrightText: 2010 Nokia Corporation and /or its subsidiary(-ies)
0007     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "screenshot.h"
0012 #include "screenshotdbusinterface2.h"
0013 
0014 #include "core/output.h"
0015 #include "core/pixelgrid.h"
0016 #include "core/rendertarget.h"
0017 #include "core/renderviewport.h"
0018 #include "effect/effecthandler.h"
0019 #include "opengl/glplatform.h"
0020 #include "opengl/glutils.h"
0021 
0022 #include <QPainter>
0023 
0024 namespace KWin
0025 {
0026 
0027 struct ScreenShotWindowData
0028 {
0029     QPromise<QImage> promise;
0030     ScreenShotFlags flags;
0031     EffectWindow *window = nullptr;
0032 };
0033 
0034 struct ScreenShotAreaData
0035 {
0036     QPromise<QImage> promise;
0037     ScreenShotFlags flags;
0038     QRect area;
0039     QImage result;
0040     QList<Output *> screens;
0041 };
0042 
0043 struct ScreenShotScreenData
0044 {
0045     QPromise<QImage> promise;
0046     ScreenShotFlags flags;
0047     Output *screen = nullptr;
0048 };
0049 
0050 static void convertFromGLImage(QImage &img, int w, int h, const OutputTransform &renderTargetTransformation)
0051 {
0052     // from QtOpenGL/qgl.cpp
0053     // SPDX-FileCopyrightText: 2010 Nokia Corporation and /or its subsidiary(-ies)
0054     // see https://github.com/qt/qtbase/blob/dev/src/opengl/qgl.cpp
0055     if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
0056         // OpenGL gives RGBA; Qt wants ARGB
0057         uint *p = reinterpret_cast<uint *>(img.bits());
0058         uint *end = p + w * h;
0059         while (p < end) {
0060             uint a = *p << 24;
0061             *p = (*p >> 8) | a;
0062             p++;
0063         }
0064     } else {
0065         // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB
0066         for (int y = 0; y < h; y++) {
0067             uint *q = reinterpret_cast<uint *>(img.scanLine(y));
0068             for (int x = 0; x < w; ++x) {
0069                 const uint pixel = *q;
0070                 *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff)
0071                     | (pixel & 0xff00ff00);
0072 
0073                 q++;
0074             }
0075         }
0076     }
0077 
0078     QMatrix4x4 matrix;
0079     // apply render target transformation
0080     matrix *= renderTargetTransformation.inverted().toMatrix();
0081     // OpenGL textures are flipped vs QImage
0082     matrix.scale(1, -1);
0083     img = img.transformed(matrix.toTransform());
0084 }
0085 
0086 bool ScreenShotEffect::supported()
0087 {
0088     return effects->isOpenGLCompositing() && GLFramebuffer::supported();
0089 }
0090 
0091 ScreenShotEffect::ScreenShotEffect()
0092     : m_dbusInterface2(new ScreenShotDBusInterface2(this))
0093 {
0094     connect(effects, &EffectsHandler::screenAdded, this, &ScreenShotEffect::handleScreenAdded);
0095     connect(effects, &EffectsHandler::screenRemoved, this, &ScreenShotEffect::handleScreenRemoved);
0096     connect(effects, &EffectsHandler::windowClosed, this, &ScreenShotEffect::handleWindowClosed);
0097 }
0098 
0099 ScreenShotEffect::~ScreenShotEffect()
0100 {
0101     cancelWindowScreenShots();
0102     cancelAreaScreenShots();
0103     cancelScreenScreenShots();
0104 }
0105 
0106 QFuture<QImage> ScreenShotEffect::scheduleScreenShot(Output *screen, ScreenShotFlags flags)
0107 {
0108     for (const ScreenShotScreenData &data : m_screenScreenShots) {
0109         if (data.screen == screen && data.flags == flags) {
0110             return data.promise.future();
0111         }
0112     }
0113 
0114     ScreenShotScreenData data;
0115     data.screen = screen;
0116     data.flags = flags;
0117 
0118     data.promise.start();
0119     QFuture<QImage> future = data.promise.future();
0120 
0121     m_screenScreenShots.push_back(std::move(data));
0122     effects->addRepaint(screen->geometry());
0123 
0124     return future;
0125 }
0126 
0127 QFuture<QImage> ScreenShotEffect::scheduleScreenShot(const QRect &area, ScreenShotFlags flags)
0128 {
0129     for (const ScreenShotAreaData &data : m_areaScreenShots) {
0130         if (data.area == area && data.flags == flags) {
0131             return data.promise.future();
0132         }
0133     }
0134 
0135     ScreenShotAreaData data;
0136     data.area = area;
0137     data.flags = flags;
0138 
0139     const QList<Output *> screens = effects->screens();
0140     for (Output *screen : screens) {
0141         if (screen->geometry().intersects(area)) {
0142             data.screens.append(screen);
0143         }
0144     }
0145 
0146     qreal devicePixelRatio = 1.0;
0147     if (flags & ScreenShotNativeResolution) {
0148         for (const Output *screen : std::as_const(data.screens)) {
0149             if (screen->scale() > devicePixelRatio) {
0150                 devicePixelRatio = screen->scale();
0151             }
0152         }
0153     }
0154 
0155     data.result = QImage(area.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
0156     data.result.fill(Qt::transparent);
0157     data.result.setDevicePixelRatio(devicePixelRatio);
0158 
0159     data.promise.start();
0160     QFuture<QImage> future = data.promise.future();
0161 
0162     m_areaScreenShots.push_back(std::move(data));
0163     effects->addRepaint(area);
0164 
0165     return future;
0166 }
0167 
0168 QFuture<QImage> ScreenShotEffect::scheduleScreenShot(EffectWindow *window, ScreenShotFlags flags)
0169 {
0170     for (const ScreenShotWindowData &data : m_windowScreenShots) {
0171         if (data.window == window && data.flags == flags) {
0172             return data.promise.future();
0173         }
0174     }
0175 
0176     ScreenShotWindowData data;
0177     data.window = window;
0178     data.flags = flags;
0179 
0180     data.promise.start();
0181     QFuture<QImage> future = data.promise.future();
0182 
0183     m_windowScreenShots.push_back(std::move(data));
0184     window->addRepaintFull();
0185 
0186     return future;
0187 }
0188 
0189 void ScreenShotEffect::cancelWindowScreenShots()
0190 {
0191     m_windowScreenShots.clear();
0192 }
0193 
0194 void ScreenShotEffect::cancelAreaScreenShots()
0195 {
0196     m_areaScreenShots.clear();
0197 }
0198 
0199 void ScreenShotEffect::cancelScreenScreenShots()
0200 {
0201     m_screenScreenShots.clear();
0202 }
0203 
0204 void ScreenShotEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
0205 {
0206     m_paintedScreen = screen;
0207     effects->paintScreen(renderTarget, viewport, mask, region, screen);
0208 
0209     for (ScreenShotWindowData &data : m_windowScreenShots) {
0210         takeScreenShot(&data);
0211     }
0212     m_windowScreenShots.clear();
0213 
0214     for (int i = m_areaScreenShots.size() - 1; i >= 0; --i) {
0215         if (takeScreenShot(renderTarget, viewport, &m_areaScreenShots[i])) {
0216             m_areaScreenShots.erase(m_areaScreenShots.begin() + i);
0217         }
0218     }
0219 
0220     for (int i = m_screenScreenShots.size() - 1; i >= 0; --i) {
0221         if (takeScreenShot(renderTarget, viewport, &m_screenScreenShots[i])) {
0222             m_screenScreenShots.erase(m_screenScreenShots.begin() + i);
0223         }
0224     }
0225 }
0226 
0227 void ScreenShotEffect::takeScreenShot(ScreenShotWindowData *screenshot)
0228 {
0229     EffectWindow *window = screenshot->window;
0230 
0231     WindowPaintData d;
0232     QRectF geometry = window->expandedGeometry();
0233     qreal devicePixelRatio = 1;
0234 
0235     if (window->hasDecoration() && !(screenshot->flags & ScreenShotIncludeDecoration)) {
0236         geometry = window->clientGeometry();
0237     } else if (!(screenshot->flags & ScreenShotIncludeShadow)) {
0238         geometry = window->frameGeometry();
0239     }
0240 
0241     if (screenshot->flags & ScreenShotNativeResolution) {
0242         if (const Output *screen = window->screen()) {
0243             devicePixelRatio = screen->scale();
0244         }
0245     }
0246 
0247     bool validTarget = true;
0248     std::unique_ptr<GLTexture> offscreenTexture;
0249     std::unique_ptr<GLFramebuffer> target;
0250     if (effects->isOpenGLCompositing()) {
0251         offscreenTexture = GLTexture::allocate(GL_RGBA8, QSizeF(geometry.size() * devicePixelRatio).toSize());
0252         if (!offscreenTexture) {
0253             return;
0254         }
0255         offscreenTexture->setFilter(GL_LINEAR);
0256         offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
0257         target = std::make_unique<GLFramebuffer>(offscreenTexture.get());
0258         validTarget = target->valid();
0259     }
0260     if (validTarget) {
0261         d.setXTranslation(-geometry.x());
0262         d.setYTranslation(-geometry.y());
0263 
0264         // render window into offscreen texture
0265         int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT;
0266         QImage img;
0267         if (effects->isOpenGLCompositing()) {
0268             RenderTarget renderTarget(target.get());
0269             RenderViewport viewport(geometry, devicePixelRatio, renderTarget);
0270             GLFramebuffer::pushFramebuffer(target.get());
0271             glClearColor(0.0, 0.0, 0.0, 0.0);
0272             glClear(GL_COLOR_BUFFER_BIT);
0273             glClearColor(0.0, 0.0, 0.0, 1.0);
0274 
0275             QMatrix4x4 projection;
0276             projection.ortho(QRect(0, 0, geometry.width() * devicePixelRatio, geometry.height() * devicePixelRatio));
0277             d.setProjectionMatrix(projection);
0278 
0279             effects->drawWindow(renderTarget, viewport, window, mask, infiniteRegion(), d);
0280 
0281             // copy content from framebuffer into image
0282             img = QImage(offscreenTexture->size(), QImage::Format_ARGB32);
0283             img.setDevicePixelRatio(devicePixelRatio);
0284             glReadnPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(),
0285                           static_cast<GLvoid *>(img.bits()));
0286             GLFramebuffer::popFramebuffer();
0287             convertFromGLImage(img, img.width(), img.height(), renderTarget.transform());
0288         }
0289 
0290         if (screenshot->flags & ScreenShotIncludeCursor) {
0291             grabPointerImage(img, geometry.x(), geometry.y());
0292         }
0293 
0294         screenshot->promise.addResult(img);
0295         screenshot->promise.finish();
0296     }
0297 }
0298 
0299 bool ScreenShotEffect::takeScreenShot(const RenderTarget &renderTarget, const RenderViewport &viewport, ScreenShotAreaData *screenshot)
0300 {
0301     if (!effects->waylandDisplay()) {
0302         // On X11, all screens are painted simultaneously and there is no native HiDPI support.
0303         QImage snapshot = blitScreenshot(renderTarget, viewport, screenshot->area);
0304         if (screenshot->flags & ScreenShotIncludeCursor) {
0305             grabPointerImage(snapshot, screenshot->area.x(), screenshot->area.y());
0306         }
0307         screenshot->promise.addResult(snapshot);
0308         screenshot->promise.finish();
0309         return true;
0310     } else {
0311         if (!screenshot->screens.contains(m_paintedScreen)) {
0312             return false;
0313         }
0314         screenshot->screens.removeOne(m_paintedScreen);
0315 
0316         const QRect sourceRect = screenshot->area & m_paintedScreen->geometry();
0317         qreal sourceDevicePixelRatio = 1.0;
0318         if (screenshot->flags & ScreenShotNativeResolution) {
0319             sourceDevicePixelRatio = m_paintedScreen->scale();
0320         }
0321 
0322         const QImage snapshot = blitScreenshot(renderTarget, viewport, sourceRect, sourceDevicePixelRatio);
0323         const QRect nativeArea(screenshot->area.topLeft(),
0324                                screenshot->area.size() * screenshot->result.devicePixelRatio());
0325 
0326         QPainter painter(&screenshot->result);
0327         painter.setRenderHint(QPainter::SmoothPixmapTransform);
0328         painter.setWindow(nativeArea);
0329         painter.drawImage(sourceRect, snapshot);
0330         painter.end();
0331 
0332         if (screenshot->screens.isEmpty()) {
0333             if (screenshot->flags & ScreenShotIncludeCursor) {
0334                 grabPointerImage(screenshot->result, screenshot->area.x(), screenshot->area.y());
0335             }
0336             screenshot->promise.addResult(screenshot->result);
0337             screenshot->promise.finish();
0338             return true;
0339         }
0340     }
0341 
0342     return false;
0343 }
0344 
0345 bool ScreenShotEffect::takeScreenShot(const RenderTarget &renderTarget, const RenderViewport &viewport, ScreenShotScreenData *screenshot)
0346 {
0347     if (m_paintedScreen && screenshot->screen != m_paintedScreen) {
0348         return false;
0349     }
0350 
0351     qreal devicePixelRatio = 1.0;
0352     if (screenshot->flags & ScreenShotNativeResolution) {
0353         devicePixelRatio = screenshot->screen->scale();
0354     }
0355 
0356     QImage snapshot = blitScreenshot(renderTarget, viewport, screenshot->screen->geometry(), devicePixelRatio);
0357     if (screenshot->flags & ScreenShotIncludeCursor) {
0358         const int xOffset = screenshot->screen->geometry().x();
0359         const int yOffset = screenshot->screen->geometry().y();
0360         grabPointerImage(snapshot, xOffset, yOffset);
0361     }
0362 
0363     screenshot->promise.addResult(snapshot);
0364     screenshot->promise.finish();
0365 
0366     return true;
0367 }
0368 
0369 QImage ScreenShotEffect::blitScreenshot(const RenderTarget &renderTarget, const RenderViewport &viewport, const QRect &geometry, qreal devicePixelRatio) const
0370 {
0371     QImage image;
0372 
0373     if (effects->isOpenGLCompositing()) {
0374         const auto screenGeometry = m_paintedScreen ? m_paintedScreen->geometry() : effects->virtualScreenGeometry();
0375         const QSize nativeSize = renderTarget.transform().map(
0376             snapToPixelGrid(scaledRect(geometry, devicePixelRatio))
0377                 .translated(-snapToPixelGrid(scaledRect(screenGeometry, devicePixelRatio)).topLeft())
0378                 .size());
0379         image = QImage(nativeSize, QImage::Format_ARGB32);
0380 
0381         const auto texture = GLTexture::allocate(GL_RGBA8, nativeSize);
0382         if (!texture) {
0383             return {};
0384         }
0385         GLFramebuffer target(texture.get());
0386         if (renderTarget.texture()) {
0387             GLFramebuffer::pushFramebuffer(&target);
0388             ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
0389             binder.shader()->setColorspaceUniformsToSRGB(renderTarget.colorDescription());
0390             QMatrix4x4 projectionMatrix;
0391             projectionMatrix.scale(1, -1);
0392             projectionMatrix *= renderTarget.transform().toMatrix();
0393             projectionMatrix.scale(1, -1);
0394             projectionMatrix.ortho(QRect(QPoint(), nativeSize));
0395             binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix);
0396             renderTarget.texture()->render(viewport.mapToRenderTargetTexture(geometry), infiniteRegion(), nativeSize);
0397         } else {
0398             target.blitFromFramebuffer(viewport.mapToRenderTarget(geometry));
0399             GLFramebuffer::pushFramebuffer(&target);
0400         }
0401         glReadPixels(0, 0, nativeSize.width(), nativeSize.height(), GL_RGBA,
0402                      GL_UNSIGNED_BYTE, static_cast<GLvoid *>(image.bits()));
0403         GLFramebuffer::popFramebuffer();
0404         convertFromGLImage(image, nativeSize.width(), nativeSize.height(), renderTarget.transform());
0405     }
0406 
0407     image.setDevicePixelRatio(devicePixelRatio);
0408     return image;
0409 }
0410 
0411 void ScreenShotEffect::grabPointerImage(QImage &snapshot, int xOffset, int yOffset) const
0412 {
0413     if (effects->isCursorHidden()) {
0414         return;
0415     }
0416 
0417     const PlatformCursorImage cursor = effects->cursorImage();
0418     if (cursor.image().isNull()) {
0419         return;
0420     }
0421 
0422     QPainter painter(&snapshot);
0423     painter.setRenderHint(QPainter::SmoothPixmapTransform);
0424     painter.drawImage(effects->cursorPos() - cursor.hotSpot() - QPoint(xOffset, yOffset), cursor.image());
0425 }
0426 
0427 bool ScreenShotEffect::isActive() const
0428 {
0429     return (!m_windowScreenShots.empty() || !m_areaScreenShots.empty() || !m_screenScreenShots.empty())
0430         && !effects->isScreenLocked();
0431 }
0432 
0433 int ScreenShotEffect::requestedEffectChainPosition() const
0434 {
0435     return 0;
0436 }
0437 
0438 void ScreenShotEffect::handleScreenAdded()
0439 {
0440     cancelAreaScreenShots();
0441 }
0442 
0443 void ScreenShotEffect::handleScreenRemoved(Output *screen)
0444 {
0445     cancelAreaScreenShots();
0446 
0447     std::erase_if(m_screenScreenShots, [screen](const auto &screenshot) {
0448         return screenshot.screen == screen;
0449     });
0450 }
0451 
0452 void ScreenShotEffect::handleWindowClosed(EffectWindow *window)
0453 {
0454     std::erase_if(m_windowScreenShots, [window](const auto &screenshot) {
0455         return screenshot.window == window;
0456     });
0457 }
0458 
0459 } // namespace KWin
0460 
0461 #include "moc_screenshot.cpp"