QT的Paint 系統

下面對於QT的繪製系統做一個簡要說明, 這個系統主要由三部分組成,  QPainter, QPaintDevice, QPaintEngine。

QPainter 是一個繪製接口類,提供繪製各種面向用戶的命令,而QPaintDevice 是一個QPainter繪製的目的地,相當於畫布,   而QPaintEngine 是基本繪製命令的具體實現。

我們打交道比較多的是 QPainter , 注意對於Windows平臺來說,當繪製目標是一個widget的時候,QPainter只能在 paintEvent() 裏面或者由paintEvent()導致調用的函數裏面使用。

QPainter 可以定製如下的一些參數:

font() 字體,輔助接口 fontInfo() 和 fontMetrics()

brush() 定義用填充模式繪製幾何形狀時候的畫刷,主要是畫刷的顏色和模式

pen() 定義花框圖的時候線條的樣條和顏色

backgroundMode() 定義是否存在 background(), 分爲, Qt::OpaqueMode 和  Qt::TransparentMode 兩個

background() 只有當 backgroundMode() 是 Qt::OpaqueMode, pen() 是 一個 一個stripple (各種虛線。。。。), 這個描述的是背景像素的顏色值。

brushOrigin() 畫刷原點,正常情況下,畫刷原點就是widget背景的原點

viewport, window() 和 worldTransform(), 一起構成painter的座標系。

hasClipping() 告訴 painter 是否執行裁剪操作,裁剪的區域是 clipRegion()。

layoutDirection() , 表明的是在繪製文字項的時候,文字的排版方向

worldMatrixEnabled() 告訴繪製流程是否開啓 world 變換

viewTransformEnabled() 告訴繪製流程是否開啓 view 變換

上面的設置項,很多在繪製的 device 上也會由相應的設置,比如 QWdiget::font()。接口QPainter::begin() 或者是QPainter的構造函數,會從當前的device上拷貝那些屬性。

對於QPainter來說,內部有一個狀態堆棧,任何時候都可以通過調用 save() 和 restore() 對QPainter的內部狀態執行 進棧保存和壓棧還原的操作。

QPainter 提供了大部分基本二維幾何元的繪製命令,如: drawPoint(), drawPoints(), drawLine(), drawRect(), drawRoundedRect(), drawEllipse(), drawArc(), drawPie(), drawChord(), drawPolyline(), drawPolygon(), drawConvexPolygon() and drawCubicBezier().

其中有兩個遍歷的函數 drawRects() and drawLines(), ,會根據當前設置的brush和pen繪製給定的QRects數組或者QLines數組。

QPainter 還兩個了兩個函數 fillRect 用來填充一個 QRect, 以及 eraseRect 用來擦除一個矩形。

上面的繪製命令提供一個整數參數版本,也提供一個浮點參數版本。

如果你需要繪製一個複雜的幾何形狀,而且需要反覆的繪製,建議你使用QPainterPath 和 drawPth()。

QPainter 還提供了 fillPath 來填充 QPainterPath 組成的形狀,還有strokePath()來繪製給定 path的邊緣(勾邊)。

QT也提供了一些列繪圖命令來繪製 pixmaps 和 images,  它們是: drawPixmap(), drawImage() and drawTiledPixmap(). 其中 drawPixmap(),和 drawImage() 產生的效果是一樣的,只是 drawPixmap在 屏幕上繪製比較快,而drawImage 在 QPrinter 和其他設備上繪製會比較快

文字繪製用接口 drawText(), 繪製文字需要提供座標,還有boundingRect()

QPainter還有一個接口,用來在 painter device 上繪製一個 QPicture, 接口爲 drawPicture(),  這個接口繪製的時候不會使用當前device的狀態設置,因爲QPicture有它自己的設置。

座標變換

默認情況下,QPainter 使用的是 當前 device 的座標系(以像素爲單位),但是QPainter 對於座標系變換提供了很好的支持,主要有如下的一些座標系變換:

Image(134)

旋轉, 縮放, 平移, shearing, 用 scale() 來縮放作響, rotate() 用來對座標系進行順時針旋轉, translate() 對座標系執行平移操作。也可以通過函數 shear() 對座標系執行扭曲。類似對矩陣執行雅克比切變,讓 座標系的 x 和 y 不再是正交的向量。這裏提到的變換都是作用在 worldTransform()的矩陣上。 還有一個矩陣 deviceTransform 用來把邏輯座標變換到設備的座標。

當用QPainter執行繪製的時候,我們指定的頂點座標都是邏輯座標,這個邏輯座標最終會被轉換成設備的物理座標,從邏輯座標到物理座標的轉換,是通過矩陣 combinedTransform()執行的,這個矩陣,結合了 viewport() , window(), 和 worldTransform()。 其中 viewport()代表的是物理座標系中的任意的一個矩形區域,而window()是以邏輯座標的形式描述viewport()指定的同一個矩形。其中worldTransform() 就等於變換矩陣。

裁剪

