小谈一下Qt的绘制引擎(结尾有彩蛋)
公眾號(hào):張小飛那些事兒
小談一下Qt的繪制引擎(結(jié)尾有彩蛋)
序
這一篇算是我給部門(mén)分享的一篇業(yè)務(wù)基礎(chǔ)吧。以及說(shuō)一下自己對(duì)Qt繪制引擎的理解以及及時(shí)的復(fù)盤(pán)。
先談一個(gè)疑問(wèn)?如何設(shè)計(jì)一個(gè)優(yōu)秀的繪制引擎。
注意下這里,我說(shuō)的是繪制引擎,而不是光柵化引擎。這有本質(zhì)的區(qū)別。
繪制引擎是我們開(kāi)發(fā)者用的一些常見(jiàn)的接口。光柵化引擎我認(rèn)為是繪制引擎一部分的實(shí)現(xiàn),所以這里只講外層的東西。逃)
個(gè)人認(rèn)為,Qt是把C++ OOP的特性用到滾瓜爛熟的框架-封裝,繼承,多態(tài)。
廢話不多說(shuō),先舉個(gè)栗子吧。
舉個(gè)🌰
假如要畫(huà)一條線,需要哪幾步
要把畫(huà)一條線總共需要幾個(gè)角色(要把大象裝冰箱總共分幾步)
第一步,需要一個(gè)人(畫(huà)線的方法)。(廢話)
第二步,需要一個(gè)筆。
第三步,需要一張紙。
換成Qt來(lái)畫(huà)線的話那就是
第一步,需要一個(gè)光柵化引擎(QPaintEngine)
第二步,需要一個(gè)筆(QPainter)
第三步,需要一個(gè)設(shè)備(QPaintDevice)
所以Qt給我們暴露的接口就是這三個(gè)
-
QPaintEngine
-
QPainter
-
QPaintDevice
Qt的繪制引擎簡(jiǎn)介
Qt官方簡(jiǎn)介
QPaintEngine,QPainter,QPaintDevice組成了Qt繪制界面的基礎(chǔ)。
直接貼上三個(gè)類(lèi)的官方說(shuō)明介紹
順便打個(gè)廣告,如果有興趣參與Qt文檔的翻譯,歡迎參與項(xiàng)目 QtDocumentCN/QtDocumentCN: Qt中文文檔翻譯 (github.com)
一下說(shuō)明來(lái)自項(xiàng)目QtDocumentCN
QPaintEngine
QPaintEngine類(lèi)為QPainter提供了如何在指定繪圖設(shè)備上(譯者注:一般為QPaintDevice的派生)繪制的一些抽象的方法。
Qt為不同的painter后端提供了一些預(yù)設(shè)實(shí)現(xiàn)的QPaintEngine
譯者注:提供一個(gè)更加好理解的說(shuō)法。QPainter的Qt實(shí)現(xiàn)一般默認(rèn)調(diào)用的是QPaintEngine的方法。
現(xiàn)在QPaintEngine主要提供的是Qt自帶的光柵化引擎(raster engine),Qt在他所有支持的平臺(tái)上,提供了一個(gè)功能完備的光柵化引擎。
在Windows, X11 和 macOS平臺(tái)上,Qt自帶的光柵化引擎都是QWidget這個(gè)基礎(chǔ)類(lèi)的默認(rèn)的繪制方法的提供者,亦或是QImage的繪制方法的提供者。當(dāng)然有一些特殊的繪制設(shè)備的繪制引擎不提供對(duì)應(yīng)的繪制方法,這時(shí)候就會(huì)調(diào)用默認(rèn)的光柵化引擎。
當(dāng)然,我們也為OpenGL(可通過(guò)QOpenGLWidget訪問(wèn))跟打印(允許QPainter在QPrinter對(duì)象上繪制,用于生成pdf之類(lèi)的)也提供了對(duì)應(yīng)的QPaintEngine的實(shí)現(xiàn)。
譯者注: QPainter,QPainterEngine,QPaintDevice三個(gè)是相輔相成的。
- QPainter為開(kāi)發(fā)者提供外部接口方法用于繪制
- QPaintEngine為QPainter提供一些繪制的具體實(shí)現(xiàn)
- QPaintDevice為QPainter提供一個(gè)繪圖設(shè)備,用于顯示亦或儲(chǔ)存。
如果你想使用QPainter繪制自定義的后端(譯者注:這里可以理解為QPaintDevice)。你可以繼承QPaintEngine,并實(shí)現(xiàn)其所有的虛函數(shù)。然后子類(lèi)化QPaintDevice并且實(shí)現(xiàn)它的純虛成員函數(shù)(QPaintDevice::paintEngine())。
由QPaintDevice創(chuàng)建QPaintEngine,并維護(hù)其生命周期。
另請(qǐng)參見(jiàn)QPainter,QPaintDevice::paintEngine()和Paint System
QPaintDevice
翻譯TODO
QPainter
翻譯TODO
舉個(gè)Qt里實(shí)現(xiàn)QPaintEngine相關(guān)的例子
首先在Qt的源碼里打開(kāi)終端執(zhí)行命令
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在各個(gè)不同端的派生,有興趣可以搜下qpaintdevice相關(guān)的,也差不多都是這樣。
比如qpaintengine_raster.cpp 就是Qt自己的光柵化引擎實(shí)現(xiàn),qpaintengine_x11.cpp就是在Linux下默認(rèn)跟x11交互的光柵化實(shí)現(xiàn)。。。
當(dāng)然并不是所有的派生都會(huì)有自己獨(dú)立的cpp文件,或者叫相關(guān)的cpp 。可以對(duì)比Qt的官方API來(lái)對(duì)照下
| 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的引擎 |
說(shuō)下Qt繪制一條線的流程
現(xiàn)在有這樣的代碼,我們來(lái)在Qt中繪制一條線
QLineF line(10.0, 80.0, 90.0, 20.0);QPainter painter(this); painter.drawLine(line);如果我們的Qt把渲染引擎設(shè)置成了raster引擎,那么qpainter的實(shí)現(xiàn)本質(zhì)上是調(diào)用的QPaintEngine的相關(guān)代碼。
void QPainter::drawLines(const QLineF *lines, int lineCount) { //此處精簡(jiǎn)代碼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); //這里調(diào)用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); //這里會(huì)走模擬繪制本質(zhì)上也會(huì)走一個(gè)engine}return;}d->engine->drawLines(lines, lineCount); //或者這里調(diào)用qpaintengine }那么就調(diào)用到了QPaintEngineRaster的相關(guān)實(shí)現(xiàn)。
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在畫(huà)畫(huà)的時(shí)候本質(zhì)上是QPaintEngine提供的方法。
關(guān)于QPaintDevice
由QPaintDevice創(chuàng)建QPaintEngine,并維護(hù)其生命周期。by官方文檔
上面的代碼中,是這樣初始化QPainter的。
我們一般重寫(xiě)一個(gè)QWidget的paintevent的時(shí)候才會(huì)這樣。
//這里的this實(shí)際上就是一個(gè)QWidget,QWidget繼承自QPaintDevice QPainter painter(this);QWidget繼承自QPaintDevice,看下源碼實(shí)現(xiàn)
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f): QObject(dd, nullptr), QPaintDevice()QPaintDevice本質(zhì)上就是一個(gè)繪制設(shè)備,供我們使用,由于QPaintDevice創(chuàng)建QPaintEngine ,所以QPaintDevice跟QPaintEngine一樣,也會(huì)有很多種類(lèi)型的派生。
- QGLFramebufferObject
- QGLPixelBuffer
- QImage,
- QOpenGLPaintDevice,
- QPagedPaintDevice
- QPaintDeviceWindow,
- QPicture
- QPixmap,
- QSvgGenerator
- QWidget
憑記憶說(shuō)一下繪制的一些流程
QPainter在繪制的時(shí)候是有很多講究的,就拿標(biāo)臟來(lái)說(shuō)吧。
假如有這么一段代碼
QPen pen;QPainter painter(this); painter.setPen(pen);這時(shí)候setPen的時(shí)候,QPainter里的QPaintEngine會(huì)直接設(shè)置這個(gè)flag
QPaintEngine::DirtyPen,
然后內(nèi)部再走標(biāo)臟之后需要走的邏輯,QPaintEngine使用函數(shù)QPaintEngine::updateState()來(lái)通知QPainter的延遲刷新。所以基本上,Qt的繪制效率跟效果都是有保證的。
如果你想繼承QPaintEngine來(lái)實(shí)現(xiàn)自己的光柵化引擎的話,不一定是光柵化。比如像Qt那樣支持保存成pdf也可以。
必須更新下面所有的標(biāo)臟狀態(tài)(譯者注:比如你自定義一個(gè)QPaintEngine,就需要處理上面的所有的狀態(tài)的刷新)
比如你setFont,那么狀態(tài)就QPaintEngine::DirtyFont
setBrush,那么狀態(tài)就QPaintEngine::DirtyBrush
| QPaintEngine::DirtyPen | 0x0001 | 畫(huà)筆已經(jīng)標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyBrush | 0x0002 | 畫(huà)刷已經(jīng)標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyBrushOrigin | 0x0004 | 畫(huà)刷原始數(shù)據(jù)已經(jīng)變化,應(yīng)刷新 |
| QPaintEngine::DirtyFont | 0x0008 | 字體發(fā)生變化,應(yīng)刷新 |
| QPaintEngine::DirtyBackground | 0x0010 | 背景標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyBackgroundMode | 0x0020 | 背景狀態(tài)標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyTransform | 0x0040 | 當(dāng)前矩陣標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyClipRegion | 0x0080 | 當(dāng)前裁剪區(qū)域標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyClipPath | 0x0100 | 裁剪路徑標(biāo)臟,應(yīng)刷新 |
| QPaintEngine::DirtyHints | 0x0200 | 當(dāng)前繪制精度標(biāo)志變化,應(yīng)刷新 |
| QPaintEngine::DirtyCompositionMode | 0x0400 | 繪制組合模式變化,應(yīng)刷新 |
| QPaintEngine::DirtyClipEnabled | 0x0800 | 無(wú)論是否當(dāng)前可裁剪,都應(yīng)刷新 |
| QPaintEngine::DirtyOpacity | 0x1000 | 當(dāng)前透明度已經(jīng)更改,應(yīng)當(dāng)使用QPaintEngine::updateState()來(lái)進(jìn)行刷新 |
| QPaintEngine::AllDirty | 0xffff | 內(nèi)部枚舉使用變量。 |
擴(kuò)展用法
WPS針對(duì)Qt的繪制引擎做了很多延伸操作。
舉個(gè)很簡(jiǎn)單的栗子
比如有一個(gè)圖片類(lèi)型,Qt是不支持的,那么我該如何接入。
首先我要繼承QPaintDevice來(lái)叫一個(gè)QXXXXImage。(類(lèi)似QImage那樣,當(dāng)然你也可以直接繪制到QWidget上)
也要繼承一個(gè)QPaintEngine來(lái)叫一個(gè)QXXXXXEngine。(類(lèi)似QPaintEngine::SVG那樣)
QPainter不變。
在繪制圖片的時(shí)候,QPainter會(huì)直接調(diào)用到自己實(shí)現(xiàn)的QXXXXXEngine,這里接到設(shè)備上,來(lái)自己實(shí)現(xiàn)繪制這個(gè)新格式的流程。
這樣,我們就什么都不用變,就可以支持一種新格式了,你QPainter原來(lái)該怎么drawImage,還怎么draw。
Qt源碼里這里當(dāng)然有一個(gè)參考的栗子
可以參考源碼
./5.15.2/Src/qtbase/src/printsupport/kernel/qprintengine_pdf.cppQt的PDF引擎很好的實(shí)現(xiàn)了自己的QPaintEngine,跟QPaintDevice,要不然怎么能夠支持導(dǎo)出pdf呢!
結(jié)尾彩蛋
WPS當(dāng)然遵守Qt的LGPL協(xié)議,使用的Qt版本已經(jīng)在GitHub開(kāi)源了。
開(kāi)源鏈接。 https://github.com/kingsoft-wps/qt5
公眾號(hào):張小飛那些事兒
總結(jié)
以上是生活随笔為你收集整理的小谈一下Qt的绘制引擎(结尾有彩蛋)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 万网绑定二级域名_万网主机绑定二级域名子
- 下一篇: CSI笔记【12】:阵列信号处理及MAT