Qt Mac OS、iOS和X11的Retina顯示支持

Mac OS、iOS和X11的Retina顯示支持

Published 星期四 五月 2nd, 2013 | by Liang Qihtml

原文連接:Morten Johan Sørvig – Retina display support for Mac OS, iOS and X11android

Qt 5.0中添加了對於retina顯示的基本支持。即將到來的Qt 5.1中提供了新的API和缺陷修復,對於這一問題進行了改進。Qt 4.8也得到了良好的支持,咱們反向移植了一些Qt 5的補丁。ios

儘管這些實現的努力和Mac以及iOS程序員最爲相關,可是來看一看其它平臺是如何處理高DPI顯示這一問題,也是頗有趣的。這裏主要有兩種方式:git

  • 基於DPI縮放——Win32 GDIKDE。在這種方式中,應用程序在全物理設備分辨率下工做,使用系統提供的一個DPI設定或者縮放因子,用於縮放佈局。字體一般會被操做系統自動縮放(只要您使用點數(point)而不是像素(pixel)來指定字體大小)程序員

  • 另外一種意義的像素。在這種方式中,應用程序並不知道物理解析度(在任何程度上)。物理像素被邏輯像素替代:github

平臺/API 邏輯的 物理的
HTML CSS像素 設備像素
Apple 像素
Android 密度無關像素(dp) (屏幕)像素
Direct2D 設備無關像素(DIP) 物理像素
Qt(過去) 像素 像素
Qt(如今) 設備無關像素 設備像素

在歷史上,Qt已經支持基於DPI縮放的物理像素這一方式。在2009年時,對於Windows上的高DPI值的支持已經有所改進。Qt佈局對於增長的DPI並無考慮。如今Qt 5添加了對於「新增像素」這一縮放類型的支持。windows

(還有其它的高DPI實現麼?歡迎你們在評論中進行指正。)後端

Mac OS X的高DPI支持

OS X上高DPI模式的關鍵是,之前絕大多數幾何信息都是經過物理像素給定的,如今倒是設備無關點。這包括桌面幾何信息(例如15英寸的Retina MacBook Pro是1440x900而不是全分辨率2880×1800)、窗口幾何信息和事件座標。CoreGraphics繪製引擎知道全分辨率而且針對這一解析度生成輸出。例如,對於普通屏幕和高DPI屏幕(其它參數都相同的狀況下),一個100x100的窗口在屏幕上佔用的區域是同樣的。在高DPI屏幕的窗口後端存儲包含了200x200像素。app

這種模式的主要收益是向後兼容性以及自由的高DPI矢量圖形。對於底層狀況不瞭解的應用程序能夠簡單地像之前同樣工做在相同的幾何狀況下,而且保留寫死的像素值。同時,他們還可使用如文本這樣的矢量圖形,而不用作任何修改。光柵圖形引擎不能得到自動改進,但這是能夠實現的。很差的一點是在代碼中使用點和像素的時候,有不可避免的座標系統混淆。ide

點到像素的縮放因子老是2x。在改變屏幕分辨率的時候,這一狀況也是真的——點和像素老是被一個值同時縮放。當使用「More Space」進行縮放的時候,應用程序將會被渲染到一個大的後端存儲,這個後端存儲會被再縮小到物理屏幕解析度上。

在Mac OS上縮放用戶界面解析度

若是您手裏沒有Retina硬件,在使用外部顯示器的時候,有一種仿真模式仍是頗有用的。打開顯示器(Displays)屬性而且選擇一種HiDPI模式。(若是沒有,請查看stackoverflow上的這個問題。)

爲OS X應用程序啓用高DPI

高DPI模式是經過Info.Plist文件中的這些鍵值控制的:

<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>

qmake將會爲您添加這些內容。(嚴格意義上說,它將會只添加NSPrincipalClass,NSHighResolutionCapable是可選的而且默認值爲true)。

若是NSHighResolutionCapable被設置爲false,或者缺乏這些鍵值,那麼應用程序將會被按「普通」解析度渲染而後放大。這樣的結果看起來很糟糕而且應該避免,特別是由於高DPI模式是很是向後兼容的,而且應用程序能夠得到不少高DPI支持而不用作任何修改。

