Changeset View
Changeset View
Standalone View
Standalone View
effects/screenshot/screenshot.cpp
Show First 20 Lines • Show All 153 Lines • ▼ Show 20 Line(s) | 122 | { | |||
---|---|---|---|---|---|
154 | xcb_free_gc(c, gc); | 154 | xcb_free_gc(c, gc); | ||
155 | 155 | | |||
156 | return pixmap; | 156 | return pixmap; | ||
157 | } | 157 | } | ||
158 | 158 | | |||
159 | void ScreenShotEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) | 159 | void ScreenShotEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) | ||
160 | { | 160 | { | ||
161 | m_cachedOutputGeometry = data.outputGeometry(); | 161 | m_cachedOutputGeometry = data.outputGeometry(); | ||
162 | m_cachedScale = data.screenScale(); | ||||
162 | effects->paintScreen(mask, region, data); | 163 | effects->paintScreen(mask, region, data); | ||
163 | } | 164 | } | ||
164 | 165 | | |||
165 | void ScreenShotEffect::postPaintScreen() | 166 | void ScreenShotEffect::postPaintScreen() | ||
166 | { | 167 | { | ||
167 | effects->postPaintScreen(); | 168 | effects->postPaintScreen(); | ||
168 | if (m_scheduledScreenshot) { | 169 | if (m_scheduledScreenshot) { | ||
169 | WindowPaintData d(m_scheduledScreenshot); | 170 | WindowPaintData d(m_scheduledScreenshot); | ||
▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Line(s) | 278 | #endif | |||
278 | } | 279 | } | ||
279 | m_scheduledScreenshot = nullptr; | 280 | m_scheduledScreenshot = nullptr; | ||
280 | } | 281 | } | ||
281 | 282 | | |||
282 | if (!m_scheduledGeometry.isNull()) { | 283 | if (!m_scheduledGeometry.isNull()) { | ||
283 | if (!m_cachedOutputGeometry.isNull()) { | 284 | if (!m_cachedOutputGeometry.isNull()) { | ||
284 | // special handling for per-output geometry rendering | 285 | // special handling for per-output geometry rendering | ||
285 | const QRect intersection = m_scheduledGeometry.intersected(m_cachedOutputGeometry); | 286 | const QRect intersection = m_scheduledGeometry.intersected(m_cachedOutputGeometry); | ||
287 | | ||||
286 | if (intersection.isEmpty()) { | 288 | if (intersection.isEmpty()) { | ||
287 | // doesn't intersect, not going onto this screenshot | 289 | // doesn't intersect, not going onto this screenshot | ||
288 | return; | 290 | return; | ||
289 | } | 291 | } | ||
290 | const QImage img = blitScreenshot(intersection); | 292 | | ||
291 | if (img.size() == m_scheduledGeometry.size()) { | 293 | const QImage img = blitScreenshot(intersection, m_cachedScale); | ||
294 | | ||||
295 | if (img.size() == QSize(m_scheduledGeometry.width() * m_cachedScale, | ||||
296 | m_scheduledGeometry.height() * m_cachedScale)) { | ||||
292 | // we are done | 297 | // we are done | ||
293 | sendReplyImage(img); | 298 | sendReplyImage(img); | ||
294 | return; | 299 | return; | ||
295 | } | 300 | } | ||
296 | if (m_multipleOutputsImage.isNull()) { | 301 | | ||
297 | m_multipleOutputsImage = QImage(m_scheduledGeometry.size(), QImage::Format_ARGB32); | 302 | m_cacheOutputsImages.append(img); | ||
298 | m_multipleOutputsImage.fill(Qt::transparent); | 303 | | ||
304 | m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); | ||||
305 | | ||||
306 | if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { | ||||
307 | | ||||
308 | int width = 0; | ||||
309 | int height = 0; | ||||
310 | // find the output image size | ||||
311 | for (const QImage& img: m_cacheOutputsImages) { | ||||
312 | width = qMax(width, img.width()); | ||||
313 | height = height + img.height(); | ||||
299 | } | 314 | } | ||
315 | QSize outputSize(width, height); | ||||
316 | QImage m_multipleOutputsImage = QImage(outputSize, QImage::Format_ARGB32); | ||||
317 | | ||||
318 | QPoint pointer(0, 0); | ||||
300 | QPainter p; | 319 | QPainter p; | ||
301 | p.begin(&m_multipleOutputsImage); | 320 | p.begin(&m_multipleOutputsImage); | ||
302 | p.drawImage(intersection.topLeft() - m_scheduledGeometry.topLeft(), img); | 321 | | ||
322 | // reassemble images together | ||||
323 | // stacking them on the y axis on top of each until better implemented | ||||
324 | for (const QImage& img: m_cacheOutputsImages) { | ||||
325 | p.drawImage(pointer, img); | ||||
326 | pointer.setY(pointer.y() + img.height()); | ||||
327 | } | ||||
303 | p.end(); | 328 | p.end(); | ||
304 | m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); | 329 | | ||
305 | if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { | | |||
306 | sendReplyImage(m_multipleOutputsImage); | 330 | sendReplyImage(m_multipleOutputsImage); | ||
307 | } | 331 | } | ||
308 | 332 | | |||
309 | } else { | 333 | } else { | ||
310 | const QImage img = blitScreenshot(m_scheduledGeometry); | 334 | const QImage img = blitScreenshot(m_scheduledGeometry, m_cachedScale); | ||
311 | sendReplyImage(img); | 335 | sendReplyImage(img); | ||
312 | } | 336 | } | ||
313 | } | 337 | } | ||
314 | } | 338 | } | ||
315 | 339 | | |||
316 | void ScreenShotEffect::sendReplyImage(const QImage &img) | 340 | void ScreenShotEffect::sendReplyImage(const QImage &img) | ||
317 | { | 341 | { | ||
318 | if (m_fd != -1) { | 342 | if (m_fd != -1) { | ||
319 | QtConcurrent::run( | 343 | QtConcurrent::run( | ||
320 | [] (int fd, const QImage &img) { | 344 | [] (int fd, const QImage &img) { | ||
321 | QFile file; | 345 | QFile file; | ||
322 | if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { | 346 | if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { | ||
323 | QDataStream ds(&file); | 347 | QDataStream ds(&file); | ||
324 | ds << img; | 348 | ds << img; | ||
325 | file.close(); | 349 | file.close(); | ||
326 | } else { | 350 | } else { | ||
327 | close(fd); | 351 | close(fd); | ||
328 | } | 352 | } | ||
329 | }, m_fd, img); | 353 | }, m_fd, img); | ||
330 | m_fd = -1; | 354 | m_fd = -1; | ||
331 | } else { | 355 | } else { | ||
332 | QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); | 356 | QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); | ||
333 | } | 357 | } | ||
334 | m_scheduledGeometry = QRect(); | 358 | m_scheduledGeometry = QRect(); | ||
335 | m_multipleOutputsImage = QImage(); | | |||
336 | m_multipleOutputsRendered = QRegion(); | 359 | m_multipleOutputsRendered = QRegion(); | ||
337 | m_captureCursor = false; | 360 | m_captureCursor = false; | ||
338 | m_windowMode = WindowMode::NoCapture; | 361 | m_windowMode = WindowMode::NoCapture; | ||
362 | m_cacheOutputsImages = QVector<QImage>(); | ||||
339 | } | 363 | } | ||
340 | 364 | | |||
341 | QString ScreenShotEffect::saveTempImage(const QImage &img) | 365 | QString ScreenShotEffect::saveTempImage(const QImage &img) | ||
342 | { | 366 | { | ||
343 | if (img.isNull()) { | 367 | if (img.isNull()) { | ||
344 | return QString(); | 368 | return QString(); | ||
345 | } | 369 | } | ||
346 | QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png")); | 370 | QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png")); | ||
▲ Show 20 Lines • Show All 252 Lines • ▼ Show 20 Line(s) | 610 | { | |||
599 | } | 623 | } | ||
600 | m_captureCursor = captureCursor; | 624 | m_captureCursor = captureCursor; | ||
601 | m_replyMessage = message(); | 625 | m_replyMessage = message(); | ||
602 | setDelayedReply(true); | 626 | setDelayedReply(true); | ||
603 | effects->addRepaint(m_scheduledGeometry); | 627 | effects->addRepaint(m_scheduledGeometry); | ||
604 | return QString(); | 628 | return QString(); | ||
605 | } | 629 | } | ||
606 | 630 | | |||
607 | QImage ScreenShotEffect::blitScreenshot(const QRect &geometry) | 631 | QImage ScreenShotEffect::blitScreenshot(const QRect &geometry, qreal scale) | ||
608 | { | 632 | { | ||
609 | QImage img; | 633 | QImage img; | ||
610 | if (effects->isOpenGLCompositing()) | 634 | if (effects->isOpenGLCompositing()) | ||
611 | { | 635 | { | ||
612 | img = QImage(geometry.size(), QImage::Format_ARGB32); | 636 | int width = geometry.width(); | ||
637 | int height = geometry.height(); | ||||
613 | if (GLRenderTarget::blitSupported() && !GLPlatform::instance()->isGLES()) { | 638 | if (GLRenderTarget::blitSupported() && !GLPlatform::instance()->isGLES()) { | ||
614 | GLTexture tex(GL_RGBA8, geometry.width(), geometry.height()); | 639 | | ||
640 | width = static_cast<int>(width * scale); | ||||
641 | height = static_cast<int>(height * scale); | ||||
davidedmundson: Good question.
Use of Qt methods is slightly weird, as kwin implements it's own QPA which… | |||||
642 | | ||||
643 | img = QImage(width, height, QImage::Format_ARGB32); | ||||
644 | img.setDevicePixelRatio(scale); | ||||
645 | | ||||
646 | GLTexture tex(GL_RGBA8, width, height); | ||||
615 | GLRenderTarget target(tex); | 647 | GLRenderTarget target(tex); | ||
616 | target.blitFromFramebuffer(geometry); | | |||
617 | // copy content from framebuffer into image | 648 | // copy content from framebuffer into image | ||
Avoid the term scaled in any variable name. It can mean scaled from logical to device, or scaled from device to logical. Which means it fails to convey the one piece of important information you're trying to say. use something like"deviceGeometry" davidedmundson: Avoid the term scaled in any variable name.
It can mean scaled from logical to device, or… | |||||
649 | target.blitFromFramebuffer(geometry); | ||||
618 | tex.bind(); | 650 | tex.bind(); | ||
619 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); | 651 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast<GLvoid*>(img.bits())); | ||
620 | tex.unbind(); | 652 | tex.unbind(); | ||
621 | } else { | 653 | } else { | ||
622 | glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); | 654 | img = QImage(width, height, QImage::Format_ARGB32); | ||
655 | glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, static_cast<GLvoid*>(img.bits())); | ||||
623 | } | 656 | } | ||
624 | ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height()); | 657 | ScreenShotEffect::convertFromGLImage(img, width, height); | ||
625 | } | 658 | } | ||
626 | 659 | | |||
627 | #ifdef KWIN_HAVE_XRENDER_COMPOSITING | 660 | #ifdef KWIN_HAVE_XRENDER_COMPOSITING | ||
628 | if (effects->compositingType() == XRenderCompositing) { | 661 | if (effects->compositingType() == XRenderCompositing) { | ||
629 | xcb_image_t *xImage = nullptr; | 662 | xcb_image_t *xImage = nullptr; | ||
630 | img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage); | 663 | img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage); | ||
631 | if (xImage) { | 664 | if (xImage) { | ||
632 | xcb_image_destroy(xImage); | 665 | xcb_image_destroy(xImage); | ||
633 | } | 666 | } | ||
634 | } | 667 | } | ||
635 | #endif | 668 | #endif | ||
636 | 669 | | |||
637 | if (m_captureCursor) { | 670 | if (m_captureCursor) { | ||
638 | grabPointerImage(img, geometry.x(), geometry.y()); | 671 | grabPointerImage(img, static_cast<int>(geometry.x() * scale), static_cast<int>(geometry.y() * scale)); | ||
639 | } | 672 | } | ||
640 | 673 | | |||
641 | return img; | 674 | return img; | ||
642 | } | 675 | } | ||
643 | 676 | | |||
644 | void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety) | 677 | void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety) | ||
645 | { | 678 | { | ||
646 | const auto cursor = effects->cursorImage(); | 679 | const auto cursor = effects->cursorImage(); | ||
▲ Show 20 Lines • Show All 66 Lines • Show Last 20 Lines |
Good question.
Use of Qt methods is slightly weird, as kwin implements it's own QPA which means we're reading things via an abstraction layer rather than directly. In theory it should work, but you're going to struggle to tie a QScreen to what's being rendered.
Ideally we would want to use KWin::Screens() but that's not exposed to effects.
GLRenderTarget::virtualScreenScale() is currently only set to screen->scale during the paint method. This is postPaint. We could set it during pre/postPaint too.