下面對於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 對於坐標系變換提供了很好的支持,主要有如下的一些坐標系變換:
旋轉, 縮放, 平移, 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 開始
然后這個會由一個繪制事件發送給 QCoreApplication , 然后又轉到了 GraphicsView 的 viewportEvent, 這個中間的轉換需要關注一下,是通過filter 轉到View的。
這個直接的傳遞是通過把 相應的 View 當做一個 事件過濾器安裝到 Application 上
接着由下面的接口處理繪制事件
我們知道 View 只是一個窗口,View本身沒有內容,有內容的是 Scene , 一個Scene 根據層級樹的方式掛載這 QGraphicsItem,所以繪制事件就會
傳遞給 QGraphicsScene ,用來繪制當前 Scene 中的所有 Item。
我們知道 scene 中的 item 是一個具備父子關系的樹,所以繪制 scene的時候,我們只需要找到 所有子樹的根節點,也就是QT中的toplevelItem。
然后在每個樹根上執行遞歸繪制。
這個過程是一個遞歸的過程, 來看下 在一個遞歸循環里面的繪制函數
這個函數 執行一個完整的 繪制流程:
開始 繪制 item 的那些位於 item 之后的 子 item
然后 繪制 item 自己
最后繪制那些 位於 item 之前的 子item
這樣一個完整的item繪制流程就完成了。
這個是一個虛接口函數,由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中數據的輔助結構體
這里包括了各種位圖操作。
前面我們知道了QT的渲染是軟件光柵化,把所有內容都會繪入一個QImage,但它和windows是怎么交互的呢,怎么把QImage的內容顯示在窗口上呢,看下下面的代碼段:
這個位於:bool QETWidget::translatePaintEvent(const MSG &msg)
也就是
中的相應窗口的 WINDOWS 的PAINT消息。
有機會再分析 QT的 光柵渲染引擎。