縮放的Qt Creator

高DPI的Qt Creator

(除了一個更新「模式」圖標的補丁以外,沒有其它修改了。)

Qt的實現細節

Mac OS 10.8(10.7是非正式的?)添加了對高DPI的Retina顯示的支持。Qt 4免費得到這一支持,由於它使用的是CoreGraphics繪製引擎。

Qt 5使用的是光柵繪製引擎而且Qt經過縮放繪圖器變換(transform)實現了高DPI矢量的繪製。HITheme同時爲Qt 4和5提供了高DPI的Mac風格。在Qt 5的Fusion風格中,對於高DPI模式的支持也已經修改好了。

OpenGL是一種基於設備像素的API而且對於高DPI模式也仍然如此。在NSView中有一個flag能夠用來開啓或者禁用2x縮放——Qt在全部狀況下均可以設置它。Shaders運行在設備像素中。

Qt Quick 1是構建於QGraphicsView之上的,它是一個QWidget而且經過QPainter得到對於高DPI的支持。

Qt Quick 2是基於Scene Graph(和OpenGL),已經更新了高DPI的支持。Qt Quick控件(也就是之前的Desktop Component)也已經更新了在高DPI模式下的渲染,其中包括距離場(distance field)文本渲染。(譯者注:關於距離場,能夠參考Yoann Lopes – Text Rendering in the QML Scene Graph以及iKDE上的譯文。)

這裏的賣點是應用程序開發人員不須要關心這些,您只須要在設備無關像素的空間裏溫馨地開發,Qt和/或OS會爲您處理那些複雜的事情。但有一個例外,光柵內容(raster content)——須要提供高DPI光柵內容,而且應用程序代碼須要正確處理這些內容。

窗口部件和繪圖器

QPainter代碼絕大多數狀況下都和原來同樣。咱們來看看繪製漸變(gradient)的代碼:

QRect destinationRect = ...
QGradient gradient = ...
painter.fillRect(rect, QBrush(gradient));

在高DPI顯示器上,這個漸變在屏幕上的大小仍是同樣的,可是被填充了更多的(設備)像素。

繪製一個像素映射(pixmap)也是相似的:

QRect destinationRect = ...
QPixmap pixmap = ...
painter.drawPixmap(destinationRect, pixmap);

爲了不在高DPI顯示器上出現縮放失真,像素映射必須包含足夠的像素:兩倍於destinationRect的寬和高。應用程序能夠直接提供它們,也可使用QIcon來管理不一樣的解析度:

QRect destinationRect = ...
QIcon icon = ...
painter.drawPixmap(destinationRect, icon.pixmap(destinationRect.size()));

QIcon::pixmap()已經被修改了,能夠在高DPI系統中返回一個更大的像素映射。這種行爲的改變會破壞現有的代碼,因此它是由AA_UseHighDpiPixmaps這個應用程序屬性來控制的:

qApp->setAttribute(Qt::AA_UseHighDpiPixmaps);

在Qt 5.1中這個屬性默認值是關閉的,但在將來的Qt發佈中它頗有可能默認爲打開。

極端狀況和devicePixelRatio

Qt的窗口部件有一些極端狀況。在理想狀況下,它一直使用QIcon,而且在繪製的時候會使用正確的像素映射,可是實際狀況是Qt API常常直接生成和使用像素映射。當像素映射的大小被用來計算佈局的幾何信息時,會發生錯誤——若是一個像素映射已是高分辨率的,那麼在屏幕上它就不該該再佔用更多的空間。

經過使用QPixmap::devicePixelRatio(),就能讓200x200的像素映射實際佔據100x100的設備無關像素。由QIcon::pixmap()返回的像素映射中devicePixelRatio已經設置好了。

例如QLabel就是一個「像素映射消費者」:

QPixmap pixmap2x = ...
pixmap2x.setDevicePixelRatio(2.0);
QLabel *label = ...
label->setPixmap(pixmap2x);

