From 5e61bbe586519c3d9bc636153d32e810da4e59a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Tue, 20 Nov 2012 11:34:52 +0100 Subject: Basic high-dpi "retina" support for Qt 5. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring Qt 5 on par with Qt 4, prepare for more comprehensive support later on. Introduce device independent pixels (dips), device pixels, and devicePixelRatio. Add high-dpi support to QPainter, QGLWidget, the cocoa platform plugin, mac and fusion styles. Dips are similar to CSS pixels, Apple points and Android density-independent pixels. Device pixels are pixels in the backing store/physical pixels on screen. devicePixelRatio is the ratio between them, which is 1.0 on standard displays and 2.0 on "retina" displays. New API: QImage::devicePixelRatio() and setDevicePixelRatio() QPixmap::devicePixelRatio() and setDevicePixelRatio() QWindow::devicePixelRatio() QScreen::devicePixelRatio() QGuiApplicaiton::devicePixelRatio() Change-Id: If98c3ca9bfdf0e1bdbcf7574cd5b912c9ff63856 Reviewed-by: Morten Johan Sørvig Reviewed-by: Gunnar Sletta --- src/plugins/platforms/cocoa/qcocoabackingstore.h | 1 + src/plugins/platforms/cocoa/qcocoabackingstore.mm | 26 ++++++++++++++++--- src/plugins/platforms/cocoa/qcocoaglcontext.mm | 12 +++++++++ src/plugins/platforms/cocoa/qcocoaintegration.h | 1 + src/plugins/platforms/cocoa/qcocoaintegration.mm | 12 +++++++++ src/plugins/platforms/cocoa/qcocoawindow.h | 2 ++ src/plugins/platforms/cocoa/qcocoawindow.mm | 15 +++++++++++ src/plugins/platforms/cocoa/qnsview.mm | 31 +++++++++++++++-------- 8 files changed, 86 insertions(+), 14 deletions(-) (limited to 'src/plugins/platforms/cocoa') diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 0e1998170a..192ef00649 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -62,6 +62,7 @@ public: void resize (const QSize &size, const QRegion &); bool scroll(const QRegion &area, int dx, int dy); CGImageRef getBackingStoreCGImage(); + qreal getBackingStoreDevicePixelRatio(); private: QImage m_qImage; diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 7bd7e4ce38..ec3168ce99 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -59,10 +59,22 @@ QCocoaBackingStore::~QCocoaBackingStore() QPaintDevice *QCocoaBackingStore::paintDevice() { - if (m_qImage.size() != m_requestedSize) { + if (m_qImage.size() / m_qImage.devicePixelRatio() != m_requestedSize) { CGImageRelease(m_cgImage); m_cgImage = 0; - m_qImage = QImage(m_requestedSize, QImage::Format_ARGB32_Premultiplied); + + int scaleFactor = 1; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + QCocoaWindow *cocoaWindow = static_cast(window()->handle()); + if (cocoaWindow && cocoaWindow->m_contentView) { + scaleFactor = int([[cocoaWindow->m_contentView window] backingScaleFactor]); + } + } +#endif + + m_qImage = QImage(m_requestedSize * scaleFactor, QImage::Format_ARGB32_Premultiplied); + m_qImage.setDevicePixelRatio(scaleFactor); } return &m_qImage; } @@ -90,10 +102,11 @@ void QCocoaBackingStore::resize(const QSize &size, const QRegion &) bool QCocoaBackingStore::scroll(const QRegion &area, int dx, int dy) { extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); - QPoint qpoint(dx, dy); + const qreal devicePixelRatio = m_qImage.devicePixelRatio(); + QPoint qpoint(dx * devicePixelRatio, dy * devicePixelRatio); const QVector qrects = area.rects(); for (int i = 0; i < qrects.count(); ++i) { - const QRect &qrect = qrects.at(i); + const QRect &qrect = QRect(qrects.at(i).topLeft() * devicePixelRatio, qrects.at(i).size() * devicePixelRatio); qt_scrollRectInImage(m_qImage, qrect, qpoint); } return true; @@ -110,4 +123,9 @@ CGImageRef QCocoaBackingStore::getBackingStoreCGImage() return m_cgImage; } +qreal QCocoaBackingStore::getBackingStoreDevicePixelRatio() +{ + return m_qImage.devicePixelRatio(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index d9bb9c60a9..99956a0b60 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -114,6 +114,18 @@ void QCocoaGLContext::setActiveWindow(QWindow *window) cocoaWindow->setCurrentContext(this); [(QNSView *) cocoaWindow->contentView() setQCocoaGLContext:this]; + + // Enable high-dpi OpenGL for retina displays. Enabling has the side + // effect that Cooca will start calling glViewport(0, 0, width, height), + // overriding any glViewport calls in application code. This is usually not a + // problem, except if the applcation wants to have a "custom" viewport. + // (like the hellogl example) +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + if (cocoaWindow->devicePixelRatio() > 1) + [cocoaWindow->contentView() setWantsBestResolutionOpenGLSurface:YES]; + } +#endif } void QCocoaGLContext::doneCurrent() diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index dae9872566..1bb46ea3ea 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -68,6 +68,7 @@ public: QRect availableGeometry() const { return m_availableGeometry; } int depth() const { return m_depth; } QImage::Format format() const { return m_format; } + qreal devicePixelRatio() const; QSizeF physicalSize() const { return m_physicalSize; } QDpi logicalDpi() const { return m_logicalDpi; } qreal refreshRate() const { return m_refreshRate; } diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 3767fa014d..393c471c25 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -115,6 +115,18 @@ void QCocoaScreen::updateGeometry() QWindowSystemInterface::handleScreenAvailableGeometryChange(screen(), availableGeometry()); } +qreal QCocoaScreen::devicePixelRatio() const +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + return qreal([m_screen backingScaleFactor]); + } else +#endif + { + return 1.0; + } +} + extern CGContextRef qt_mac_cg_context(const QPaintDevice *pdev); QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index a9ea135b3e..228644c351 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -140,6 +140,8 @@ public: void setMenubar(QCocoaMenuBar *mb); QCocoaMenuBar *menubar() const; + + qreal devicePixelRatio() const; protected: // NSWindow handling. The QCocoaWindow/QNSView can either be displayed // in an existing NSWindow or in one created by Qt. diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 77073d9bc6..c3b2139998 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -39,6 +39,7 @@ ** ****************************************************************************/ #include "qcocoawindow.h" +#include "qcocoaintegration.h" #include "qnswindowdelegate.h" #include "qcocoaautoreleasepool.h" #include "qcocoaeventdispatcher.h" @@ -820,6 +821,20 @@ QCocoaMenuBar *QCocoaWindow::menubar() const return m_menubar; } +qreal QCocoaWindow::devicePixelRatio() const +{ + if (!m_nsWindow) + return 1.0; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + return qreal([m_nsWindow backingScaleFactor]); + } else +#endif + { + return 1.0; + } +} + QMargins QCocoaWindow::frameMargins() const { NSRect frameW = [m_nsWindow frame]; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index d2a4685872..b608989e43 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -244,7 +244,7 @@ static QTouchDevice *touchDevice = 0; - (void) flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset { m_backingStore = backingStore; - m_backingStoreOffset = offset; + m_backingStoreOffset = offset * m_backingStore->getBackingStoreDevicePixelRatio(); QRect br = region.boundingRect(); [self setNeedsDisplayInRect:NSMakeRect(br.x(), br.y(), br.width(), br.height())]; } @@ -275,33 +275,44 @@ static QTouchDevice *touchDevice = 0; if (!m_backingStore) return; - CGRect dirtyCGRect = NSRectToCGRect(dirtyRect); + // Calculate source and target rects. The target rect is the dirtyRect: + CGRect dirtyWindowRect = NSRectToCGRect(dirtyRect); + + // The backing store source rect will be larger on retina displays. + // Scale dirtyRect by the device pixel ratio: + const qreal devicePixelRatio = m_backingStore->getBackingStoreDevicePixelRatio(); + CGRect dirtyBackingRect = CGRectMake(dirtyRect.origin.x * devicePixelRatio, + dirtyRect.origin.y * devicePixelRatio, + dirtyRect.size.width * devicePixelRatio, + dirtyRect.size.height * devicePixelRatio); + NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext]; CGContextRef cgContext = (CGContextRef) [nsGraphicsContext graphicsPort]; // Translate coordiate system from CoreGraphics (bottom-left) to NSView (top-left): CGContextSaveGState(cgContext); - int dy = dirtyCGRect.origin.y + CGRectGetMaxY(dirtyCGRect); + int dy = dirtyWindowRect.origin.y + CGRectGetMaxY(dirtyWindowRect); + CGContextTranslateCTM(cgContext, 0, dy); CGContextScaleCTM(cgContext, 1, -1); // If a mask is set, modify the sub image accordingly: CGImageRef subMask = 0; if (m_maskImage) { - subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyCGRect); - CGContextClipToMask(cgContext, dirtyCGRect, subMask); + subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyWindowRect); + CGContextClipToMask(cgContext, dirtyWindowRect, subMask); } // Clip out and draw the correct sub image from the (shared) backingstore: CGRect backingStoreRect = CGRectMake( - dirtyRect.origin.x + m_backingStoreOffset.x(), - dirtyRect.origin.y + m_backingStoreOffset.y(), - dirtyRect.size.width, - dirtyRect.size.height + dirtyBackingRect.origin.x + m_backingStoreOffset.x(), + dirtyBackingRect.origin.y + m_backingStoreOffset.y(), + dirtyBackingRect.size.width, + dirtyBackingRect.size.height ); CGImageRef bsCGImage = m_backingStore->getBackingStoreCGImage(); CGImageRef cleanImg = CGImageCreateWithImageInRect(bsCGImage, backingStoreRect); - CGContextDrawImage(cgContext, dirtyCGRect, cleanImg); + CGContextDrawImage(cgContext, dirtyWindowRect, cleanImg); // Clean-up: CGContextRestoreGState(cgContext); -- cgit v1.2.3