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 ®ion, 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"