北京做网站好的公司,南充建设企业网站,网站推广步骤有哪些,酒店 wordpressQt 是目前C语言首选的框架库。之所以称为框架库而不单单是GUI库#xff0c;是因为Qt提供了远远超过GUI的功能封装#xff0c;即使不使用GUI的后台服务#xff0c;也可以用Qt大大提高跨平台的能力。
仅就界面来说#xff0c;Qt 保持各个平台绘图等效果的统一#xff0c;并…Qt 是目前C语言首选的框架库。之所以称为框架库而不单单是GUI库是因为Qt提供了远远超过GUI的功能封装即使不使用GUI的后台服务也可以用Qt大大提高跨平台的能力。
仅就界面来说Qt 保持各个平台绘图等效果的统一并不是对windows标准控件或者GTK等控件库的简单封装而是完全从像素级别实现了GUI渲染。这一点和MFC等库有本质的区别。今天就借着Qt 6.6的源码看看widgets是如何绘制一个按钮的。
1. 从windows API找起
由于不想重新编译一个 debug 版本的 Qt 来跟踪其 Callstack我们直接用 QtCreator打开QtBase的CMakeList.txt静态的进行分析。
静态分析代码可以从最顶层开始也可以从最底层。还可以利用 doxygen等工具分析调用关系。我们这次从最底层开始即看看Qt为了绘图到底调用了哪些windows API。
首先搜索GDI的一些典型绘制API比如划线等等但没有找到切入点。直到搜索 BitBlt这个API即贴图终于发现了调用的位置
void QWindowsBackingStore::flush(QWindow *window, const QRegion region,const QPoint offset)
{//省略大量上下文QWindowsWindow *rw QWindowsWindow::windowsWindowOf(window);const bool hasAlpha rw-format().hasAlpha();if (rw) {const HDC dc rw-getDC();if (!dc) {qErrnoWarning(%s: GetDC failed, __FUNCTION__);return;}if (!BitBlt(dc, br.x(), br.y(), br.width(), br.height(),m_image-hdc(), br.x() offset.x(), br.y() offset.y(), SRCCOPY)) {const DWORD lastError GetLastError(); // QTBUG-35926, QTBUG-29716: may fail after lock screen.if (lastError ! ERROR_SUCCESS lastError ! ERROR_INVALID_HANDLE)qErrnoWarning(int(lastError), %s: BitBlt failed, __FUNCTION__);}rw-releaseDC();} 可以看到 QWindowsBackingStore::flush 实际上是把一个 QImage 设备无关位图缓存里的像素粘贴到窗口的设备上下文(DC)上。这说明在整体贴图之前各种绘制已经完成了。那么是谁绘制了 m_Image呢看看 m_image的定义
class QWindowsBackingStore : public QPlatformBackingStore
{Q_DISABLE_COPY_MOVE(QWindowsBackingStore)
public:QImage toImage() const override;
private:QScopedPointerQWindowsNativeImage m_image;
};这是一个windows平台的本地Image使用智能指针管理的对象实例。继续跟踪这个类的定义
class Q_GUI_EXPORT QWindowsNativeImage
{Q_DISABLE_COPY_MOVE(QWindowsNativeImage)
public:QWindowsNativeImage(int width, int height,QImage::Format format);~QWindowsNativeImage();inline int width() const { return m_image.width(); }inline int height() const { return m_image.height(); }QImage image() { return m_image; }const QImage image() const { return m_image; }HDC hdc() const { return m_hdc; }static QImage::Format systemFormat();private:const HDC m_hdc;QImage m_image;HBITMAP m_bitmap 0;HBITMAP m_null_bitmap 0;
};可以看到这个类是对设备无关位图 QImage进行了封装同时封装了windows平台上的设备上下文 const HDC m_hdc。从这一步基本就可以确定我们要跟踪的就是这个QWindowsNativeImage::m_image。
2. 顺藤摸瓜
熟悉Qt类继承关系的话就会知道QImage本身就是一个可被 QPainter 绘图的 QPaintDevice 派生类。这个东西是可以返回绘图设备指针的。
继续查看 QWindowsBackingStore 的方法果然有一个成员函数:
// QPaintDevice *paintDevice() override;QPaintDevice *QWindowsBackingStore::paintDevice()
{Q_ASSERT(!m_image.isNull());return m_image-image();
}以及基类的定义
/*!Returns the paint device for this surface.\warning The device is only valid between calls to beginPaint() andendPaint(). You should not cache the returned value.
*/
QPaintDevice *QBackingStore::paintDevice()
{QPaintDevice *device handle()-paintDevice();if (QHighDpiScaling::isActive() device-devType() QInternal::Image)return d_ptr-highDpiBackingstore.data();return device;
}
那么谁又使用了这个 paintDevice呢直接右键单击 QtCreator里“QPaintDevice *QBackingStore::paintDevice()” 这一行在弹出的菜单里选择“Find reference to Symbol Under Cursor” 会发现在类 QRasterWindow 及其私有数据成员类 QRasterWindowPrivate里有引用。
/*!\class QRasterWindow\inmodule QtGui\since 5.4\brief QRasterWindow is a convenience class for using QPainter on a QWindow.QRasterWindow is a QWindow with a raster-based, non-OpenGL surface. On top ofthe functionality offered by QWindow, QRasterWindow adds a virtualpaintEvent() function and the possibility to open a QPainter on itself. Theunderlying paint engine will be the raster one, meaning that all drawing willhappen on the CPU. For performing accelerated, OpenGL-based drawing, useQOpenGLWindow instead.Internally the class is thin wrapper for QWindow and QBackingStoreand is very similar to the \l{Raster Window Example}{Raster WindowExample} that uses these classes directly.\sa QPaintDeviceWindow::paintEvent(), QPaintDeviceWindow::update()
*/class QRasterWindowPrivate : public QPaintDeviceWindowPrivate
{
public:void beginPaint(const QRegion region) override{Q_Q(QRasterWindow);const QSize size q-size();if (backingstore-size() ! size) {backingstore-resize(size);markWindowAsDirty();}backingstore-beginPaint(region);}void endPaint() override{backingstore-endPaint();}void flush(const QRegion region) override{Q_Q(QRasterWindow);backingstore-flush(region, q);}QScopedPointerQBackingStore backingstore;
};/*!Constructs a new QRasterWindow with \a parent.
*/
QRasterWindow::QRasterWindow(QWindow *parent): QPaintDeviceWindow(*(new QRasterWindowPrivate), parent)
{setSurfaceType(QSurface::RasterSurface);d_func()-backingstore.reset(new QBackingStore(this));
}QRasterWindow::~QRasterWindow()
{Q_D(QRasterWindow);// Delete backingstore while window is still alive, as it// might need to reference the window in the processd-backingstore.reset(nullptr);
}/*!\internal
*/
int QRasterWindow::metric(PaintDeviceMetric metric) const
{Q_D(const QRasterWindow);switch (metric) {case PdmDepth:return d-backingstore-paintDevice()-depth();default:break;}return QPaintDeviceWindow::metric(metric);
}/*!\internal
*/
QPaintDevice *QRasterWindow::redirected(QPoint *) const
{Q_D(const QRasterWindow);return d-backingstore-paintDevice();
}QT_END_NAMESPACE#include moc_qrasterwindow.cpp
这个 QRasterWindow 就是代表了CPU渲染的传统GUI窗口窗体。需要注意在gui模块外部尤其是 widget 模块下是什么类首先调用了上文中的方法如 beginPaint() paintDevice等。
3. 从GUI到Widgets
上文的顺藤摸瓜还是在Qt GUI模块里摸索。当我们全文搜索对 GUI 主要入口点 paintDevice\beginPaint的引用时很快就能注意到QWidgetRepaintManager这个类。
void QWidgetRepaintManager::paintAndFlush()
{
//...store-setStaticContents(staticRegion);store-beginPaint(toClean);wd-drawWidget(store-paintDevice(), toBePainted, offset, flags, nullptr, this);tlw-d_func()-drawWidget(store-paintDevice(), dirtyCopy, QPoint(), flags, nullptr, this);store-endPaint();flush();
}
在上述经过大幅度缩减的代码里我们大致能够看到对一次绘制要经过 beginPaint、drawWidget、endPaint、flush四步骤。
这些步骤在 Qt GUI模块的跟踪里都反复遇到过。其中只有 flush 是发生了 windows Native DC的 bitblt其余都在 Image 内存对象里进行。
调用 paintAndFlush有且仅在QWidgetRepaintManager::sync()中。这个函数的作用就是把改变的窗口图案同步到后台存储中Synchronizes the backing store。
/*!Synchronizes the backing store, i.e. dirty areas are repainted and flushed.
*/
void QWidgetRepaintManager::sync()
{qCInfo(lcWidgetPainting) Syncing dirty widgets;updateRequestSent false;if (qt_widget_private(tlw)-shouldDiscardSyncRequest()) {// If the top-level is minimized, its not visible on the screen so we can delay the// update until its shown again. In order to do that we must keep the dirty states.// These will be cleared when we receive the first expose after showNormal().// However, if the widget is not visible (isVisible() returns false), everything will// be invalidated once the widget is shown again, so clear all dirty states.if (!tlw-isVisible()) {dirty QRegion();for (int i 0; i dirtyWidgets.size(); i)resetWidget(dirtyWidgets.at(i));dirtyWidgets.clear();}return;}if (syncAllowed())paintAndFlush();
}
看起来QWidgetRepaintManager 包揽了所有widgets的重绘工作而不是各个Widget直接绘制到backing store。
那么什么时候调用sync呢 可以很方便的跟踪到 QWidgetPrivate::syncBackingStore
void QWidgetPrivate::syncBackingStore()
{if (shouldPaintOnScreen()) {paintOnScreen(dirty);dirty QRegion();} else if (QWidgetRepaintManager *repaintManager maybeRepaintManager()) {repaintManager-sync();}
}以及 QWidget::event(QEvent *event) bool QWidget::event(QEvent *event)
{case QEvent::UpdateRequest:d-syncBackingStore();break;}总的出发地点就是这个 event具体就是 UpdateRequest的响应了。
这里要额外插一句这个 Q_D(QWidget);其实展开为
QWidgetPrivate * const d d_func();为啥子Qt大多数类都有一个对应的私有封装类呢主要是要提纯接口把不需要用户看到的辅助东西放在 Priavte类里接口只保留公开的属性、方法。这个技术网上有专门介绍。
4. 顺流而上
搞清楚了事件的起点我们再回到第三章的一开始void QWidgetRepaintManager::paintAndFlush()里有一个关键的drawWidget这个方法应该就是执行widget的具体绘制了真正的实现是在 QWidgetPrivate::drawWidget 里。
void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion rgn, const QPoint offset, DrawWidgetFlags flags,QPainter *sharedPainter, QWidgetRepaintManager *repaintManager)
{if (!skipPaintEvent) {//actually send the paint eventsendPaintEvent(toBePainted);}
}这个函数非常长最重要的就是上面这句 sendPaintEvent(toBePainted);
由此各个 Widget 的paintEvent会在事件响应中被调用了。我们随便看看一个按钮的paintEvent
/*!\reimp
*/
void QPushButton::paintEvent(QPaintEvent *)
{QStylePainter p(this);QStyleOptionButton option;initStyleOption(option);p.drawControl(QStyle::CE_PushButton, option);
}
它会具体驱动一个风格对象来绘图比如fusion或者 vista。
有兴趣可以看看
void QWindowsStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p,const QWidget *widget) const里面有非常可怕和冗长的绘制代码。以简单的一个小拐角风格来说 case QTabBar::RoundedNorth: {if (!selected) {y1 2;x1 onlyOne || firstTab ? borderThinkness : 0;x2 - onlyOne || lastTab ? borderThinkness : 0;}p-fillRect(QRect(x1 1, y1 1, (x2 - x1) - 1, (y2 - y1) - 2), tab-palette.window());// Delete borderif (selected) {p-fillRect(QRect(x1,y2-1,x2-x1,1), tab-palette.window());p-fillRect(QRect(x1,y2,x2-x1,1), tab-palette.window());}// Leftif (firstTab || selected || onlyOne || !previousSelected) {p-setPen(light);p-drawLine(x1, y1 2, x1, y2 - ((onlyOne || firstTab) selected leftAligned ? 0 : borderThinkness));p-drawPoint(x1 1, y1 1);}// Top{int beg x1 (previousSelected ? 0 : 2);int end x2 - (nextSelected ? 0 : 2);p-setPen(light);p-drawLine(beg, y1, end, y1);}// Rightif (lastTab || selected || onlyOne || !nextSelected) {p-setPen(shadow);p-drawLine(x2, y1 2, x2, y2 - ((onlyOne || lastTab) selected rightAligned ? 0 : borderThinkness));p-drawPoint(x2 - 1, y1 1);p-setPen(dark);p-drawLine(x2 - 1, y1 2, x2 - 1, y2 - ((onlyOne || lastTab) selected rightAligned ? 0 : borderThinkness));}break; }可见这是在用最底层的点线面函数在绘制界面。
5. 堆栈跟踪验证
尽管靠着阅读代码基本跟踪到了各个关键环节还是想真正验证一下。直接CMake一个带有调试信息的Qt6.6 base花了十几分钟还是很快的。我们从一次重绘事件开始直到真正意义上的按照风格绘制按钮的色彩、文本。
5.1 堆栈跟踪
从事件开始到按钮执行绘图经历的堆栈如下 qwindowsvistastyled.dll!QWindowsVistaStyle::drawControl(QStyle::ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) 行 2544 CQt6Widgetsd.dll!QCommonStyle::drawControl(QStyle::ControlElement element, const QStyleOption * opt, QPainter * p, const QWidget * widget) 行 1322 CQt6Widgetsd.dll!QWindowsStyle::drawControl(QStyle::ControlElement ce, const QStyleOption * opt, QPainter * p, const QWidget * widget) 行 1817 Cqwindowsvistastyled.dll!QWindowsVistaStyle::drawControl(QStyle::ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) 行 3284 CQt6Widgetsd.dll!QStylePainter::drawControl(QStyle::ControlElement ce, const QStyleOption opt) 行 52 CQt6Widgetsd.dll!QPushButton::paintEvent(QPaintEvent * __formal) 行 415 CQt6Widgetsd.dll!QWidget::event(QEvent * event) 行 9150 CQt6Widgetsd.dll!QAbstractButton::event(QEvent * e) 行 932 CQt6Widgetsd.dll!QPushButton::event(QEvent * e) 行 684 CQt6Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e) 行 3290 CQt6Widgetsd.dll!QApplication::notify(QObject * receiver, QEvent * e) 行 3237 CQt6Cored.dll!QCoreApplication::notifyInternal2(QObject * receiver, QEvent * event) 行 1118 CQt6Cored.dll!QCoreApplication::sendSpontaneousEvent(QObject * receiver, QEvent * event) 行 1551 CQt6Widgetsd.dll!QWidgetPrivate::sendPaintEvent(const QRegion toBePainted) 行 5651 CQt6Widgetsd.dll!QWidgetPrivate::drawWidget(QPaintDevice * pdev, const QRegion rgn, const QPoint offset, QFlagsenum QWidgetPrivate::DrawWidgetFlag flags, QPainter * sharedPainter, QWidgetRepaintManager * repaintManager) 行 5602 CQt6Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev, const QListQObject * siblings, int index, const QRegion rgn, const QPoint offset, QFlagsenum QWidgetPrivate::DrawWidgetFlag flags, QPainter * sharedPainter, QWidgetRepaintManager * repaintManager) 行 5779 CQt6Widgetsd.dll!QWidgetPrivate::drawWidget(QPaintDevice * pdev, const QRegion rgn, const QPoint offset, QFlagsenum QWidgetPrivate::DrawWidgetFlag flags, QPainter * sharedPainter, QWidgetRepaintManager * repaintManager) 行 5643 CQt6Widgetsd.dll!QWidgetRepaintManager::paintAndFlush() 行 906 CQt6Widgetsd.dll!QWidgetRepaintManager::sync(QWidget * exposedWidget, const QRegion exposedRegion) 行 630 CQt6Widgetsd.dll!QWidgetPrivate::syncBackingStore(const QRegion region) 行 1775 CQt6Widgetsd.dll!QWidgetWindow::handleExposeEvent(QExposeEvent * event) 行 1023 CQt6Widgetsd.dll!QWidgetWindow::event(QEvent * event) 行 289 C//...省去NNNN多行^^^^^^^^^Qt6Cored.dll!QEventDispatcherWin32::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 540 CQt6Guid.dll!QWindowsGuiEventDispatcher::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 36 CQt6Cored.dll!QEventLoop::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 101 CQt6Cored.dll!QEventLoop::exec(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 182 CQt6Cored.dll!QCoreApplication::exec() 行 1439 CQt6Guid.dll!QGuiApplication::exec() 行 1922 CQt6Widgetsd.dll!QApplication::exec() 行 2570 CtestBtn.exe!main(int argc, char * * argv) 行 9 CtestBtn.exe!qtEntryPoint() 行 50 CtestBtn.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 60 C
在抵达绘图代码 QWindowsVistaStyle::drawControl 后多次绘制各个widget合成的image会最终被flush到一个空白的windows窗体上。 qwindowsd.dll!QWindowsBackingStore::flush(QWindow * window, const QRegion region, const QPoint offset) 行 82 CQt6Guid.dll!QBackingStore::flush(const QRegion region, QWindow * window, const QPoint offset) 行 223 CQt6Widgetsd.dll!QWidgetRepaintManager::flush(QWidget * widget, const QRegion region, QPlatformTextureList * widgetTextures) 行 1101 CQt6Widgetsd.dll!QWidgetRepaintManager::flush() 行 977 CQt6Widgetsd.dll!QWidgetRepaintManager::paintAndFlush() 行 909 CQt6Widgetsd.dll!QWidgetRepaintManager::sync() 行 657 CQt6Widgetsd.dll!QWidgetPrivate::syncBackingStore() 行 1766 CQt6Widgetsd.dll!QWidget::event(QEvent * event) 行 9314 C//...省去NNNN多行Qt6Cored.dll!QEventDispatcherWin32::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 540 CQt6Guid.dll!QWindowsGuiEventDispatcher::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 36 CQt6Cored.dll!QEventLoop::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 101 CQt6Cored.dll!QEventLoop::exec(QFlagsenum QEventLoop::ProcessEventsFlag flags) 行 182 CQt6Cored.dll!QCoreApplication::exec() 行 1439 CQt6Guid.dll!QGuiApplication::exec() 行 1922 CQt6Widgetsd.dll!QApplication::exec() 行 2570 CtestBtn.exe!main(int argc, char * * argv) 行 9 CtestBtn.exe!qtEntryPoint() 行 50 CtestBtn.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) 行 60 C
注意上述两次触发的事件不同事件在Call Stack上还会嵌套多层。实际的 Callstack很长。
5.2 抵达最原始的像素操作
如果继续跟踪可以看到典型的底层计算及图形学椭圆生成算法如何根据画笔对原始的RGB像素进行逐一操作 Qt6Guid.dll!blend_color_argb(int count, const QT_FT_Span_ * spans, void * userData) 行 3858 CQt6Guid.dll!drawEllipsePoints(int x, int y, int length, const QRect rect, const QRect clip, void(*)(int, const QT_FT_Span_ *, void *) pen_func, void(*)(int, const QT_FT_Span_ *, void *) brush_func, QSpanData * pen_data, QSpanData * brush_data) 行 4794 CQt6Guid.dll!drawEllipse_midpoint_i(const QRect rect, const QRect clip, void(*)(int, const QT_FT_Span_ *, void *) pen_func, void(*)(int, const QT_FT_Span_ *, void *) brush_func, QSpanData * pen_data, QSpanData * brush_data) 行 4821 CQt6Guid.dll!QRasterPaintEngine::drawEllipse(const QRectF rect) 行 3301 CQt6Guid.dll!QPaintEngineEx::drawEllipse(const QRect r) 行 830 CQt6Guid.dll!QPainter::drawEllipse(const QRect r) 行 4014 CQt6Guid.dll!QPainter::drawEllipse(int x, int y, int w, int h) 行 586 CtestBtn.exe!testBtn::paintEvent(QPaintEvent * evt) 行 24 CQt6Widgetsd.dll!QWidget::event(QEvent * event) 行 9150 C
可以看到 integer point midpoint algorithm 绘制椭圆
*!\internalDraws an ellipse using the integer point midpoint algorithm.
*/
static void drawEllipse_midpoint_i(const QRect rect, const QRect clip,ProcessSpans pen_func, ProcessSpans brush_func,QSpanData *pen_data, QSpanData *brush_data)
{const qreal a qreal(rect.width()) / 2;const qreal b qreal(rect.height()) / 2;qreal d b*b - (a*a*b) 0.25*a*a;int x 0;int y (rect.height() 1) / 2;int startx x;// region 1while (a*a*(2*y - 1) 2*b*b*(x 1)) {if (d 0) { // select Ed b*b*(2*x 3);x;} else { // select SEd b*b*(2*x 3) a*a*(-2*y 2);drawEllipsePoints(startx, y, x - startx 1, rect, clip,pen_func, brush_func, pen_data, brush_data);startx x;--y;}}drawEllipsePoints(startx, y, x - startx 1, rect, clip,pen_func, brush_func, pen_data, brush_data);// region 2d b*b*(x 0.5)*(x 0.5) a*a*((y - 1)*(y - 1) - b*b);const int miny rect.height() 0x1;while (y miny) {if (d 0) { // select SEd b*b*(2*x 2) a*a*(-2*y 3);x;} else { // select Sd a*a*(-2*y 3);}--y;drawEllipsePoints(x, y, 1, rect, clip,pen_func, brush_func, pen_data, brush_data);}
}
在需要绘制的各个像素位置需要调用画笔和画刷的像素画逻辑
/*!\internal\a x and \a y is relative to the midpoint of \a rect.
*/
static inline void drawEllipsePoints(int x, int y, int length,const QRect rect,const QRect clip,ProcessSpans pen_func, ProcessSpans brush_func,QSpanData *pen_data, QSpanData *brush_data)
{if (length 0)return;QT_FT_Span _outline[4];QT_FT_Span *outline _outline;const int midx rect.x() (rect.width() 1) / 2;const int midy rect.y() (rect.height() 1) / 2;x x midx;y midy - y;// topleftoutline[0].x midx (midx - x) - (length - 1) - (rect.width() 0x1);outline[0].len qMin(length, x - outline[0].x);outline[0].y y;outline[0].coverage 255;// toprightoutline[1].x x;outline[1].len length;outline[1].y y;outline[1].coverage 255;// bottomleftoutline[2].x outline[0].x;outline[2].len outline[0].len;outline[2].y midy (midy - y) - (rect.height() 0x1);outline[2].coverage 255;// bottomrightoutline[3].x x;outline[3].len length;outline[3].y outline[2].y;outline[3].coverage 255;if (brush_func outline[0].x outline[0].len outline[1].x) {QT_FT_Span _fill[2];QT_FT_Span *fill _fill;// top fillfill[0].x outline[0].x outline[0].len - 1;fill[0].len qMax(0, outline[1].x - fill[0].x);fill[0].y outline[1].y;fill[0].coverage 255;// bottom fillfill[1].x outline[2].x outline[2].len - 1;fill[1].len qMax(0, outline[3].x - fill[1].x);fill[1].y outline[3].y;fill[1].coverage 255;int n (fill[0].y fill[1].y ? 1 : 2);n qt_intersect_spans(fill, n, clip);if (n 0)brush_func(n, fill, brush_data);}if (pen_func) {int n (outline[1].y outline[2].y ? 2 : 4);n qt_intersect_spans(outline, n, clip);if (n 0)pen_func(n, outline, pen_data);}
}上文的pen_func(n, outline, pen_data); 是一个functional对象调用 blend_color_argb 方法对每个像素进行内存级别的比特操作
static void blend_color_argb(int count, const QT_FT_Span *spans, void *userData)
{QSpanData *data reinterpret_castQSpanData *(userData);const Operator op getOperator(data, nullptr, 0);const uint color data-solidColor.rgba();if (op.mode QPainter::CompositionMode_Source) {// inline for performancewhile (count--) {uint *target ((uint *)data-rasterBuffer-scanLine(spans-y)) spans-x;if (spans-coverage 255) {qt_memfill(target, color, spans-len);
#ifdef __SSE2__} else if (spans-len 16) {op.funcSolid(target, spans-len, color, spans-coverage);
#endif} else {uint c BYTE_MUL(color, spans-coverage);int ialpha 255 - spans-coverage;for (int i 0; i spans-len; i)target[i] c BYTE_MUL(target[i], ialpha);}spans;}return;}const auto funcSolid op.funcSolid;auto function [] (int cStart, int cEnd) {for (int c cStart; c cEnd; c) {uint *target ((uint *)data-rasterBuffer-scanLine(spans[c].y)) spans[c].x;funcSolid(target, spans[c].len, color, spans[c].coverage);}};QT_THREAD_PARALLEL_FILLS(function);
}6 总结
我们可以看到Qt是遵循这样的逻辑来进行绘图的
控件的绘制逻辑是在“Style” 系列风格类里具体实现。不同的style显然绘制按钮的样子也不同。绘制的操作类是 QPainter提供各种 draw的具体实现。绘制的目的设备是 QPainterDevice大部分情况下是一个临时 QImage对象并最终合并到 QWindowsBackingStore::m_image里。绘制行为的触发点是Event比如鼠标或者别的窗口划过了按钮最终绘制生效被显示的位置就是 QWindowsBackingStore::flush。
Qt没有使用windows自带的控件库而是靠 QWindowsVistaStyle 或者其他的风格类来硬画图一个点一条线地画出来。同时因为上层的绘制全部是在设备无关位图 QImage里绘制的所以无论是显示、打印或者 render 到一个文件里上层的代码都是不需要改动的。
这也是为什么Qt可以在嵌入式系统不完全的绘图支持下比如仅有一个FrameBuf依旧可以绘制出复杂图形的原因。Qt完全可作为OS一部分而存在Linux KDE桌面就是基于Qt的实现。同时由于 backing store的可替换性借助openGL等加速的绘制系统也很方便的集成进来了。
当然上述跟踪只是Desktop Widgets的一个很小的一撇。Qt在融合各类显示策略上的规划非常宏大。 能够直接看到计算机图形学中点线面的生成算法是Qt作为GUI工具链具备OS特性的一大特征。这不是对各种轮子的反复封装而是硬核的底层实现