小谈一下Qt的绘制引擎(结尾有彩蛋)
公眾號:張小飛那些事兒
小談一下Qt的繪制引擎(結尾有彩蛋)
序
這一篇算是我給部門分享的一篇業務基礎吧。以及說一下自己對Qt繪制引擎的理解以及及時的復盤。
先談一個疑問?如何設計一個優秀的繪制引擎。
注意下這里,我說的是繪制引擎,而不是光柵化引擎。這有本質的區別。
繪制引擎是我們開發者用的一些常見的接口。光柵化引擎我認為是繪制引擎一部分的實現,所以這里只講外層的東西。逃)
個人認為,Qt是把C++ OOP的特性用到滾瓜爛熟的框架-封裝,繼承,多態。
廢話不多說,先舉個栗子吧。
舉個🌰
假如要畫一條線,需要哪幾步
要把畫一條線總共需要幾個角色(要把大象裝冰箱總共分幾步)
第一步,需要一個人(畫線的方法)。(廢話)
第二步,需要一個筆。
第三步,需要一張紙。
換成Qt來畫線的話那就是
第一步,需要一個光柵化引擎(QPaintEngine)
第二步,需要一個筆(QPainter)
第三步,需要一個設備(QPaintDevice)
所以Qt給我們暴露的接口就是這三個
-
QPaintEngine
-
QPainter
-
QPaintDevice
Qt的繪制引擎簡介
Qt官方簡介
QPaintEngine,QPainter,QPaintDevice組成了Qt繪制界面的基礎。
直接貼上三個類的官方說明介紹
順便打個廣告,如果有興趣參與Qt文檔的翻譯,歡迎參與項目 QtDocumentCN/QtDocumentCN: Qt中文文檔翻譯 (github.com)
一下說明來自項目QtDocumentCN
QPaintEngine
QPaintEngine類為QPainter提供了如何在指定繪圖設備上(譯者注:一般為QPaintDevice的派生)繪制的一些抽象的方法。
Qt為不同的painter后端提供了一些預設實現的QPaintEngine
譯者注:提供一個更加好理解的說法。QPainter的Qt實現一般默認調用的是QPaintEngine的方法。
現在QPaintEngine主要提供的是Qt自帶的光柵化引擎(raster engine),Qt在他所有支持的平臺上,提供了一個功能完備的光柵化引擎。
在Windows, X11 和 macOS平臺上,Qt自帶的光柵化引擎都是QWidget這個基礎類的默認的繪制方法的提供者,亦或是QImage的繪制方法的提供者。當然有一些特殊的繪制設備的繪制引擎不提供對應的繪制方法,這時候就會調用默認的光柵化引擎。
當然,我們也為OpenGL(可通過QOpenGLWidget訪問)跟打印(允許QPainter在QPrinter對象上繪制,用于生成pdf之類的)也提供了對應的QPaintEngine的實現。
譯者注: QPainter,QPainterEngine,QPaintDevice三個是相輔相成的。
- QPainter為開發者提供外部接口方法用于繪制
- QPaintEngine為QPainter提供一些繪制的具體實現
- QPaintDevice為QPainter提供一個繪圖設備,用于顯示亦或儲存。
如果你想使用QPainter繪制自定義的后端(譯者注:這里可以理解為QPaintDevice)。你可以繼承QPaintEngine,并實現其所有的虛函數。然后子類化QPaintDevice并且實現它的純虛成員函數(QPaintDevice::paintEngine())。
由QPaintDevice創建QPaintEngine,并維護其生命周期。
另請參見QPainter,QPaintDevice::paintEngine()和Paint System
QPaintDevice
翻譯TODO
QPainter
翻譯TODO
舉個Qt里實現QPaintEngine相關的例子
首先在Qt的源碼里打開終端執行命令
find . -name qpaintengine*.cpp或者你在windows上用everything搜一下
./5.15.2/Src/qtbase/src/gui/image/qpaintengine_pic.cpp ./5.15.2/Src/qtbase/src/gui/painting/qpaintengine.cpp ./5.15.2/Src/qtbase/src/gui/painting/qpaintengineex.cpp ./5.15.2/Src/qtbase/src/gui/painting/qpaintengine_blitter.cpp ./5.15.2/Src/qtbase/src/gui/painting/qpaintengine_raster.cpp ./5.15.2/Src/qtbase/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp ./5.15.2/Src/qtbase/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp ./5.15.2/Src/qtbase/src/printsupport/kernel/qpaintengine_alpha.cpp ./5.15.2/Src/qtbase/src/printsupport/kernel/qpaintengine_preview.cpp這些都是QPaintEngine在各個不同端的派生,有興趣可以搜下qpaintdevice相關的,也差不多都是這樣。
比如qpaintengine_raster.cpp 就是Qt自己的光柵化引擎實現,qpaintengine_x11.cpp就是在Linux下默認跟x11交互的光柵化實現。。。
當然并不是所有的派生都會有自己獨立的cpp文件,或者叫相關的cpp 。可以對比Qt的官方API來對照下
| Constant | Value | Description |
| QPaintEngine::X11 | 0 | |
| QPaintEngine::Windows | 1 | |
| QPaintEngine::MacPrinter | 4 | |
| QPaintEngine::CoreGraphics | 3 | macOS的Quartz2D(CoreGraphics) |
| QPaintEngine::QuickDraw | 2 | macOS的QuickDraw |
| QPaintEngine::QWindowSystem | 5 | 嵌入式Linux的Qt |
| QPaintEngine::PostScript | 6 | (不再支持) |
| QPaintEngine::OpenGL | 7 | |
| QPaintEngine::Picture | 8 | QPicture 格式 |
| QPaintEngine::SVG | 9 | 可伸縮矢量圖形XML格式 |
| QPaintEngine::Raster | 10 | |
| QPaintEngine::Direct3D | 11 | 僅Windows,基于Direct3D的引擎 |
| QPaintEngine::Pdf | 12 | PDF格式 |
| QPaintEngine::OpenVG | 13 | |
| QPaintEngine::User | 50 | 用戶自定義的最小美劇 |
| QPaintEngine::MaxUser | 100 | 用戶自定義的最大美劇 |
| QPaintEngine::OpenGL2 | 14 | |
| QPaintEngine::PaintBuffer | 15 | |
| QPaintEngine::Blitter | 16 | |
| QPaintEngine::Direct2D | 17 | 僅Windows,基于Direct2D的引擎 |
說下Qt繪制一條線的流程
現在有這樣的代碼,我們來在Qt中繪制一條線
QLineF line(10.0, 80.0, 90.0, 20.0);QPainter painter(this); painter.drawLine(line);如果我們的Qt把渲染引擎設置成了raster引擎,那么qpainter的實現本質上是調用的QPaintEngine的相關代碼。
void QPainter::drawLines(const QLineF *lines, int lineCount) { //此處精簡代碼xxxxxxxxif (lineEmulation) {if (lineEmulation == QPaintEngine::PrimitiveTransform&& d->state->matrix.type() == QTransform::TxTranslate) {for (int i = 0; i < lineCount; ++i) {QLineF line = lines[i];line.translate(d->state->matrix.dx(), d->state->matrix.dy());d->engine->drawLines(&line, 1); //這里調用qpaintengine}} else {QPainterPath linePath;for (int i = 0; i < lineCount; ++i) {linePath.moveTo(lines[i].p1());linePath.lineTo(lines[i].p2());}d->draw_helper(linePath, QPainterPrivate::StrokeDraw); //這里會走模擬繪制本質上也會走一個engine}return;}d->engine->drawLines(lines, lineCount); //或者這里調用qpaintengine }那么就調用到了QPaintEngineRaster的相關實現。
QRasterPaintEngine繼承自QPaintEngineEx,QPaintEngineEx繼承自QPaintEngine
void QRasterPaintEngine::drawLines(const QLine *lines, int lineCount) { #ifdef QT_DEBUG_DRAWqDebug() << " - QRasterPaintEngine::drawLines(QLine*)" << lineCount; #endifQ_D(QRasterPaintEngine);QRasterPaintEngineState *s = state();ensurePen();if (!s->penData.blend)return;if (s->flags.fast_pen) {QCosmeticStroker stroker(s, d->deviceRect, d->deviceRectUnclipped);stroker.setLegacyRoundingEnabled(s->flags.legacy_rounding);for (int i=0; i<lineCount; ++i) {const QLine &l = lines[i];stroker.drawLine(l.p1(), l.p2());}} else {QPaintEngineEx::drawLines(lines, lineCount);} }所以QPainter在畫畫的時候本質上是QPaintEngine提供的方法。
關于QPaintDevice
由QPaintDevice創建QPaintEngine,并維護其生命周期。by官方文檔
上面的代碼中,是這樣初始化QPainter的。
我們一般重寫一個QWidget的paintevent的時候才會這樣。
//這里的this實際上就是一個QWidget,QWidget繼承自QPaintDevice QPainter painter(this);QWidget繼承自QPaintDevice,看下源碼實現
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f): QObject(dd, nullptr), QPaintDevice()QPaintDevice本質上就是一個繪制設備,供我們使用,由于QPaintDevice創建QPaintEngine ,所以QPaintDevice跟QPaintEngine一樣,也會有很多種類型的派生。
- QGLFramebufferObject
- QGLPixelBuffer
- QImage,
- QOpenGLPaintDevice,
- QPagedPaintDevice
- QPaintDeviceWindow,
- QPicture
- QPixmap,
- QSvgGenerator
- QWidget
憑記憶說一下繪制的一些流程
QPainter在繪制的時候是有很多講究的,就拿標臟來說吧。
假如有這么一段代碼
QPen pen;QPainter painter(this); painter.setPen(pen);這時候setPen的時候,QPainter里的QPaintEngine會直接設置這個flag
QPaintEngine::DirtyPen,
然后內部再走標臟之后需要走的邏輯,QPaintEngine使用函數QPaintEngine::updateState()來通知QPainter的延遲刷新。所以基本上,Qt的繪制效率跟效果都是有保證的。
如果你想繼承QPaintEngine來實現自己的光柵化引擎的話,不一定是光柵化。比如像Qt那樣支持保存成pdf也可以。
必須更新下面所有的標臟狀態(譯者注:比如你自定義一個QPaintEngine,就需要處理上面的所有的狀態的刷新)
比如你setFont,那么狀態就QPaintEngine::DirtyFont
setBrush,那么狀態就QPaintEngine::DirtyBrush
| QPaintEngine::DirtyPen | 0x0001 | 畫筆已經標臟,應刷新 |
| QPaintEngine::DirtyBrush | 0x0002 | 畫刷已經標臟,應刷新 |
| QPaintEngine::DirtyBrushOrigin | 0x0004 | 畫刷原始數據已經變化,應刷新 |
| QPaintEngine::DirtyFont | 0x0008 | 字體發生變化,應刷新 |
| QPaintEngine::DirtyBackground | 0x0010 | 背景標臟,應刷新 |
| QPaintEngine::DirtyBackgroundMode | 0x0020 | 背景狀態標臟,應刷新 |
| QPaintEngine::DirtyTransform | 0x0040 | 當前矩陣標臟,應刷新 |
| QPaintEngine::DirtyClipRegion | 0x0080 | 當前裁剪區域標臟,應刷新 |
| QPaintEngine::DirtyClipPath | 0x0100 | 裁剪路徑標臟,應刷新 |
| QPaintEngine::DirtyHints | 0x0200 | 當前繪制精度標志變化,應刷新 |
| QPaintEngine::DirtyCompositionMode | 0x0400 | 繪制組合模式變化,應刷新 |
| QPaintEngine::DirtyClipEnabled | 0x0800 | 無論是否當前可裁剪,都應刷新 |
| QPaintEngine::DirtyOpacity | 0x1000 | 當前透明度已經更改,應當使用QPaintEngine::updateState()來進行刷新 |
| QPaintEngine::AllDirty | 0xffff | 內部枚舉使用變量。 |
擴展用法
WPS針對Qt的繪制引擎做了很多延伸操作。
舉個很簡單的栗子
比如有一個圖片類型,Qt是不支持的,那么我該如何接入。
首先我要繼承QPaintDevice來叫一個QXXXXImage。(類似QImage那樣,當然你也可以直接繪制到QWidget上)
也要繼承一個QPaintEngine來叫一個QXXXXXEngine。(類似QPaintEngine::SVG那樣)
QPainter不變。
在繪制圖片的時候,QPainter會直接調用到自己實現的QXXXXXEngine,這里接到設備上,來自己實現繪制這個新格式的流程。
這樣,我們就什么都不用變,就可以支持一種新格式了,你QPainter原來該怎么drawImage,還怎么draw。
Qt源碼里這里當然有一個參考的栗子
可以參考源碼
./5.15.2/Src/qtbase/src/printsupport/kernel/qprintengine_pdf.cppQt的PDF引擎很好的實現了自己的QPaintEngine,跟QPaintDevice,要不然怎么能夠支持導出pdf呢!
結尾彩蛋
WPS當然遵守Qt的LGPL協議,使用的Qt版本已經在GitHub開源了。
開源鏈接。 https://github.com/kingsoft-wps/qt5
公眾號:張小飛那些事兒
總結
以上是生活随笔為你收集整理的小谈一下Qt的绘制引擎(结尾有彩蛋)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万网绑定二级域名_万网主机绑定二级域名子
- 下一篇: CSI笔记【12】:阵列信号处理及MAT