QPainter可以把繪製進行裁剪指定,裁剪區域可以是 rectange, region 或者 vector path, 當前裁剪區域可以通過clipRegion() 和 clipPath 訪問。不同的裁剪區域在不同的paintEngine()上會獲得不一樣的性能,在QPainter 執行裁剪後,  painte device 還回執行一次裁剪。

組合模式

QPainter 提供了一個枚舉 CompositionMode 類型,用來配置QPainter繪製命令的融合模式。

兩個用得最多的是 Source 和 SourceOver,  Source 模式用來繪製那些不透明的對象,在這個模式下,source中的每個像素會代替destination中的相應像素。在SourceOver模式,主要用來繪製透明對象,在這個模式下,source中的像素不會直接替代destination中的像素,source中的像素會覆蓋在destination上(沒明白QT assist 中的這個是說什麼,但猜測應該是進行 alpha 混合,至於 alpha 的混合是 SRCAPHA 還是 INV_SRCALPHA就不得而知了,需要實驗一下)。

性能

QPainter 是一個豐富的繪製框架,爲開發者提供了大量的圖像繪製命令,比如 漸變(gradients),融合模式(composition modes), 矢量圖像(vector graphics)。而且上述的繪製在大部分軟硬件平臺都是支持的,爲了讓大部分的軟硬件環境都能運行QT,這種跨平臺支持,自然讓我們犧牲了一些性能,可以想象到如果要讓QPainter中的每一個單獨繪製命令都去完整的設置一次 composition modes, brushes, clipping, transformation 等等,這種渲染狀態的海量設置幾乎是不可能完成的任務。作爲一個折中的版本,我們選擇了QPainter API中的一個子集和一些後端的關鍵技術作爲突破點,來保證這部分繪製命令的性能達到我們可以接受程度。

爲了實現上述性能目標,我們聚焦的繪製後端技術有如下一些(我們盡力保證他們的性能):

[QT assist 中隊這一段的描述,E文相對較繞,先貼下原文,可以對照:QPainter is a rich framework that allows developers to do a great variety of graphical operations, such as gradients, composition modes and vector graphics. And QPainter can do this across a variety of different hardware and software stacks. Naturally the underlying combination of hardware and software has some implications for performance, and ensuring that every single operation is fast in combination with all the various combinations of composition modes, brushes, clipping, transformation, etc, is close to an impossible task because of the number of permutations. As a compromise we have selected a subset of the QPainter API and backends, where performance is guaranteed to be as good as we can sensibly get it for the given combination of hardware and software.]

QT 主要實現的後端繪製技術:

  • Raster(光柵化) - 這個後端技術,用純軟件的方法實現渲染,並且他總是會渲染到一個QImage。爲了優化性能,這裏的渲染只使用下面的格式:,其他的任何告訴包括,光柵化的性能都很差。這個渲染引擎也是Windows和QWS上默認的渲染引擎。這個渲染引擎作爲默認的圖像系統可以運行在任何操作系統和軟硬件平臺上,在命令行中通過 -graphicssystem raster就可以指定用這個渲染引擎啓動QT。
  • OpenGL 2.0(ES) 這個是一個主要的硬件加速的圖像後端。這個可以運行在桌面機上,以及所有支持OpenGL 2.0 或者 OpenGL/ES 2.0的設備上。這也就意味着絕大部分圖形芯片都是支持的。這個引擎通過命令行 -graphicssystem -opengl 啓動 QPainter 在繪製 QGLWidget 的時候 使用 這個圖形引擎。
  • OpenVG - 這個後端技術,主要是實現 Khronos 標準的 2D 圖形和 矢量圖形。 這個使得QT在支持 OpenVG的硬件設備上也是支持的,通過命令行 -graphicsssytem openvg開啓。

性能得到保證的主要繪製相關操作包括:

  • 簡單的變換,正常的平移和縮放,或者進行 0, 90, 180, 270,這種角度的旋轉操作
  • drawPixmap() 結合簡單的變換,繪製不透明的對象(這個時候CompositionMode 不要設置成QPainter::SmoothPixmapTransform, 這個時候不支持)
  • 矩形純色填充,或者兩個顏色的漸變填充,或者還加上一些簡單的變換都是ok的。
  • 模式設置成 QPainter::CompositionMode_Source and QPainter::CompositionMode_SourceOver, 性能是最好的。
  • 用純色或者兩個顏色的漸變填充 圓角矩形 也是 ok的。
  • 用 qDrawBorderPixmap 進行 3*3的 pixmaps 修補 也是 ok的。


繪製引擎


Qt - painter

QT 的 繪製系統, 封裝得比較嚴實,這裏針對GraphicsView - Scene - Item 系統做一個介紹。

QT 的 Paint System 主要是基於 QPainter, QPainterDevice 和 QPaintEngine 三個類。

1.QPainter       

  用於完成繪製操作。

2.QPaintDevice  

  可以看成是一個2維的畫板,包含一些畫板的基本信息。直譯的話就是繪圖設備。

3.QPaintEngine

  提供了接口,QPainter 使用這些接口往不同類型的 device 上繪製。QPaintEngine不直接提供給開發人員使用。打個比方,如果你想使用windows自身的繪製設備繪製UI,那麼Qt就選擇默認地匹配windows的QPaintEngine進行界面的繪製;如果你想用OpenGL渲染界面,則需要使用OpenGL相關的QPaintEngine。Qt自帶的QGLWidget可使用OpenGL進行渲染,其內部便使用了QGLPaintEngine。