而後QLabel會除以devicePixelRatio來得到佈局的大小:

QSize layoutSize = pixmap.size() / pixmap.devicePixelRatio();

與此相似的幾種狀況在Qt中都已經修復,而且應用程序代碼在啓用AA_UseHighDpiPixmaps以前也須要作相似處理。

下面幾個Qt類中都提供了devicePixelRatio()的讀寫函數:

註釋
QWindow::devicePixelRatio() 推薦使用的讀寫函數
QScreen::devicePixelRatio()
QGuiApplication::devicePixelRatio() 若是沒有QWindow指針,請使用這個
QImage::[set]devicePixelRatio()
QPixmap::[set]devicePixelRatio()

文本

字體大小還能夠和原來同樣,會在高DPI顯示中產生相似的大小(但會有一點小問題)。字體的像素大小是設備無關的像素大小。您在高DPI顯示中永遠不會獲得過小的文本。

QGLWidget

OpenGL是在設備像素空間中工做的。例如,傳遞給glViewport的寬和高應該是設備像素。QGLWidget::resizeGL()中的寬和高也是設備像素的。

無論怎樣,QGLWidget::width()實際上就是QWidget::width(),它返回的是設備無關像素的值。若是須要,用它來乘以widget->windowHandle()->devicePixelRatio()能夠解決不少問題。

Qt Quick 2和控件

Qt Quick 2和Qt Quick控件能夠直接使用。由於窗口部件的座標是設備無關像素的。Qt Quick也有幾個和光柵相關的極端狀況,由於QML的Image元素是經過URL來指定圖像源的,這樣就避免了像素映射的傳遞。

Qt Quick控件

還有一個例外是OpenGL着色器(shader),它運行在設備像素空間中而且能夠看到全分辨率。在一般狀況下這沒有什麼問題,咱們應該知道的一件重要的事情是,鼠標座標是設備無關像素的,也許須要被轉換成設備像素。

運行中的着色器效果實例

管理高解析度的光柵內容

正如咱們所看到的,在縮放的狀況下,光柵內容看起來會不夠好,因此必須提供高解析度的內容。做爲應用程序開發人員,您有兩個選項:(請忽略「什麼都不作」選項)

  • 使用高解析度版本替換現有光柵內容

  • 另外提供一份高解析度內容

第一個選項很簡單,由於每一個資源只有一個版本。但是您也許會發現(或者您的設計師會告訴您)像圖標這樣的資源只有在它被建立的那個特定解析度下看起來才最好。爲了解決這個問題,Qt沿用了「@2x」這種圖像文件名的方案:

foo.png
foo@2x.png

這樣高解析度的內容和原來的一一對應。在須要的時候,「@2x」的版本會被QML的Image元素以及QIcon自動加載。

Image { source = 「foo.png」 }
QIcon icon(「foo.png」)

(對於QIcon請記住使用AA_UseHighDpiPixmaps)

試驗性的跨平臺的高解析度支持

QPA容許咱們相對容易的完成跨平臺的實現。Qt如今把這一問題分爲三層:

  • 應用程序層(應用程序代碼和使用QPA類的Qt代碼)

  • QPA層(QWindow、QScreen、QBackingStore)

  • 平臺插件層(QPlatform*子類)

簡化一下,應用程序層是在設備無關像素空間中工做的,並不知道設備像素。平臺插件是在設備像素空間中工做的,並不知道設備無關像素。QPA層在二者之間,基於一個由環境變量QT_HIGHDPI_SCALE_FACTOR指定的縮放因子進行轉換。

實際上,這個狀況還會更復雜一些,各層之間會有泄露的事情發生,而且在Mac和iOS下還會有一些例外狀況。

代碼在github上。最後是XCB下的Qt Creator的截屏:

DPI縮放的Qt Creator

QT_HIGDPI_SCALE_FACTOR=2縮放的Qt Creator

Leave a comment


Posted in qt-labs-chinese | Tags:  GraphicsMac OS XQPA, Qt
相關文章
相關標籤/搜索