4.QD3DPaintEngine

  如果我希望能用Windows下的DirectX9 圖形API渲染Qt界面的話,我需要創建D3D相關的QPaintEngine。具體實現可以參照QGLWidget。Qt因爲跨平臺選擇了支持OpenGL,對D3D就沒提供內部支持了。

在各個平臺上,繪製和渲染,通常有三個途徑可走,

第一個,用操作系統提供的 api,  操作系統本身通常不會提供三維渲染,只會在基礎顯示設備的驅動之上封裝繪製操作, 

第二個,用設備驅動上的渲染庫, DirectX, OpenGL,  OpeXL

第三個,直接顯示設備的操作硬件驅動,比如 皮克斯公司的 renderware ,這個相當於自己做了圖形庫

從前面知道原生的QT,是一個純軟件實現的光柵化渲染引擎,所以也就沒有D3D和OpenGL一說了。不過QT又實現了一套QGLWidget,用OpenGL2.0進行渲染。

繪製流程


深入QT的 Painter 可以看到 ,QT的繪製引擎 種類很多,總共有11類繪製引擎。引擎QT界面庫是一個相對底層的庫。

我們先看下windows下的這個繪製流程

繪製命令從 Windows 的窗口消息 ,WM_PAINT 和 WM_ERASEBKGND 開始

Image(135)

Image(136)

然後這個會由一個繪製事件發送給 QCoreApplication , 然後又轉到了 GraphicsView 的 viewportEvent, 這個中間的轉換需要關注一下,是通過filter 轉到View的。

Image(137)

這個直接的傳遞是通過把 相應的 View 當做一個 事件過濾器安裝到  Application 上

接着由下面的接口處理繪製事件

Image(138)

我們知道  View 只是一個窗口,View本身沒有內容,有內容的是 Scene , 一個Scene 根據層級樹的方式掛載這 QGraphicsItem,所以繪製事件就會

傳遞給 QGraphicsScene ,用來繪製當前 Scene 中的所有 Item。

Image(139)

我們知道 scene 中的 item 是一個具備父子關係的樹,所以繪製 scene的時候,我們只需要找到 所有子樹的根節點,也就是QT中的toplevelItem。

然後在每個樹根上執行遞歸繪製。

Image(140)

這個過程是一個遞歸的過程,  來看下 在一個遞歸循環裏面的繪製函數

Image(141)

這個函數 執行一個完整的 繪製流程:

  開始 繪製 item 的那些位於 item 之後的 子 item 

Image(142)

   然後 繪製 item 自己

Image(143)

   最後繪製那些 位於 item 之前的 子item

Image(144)

   這樣一個完整的item繪製流程就完成了。

接下來我們深入 item 本身內容的繪製中,  也就是 Image(145)

這個是一個虛接口函數,由QGraphicsItem的 各個子類實現,這裏的繪製是一個高層繪製,也就說在使用 QPainter提供的基本繪製命令上組合圖形,

基本繪製命令,有Rect, Pixmap, Point,  Lines 等

對於這些組合理論不深入了, 這裏接着深入  QPainter 本身。

QPainter 是一個 繪製接口的 Wrapper, 具體的工作都交給 相應的 QPainterEngine

而QPainterEngine 在整個繼承層次上,進行分工,分爲 PathEngine, RasterEngine,  分別處理線條的繪製,以及 pixelData 的繪製。

前面提到QT支持跨平臺,並且支持GL渲染,但這一部分是一個相對獨立的系統,和QT的原生系統不一樣。

QT原生系統是一個自己做的軟件渲染庫,基本上事情都在cpu上執行計算,而且整個渲染系統架構在一個自己寫的渲染管線上,

QPainter提供的繪製命令只會把所有數據收集整理,然後用自己寫的渲染管線執行,裁剪,光柵操作,最後把整理好的渲染位圖,填充到一個光柵化的緩衝區,

注意這個緩衝區是內存,不是顯存。所以QT是一個軟件加速的渲染引擎。至於後面提到的D3D擴展那一套,就當別論了。

下面深入QPainter的 繪製領命, 繪製框圖,繪製位圖,繪製字體,三個方面。然後再深入介紹一下,QT怎麼把它自己寫的渲染管線的結果,也就最終渲染buffer,提交給設備。

這其中涉及一個重要的渲染類: QRasterBuffer,這個是QT繪製命令執行的目的地,

而下面的這個結構體是QT用來操作 QRasterBuffer中數據的輔助結構體

Image(146)

這裏包括了各種位圖操作。

前面我們知道了QT的渲染是軟件光柵化,把所有內容都會繪入一個QImage,但它和windows是怎麼交互的呢,怎麼把QImage的內容顯示在窗口上呢,看下下面的代碼段:

Image(147)

這個位於:bool QETWidget::translatePaintEvent(const MSG &msg)

也就是

Image(148)

中的相應窗口的 WINDOWS 的PAINT消息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章