天天看点

Qt渲染原理

作者:QT教程

QStyle

QStyle决定了各种控件在不同OS平台(win10,apple,vista,xp)等基本的样式;它的继承类实现了相应的接口使得在不同操作平台上观感,细节大不相同;也就是说,QStyle的派生类能够管理到控件的整个绘制过程

Qt 包含一组 QStyle 子类,它们模拟 Qt 支持的不同平台的样式(QWindowsStyle、QMacStyle 等)。 默认情况下,这些样式内置在 Qt GUI 模块中。 样式也可以作为插件使用。

Qt 的内置小部件使用 QStyle 来执行几乎所有的绘图,确保它们看起来与等效的原生小部件完全一样。 下图显示了九种不同样式的 QComboBox。

Qt渲染原理

设置样式

可以使用 QApplication::setStyle() 函数设置整个应用程序的样式。 它也可以由应用程序的用户使用 -style 命令行选项指定:

./myapplication -style windows           

如果没有指定样式,Qt 会为用户的平台或桌面环境选择最合适的样式。

也可以使用 QWidget::setStyle() 函数在单个小部件上设置样式。

开发风格感知的自定义小部件

如果您正在开发自定义小部件并希望它们在所有平台上都看起来不错,您可以使用 QStyle 函数来执行小部件绘制的部分内容,例如 drawItemText()、drawItemPixmap()、drawPrimitive()、drawControl() 和 drawComplexControl( )。

大多数 QStyle 绘图函数采用四个参数:

例如,如果你想在你的小部件上绘制一个焦点矩形,你可以这样写:

void MyWidget::paintEvent(QPaintEvent * /* event */)
 {
     QPainter painter(this);
 
     QStyleOptionFocusRect option;
     option.initFrom(this);
     option.backgroundColor = palette().color(QPalette::Background);
 
     style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter, this);
 }           

QStyle 从 QStyleOption 获取渲染图形元素所需的所有信息。 小部件作为最后一个参数传递,以防样式需要它来执行特殊效果(例如 macOS 上的动画默认按钮),但这不是强制性的。 事实上,通过正确设置 QPainter,您可以使用 QStyle 在任何绘图设备上绘制,而不仅仅是小部件。

QStyleOption 为可以绘制的各种类型的图形元素提供了各种子类。 例如,PE_FrameFocusRect 需要一个 QStyleOptionFocusRect 参数。

为了确保绘图操作尽可能快,QStyleOption 及其子类具有公共数据成员。 有关如何使用它的详细信息,请参阅 QStyleOption 类文档。

为方便起见,Qt 提供了 QStylePainter 类,它结合了 QStyle、QPainter 和 QWidget。 这使得可以写

QStylePainter painter(this);
     ...
     painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);           

创建自定义样式

您可以通过创建自定义样式为您的应用程序创建自定义外观。创建自定义样式有两种方法。在静态方法中,您可以选择现有的 QStyle 类,对其进行子类化,然后重新实现虚函数以提供自定义行为,或者从头开始创建整个 QStyle 类。在动态方法中,您可以在运行时修改系统样式的行为。下面介绍静态方法。 QProxyStyle 中描述了动态方法。

静态方法的第一步是选择 Qt 提供的样式之一,您将从中构建您的自定义样式。您对 QStyle 类的选择将取决于哪种风格最类似于您想要的风格。您可以用作基础的最通用类是 QCommonStyle(不是 QStyle)。这是因为 Qt 要求它的样式是 QCommonStyles。

根据要更改的基本样式的哪些部分,您必须重新实现用于绘制界面这些部分的函数。为了说明这一点,我们将修改 QWindowsStyle 绘制的旋转框箭头的外观。箭头是由 drawPrimitive() 函数绘制的原始元素,因此我们需要重新实现该函数。我们需要以下类声明:

class CustomStyle : public QProxyStyle
 {
     Q_OBJECT
 
 public:
     CustomStyle();
     ~CustomStyle() {}
 
     void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                        QPainter *painter, const QWidget *widget) const override;
 };           

为了绘制向上和向下箭头,QSpinBox 使用 PE_IndicatorSpinUp 和 PE_IndicatorSpinDown 原语元素。 以下是如何重新实现 drawPrimitive() 函数以不同方式绘制它们:

void CustomStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
                                 QPainter *painter, const QWidget *widget) const
 {
     if (element == PE_IndicatorSpinUp || element == PE_IndicatorSpinDown) {
         QPolygon points(3);
         int x = option->rect.x();
         int y = option->rect.y();
         int w = option->rect.width() / 2;
         int h = option->rect.height() / 2;
         x += (option->rect.width() - w) / 2;
         y += (option->rect.height() - h) / 2;
 
         if (element == PE_IndicatorSpinUp) {
             points[0] = QPoint(x, y + h);
             points[1] = QPoint(x + w, y + h);
             points[2] = QPoint(x + w / 2, y);
         } else { // PE_SpinBoxDown
             points[0] = QPoint(x, y);
             points[1] = QPoint(x + w, y);
             points[2] = QPoint(x + w / 2, y + h);
         }
 
         if (option->state & State_Enabled) {
             painter->setPen(option->palette.mid().color());
             painter->setBrush(option->palette.buttonText());
         } else {
             painter->setPen(option->palette.buttonText().color());
             painter->setBrush(option->palette.mid());
         }
         painter->drawPolygon(points);
     } else {
     QProxyStyle::drawPrimitive(element, option, painter, widget);
     }
 }           

请注意,我们不使用 widget 参数,只是将它传递给 QWindowStyle::drawPrimitive() 函数。 如前所述,关于要绘制什么以及应该如何绘制的信息由 QStyleOption 对象指定,因此无需询问小部件。

如果您需要使用 widget 参数来获取附加信息,请在使用前小心确保它不为 0 并且类型正确。 例如:

const QSpinBox *spinBox = qobject_cast<const QSpinBox *>(widget);
     if (spinBox) {
     ...
     }           

在实现自定义样式时,您不能仅仅因为枚举值称为 PE_IndicatorSpinUp 或 PE_IndicatorSpinDown 就假定小部件是 QSpinBox。

Styles 示例的文档更详细地介绍了这个主题。

警告:自定义 QStyle 子类目前不支持 Qt 样式表。 我们计划在未来的某个版本中解决这个问题。

QCommonStyle

QCommonStyle 类封装了 GUI 的通用外观。

这个抽象类实现了作为 Qt 的一部分提供和交付的所有 GUI 样式所共有的一些小部件的外观和感觉。

由于 QCommonStyle 继承了 QStyle,它的所有功能都完整地记录在 QStyle 文档中。

qt实现了跨平台,在不同的平台下,控件的样式绘制成该平台的风格。在qt的内部,如windows平台,QWindowsStyle(路径:src\widgets\styles\qwindowsstyle_p.h) 继承 QCommonStyle,完成windows风格的绘制,同理mac,linux

QStyleFactory

QStyle 类是一个抽象基类,它封装了 GUI 的外观。 QStyleFactory 使用 create() 函数和一个标识样式的键创建一个 QStyle 对象。 样式要么是内置的,要么是从样式插件动态加载的(参见 QStylePlugin)。

可以使用 keys() 函数检索有效密钥。 通常它们包括“windows”和“fusion”。 根据平台,“windowsvista”和“macintosh”可能可用。 请注意,键不区分大小写。

QStringList key_list =  QStyleFactory::keys();
    qDebug() << key_list;//"Windows", "WindowsXP", "WindowsVista", "Fusion"           

QStyleFactory类提供了当前可应用的所有QStyle风格实现,在Windows系统上我获得如下几种风格:

Windows

WindowsXp

WindowsVista

Fusion

我们可以通过QStyleFactory::keys()和QStyleFactory::create()来获取这些可用的风格并且设置到需要的QWidget上用以改变GUI风格。

【粉丝福利】Qt开发学习资料包、大厂面试题、技术视频和学习路线图,包括(Qt C++基础,数据库编程,Qt项目实战、Qt框架、QML、Opencv、qt线程等等)有需要的可以进企鹅裙937552610领取哦~

Demo

Qt渲染原理

以QComboBox为例:

首先我们要来讲讲GUI控件结构,这里以QComboBox为例:

Qt渲染原理
Qt渲染原理

一个完整的控件由一种或多种GUI元素构成:

  • Complex Control Element。
  • Primitive Element。
  • Control Element。

Complex Control Element

Complex control elements contain sub controls. Complex controls behave differently depending on where the user handles them with the mouse and which keyboard keys are pressed.

Complex Control Elements(简称CC)包含子控件。根据用户对鼠标和键盘的不同处理,CC控件的表现也不同。上图中的QComboBox仅包含一个CC控件CC_ComboBox,该复杂控件又包含三个子控件(SC,Sub Control)SC_ComboBoxFrame、SC_ComboBoxArrow、SC_ComboBoxEditField。

Control Element

A control element performs an action or displays information to the user.

控件元素与用户交互相关,例如PushButton、CheckBox等等。QComboBox只有一个CE_ComboBoxLabel用以在ComboBox左侧展示当前选中或者正在编辑的文字。

Primitive Element

Primitive elements are GUI elements that are common and often used by several widgets.

主元素代表那些公共的GUI元素,主要用于GUI控件复用。例如PE_FrameFocusRect这个主元素就进场被多种控件用来绘制输入焦点。QComboBox包含两个主元素PE_IndicatorArrowDown、PE_FrameFocusRect。

QStyle是一套抽象接口,它定义了实现界面控件外观的一系列api并且不能用来被实例化:

virtual void drawComplexControl(...) 绘制复杂元素。

virtual void drawControl(...) 绘制控件元素。

virtual void drawPrimitive(...) 绘制主元素。

...

virtual QSize sizeFromContent(...) 获取控件大小。

virtual QRect subControlRect(...) 获取子控件位置及大小。

virtual QRect subElementRect(...) 获取子元素位置及大小。

这个自定义的QComboBox样式分为两部分,箭头区域和非箭头区域。非箭头区域包含CE_ComboBoxLabel和SC_CombBoxListBoxPopup。由于QStyle不负责绘制下拉框(由delegate绘制),我们只能更改下拉框的位置和大小(这里我们不做改变)。 箭头区域包含背景区和PE_IndicatorArrowDown。

箭头区域我们用一个辐射渐变来绘制背景,并且在鼠标Hover或者按下的时候更改渐变的颜色来重绘,中间的下拉箭头我们复用QProxyStyle的实现来完成。

CustomeStyle.h

#ifndef CUSTOMESTYLE_H
#define CUSTOMESTYLE_H
 
#include <QProxyStyle>
 
class CustomeStyle : public QProxyStyle
{
public:
    virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;
    virtual void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
    virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;
 
    //  not used in our demo
    virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &size, const QWidget *widget) const;
    virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;
    virtual QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const;
 
private:
    void drawArrowArea(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
 
private:
    static bool m_arrowAreaHovered;
};
 
#endif // CUSTOMESTYLE_H           
#include "CustomeStyle.h"
 
#include <QStyleOptionComboBox>
#include <QPainter>
#include <QLinearGradient>
#include <QRadialGradient>
#include <QDebug>
 
bool CustomeStyle::m_arrowAreaHovered = false;
 
void CustomeStyle::drawComplexControl(QStyle::ComplexControl control,
                                      const QStyleOptionComplex *option,
                                      QPainter *painter,
                                      const QWidget *widget) const
{
    switch (control) {
    case CC_ComboBox:
        {
            drawArrowArea(option, painter, widget);
            drawControl(CE_ComboBoxLabel, option, painter, widget);
            break;
        }
    default:
        QProxyStyle::drawComplexControl(control, option, painter, widget);
    }
}
 
void CustomeStyle::drawArrowArea(const QStyleOptionComplex *option,
                                 QPainter *painter,
                                 const QWidget *widget) const
{
    QRect arrowBoxRect = option->rect;
    arrowBoxRect.adjust(option->rect.width() * 0.8, 0, 0, 0);
 
    auto arrowAreaColor = Qt::darkCyan;
    m_arrowAreaHovered = arrowBoxRect.contains(widget->mapFromGlobal(QCursor::pos()));
    if (option->state & State_MouseOver && m_arrowAreaHovered)
        arrowAreaColor = Qt::cyan;
    else if (option->state & State_On && m_arrowAreaHovered)
        arrowAreaColor = Qt::darkMagenta;
 
    QRadialGradient gradient(arrowBoxRect.center(),
                             arrowBoxRect.width());
    gradient.setColorAt(0.0, Qt::gray);
    gradient.setColorAt(1.0, arrowAreaColor);
    painter->fillRect(arrowBoxRect, QBrush(gradient));
 
 
    auto arrowDownOption = *option;
    auto adjustPixel = arrowBoxRect.width() * 0.2;
    arrowDownOption.rect = arrowBoxRect.adjusted(adjustPixel,
                                                 adjustPixel,
                                                 -adjustPixel,
                                                 -adjustPixel);
    drawPrimitive(PE_IndicatorArrowDown, &arrowDownOption, painter, widget);
}
 
void CustomeStyle::drawControl(QStyle::ControlElement element,
                               const QStyleOption *option,
                               QPainter *painter,
                               const QWidget *widget) const
{
    switch (element) {
    case CE_ComboBoxLabel:
        {
            auto comboBoxOption = qstyleoption_cast<const QStyleOptionComboBox*>(option);
            if (comboBoxOption == nullptr)
                return;
 
            QColor gradientColors[] = {
                Qt::yellow,
                Qt::green,
                Qt::blue,
                Qt::red
            };
            QColor penColor = Qt::white;
            if (option->state & State_MouseOver && !m_arrowAreaHovered) {
                for (auto& color : gradientColors)
                    color.setAlpha(80);
                penColor.setAlpha(80);
            } else if (option->state & State_On && !m_arrowAreaHovered) {
                for (auto& color : gradientColors)
                    color = color.darker(300);
                penColor = penColor.darker(300);
            }
 
            QRect labelRect = comboBoxOption->rect;
            labelRect.adjust(0, 0, -(labelRect.width() * 0.2), 0);
 
            QLinearGradient linearGradient(labelRect.topLeft(), labelRect.bottomRight());
            for (int i = 0; i < 4; ++i) {
                linearGradient.setColorAt(0.25 *i, gradientColors[i]);
            }
 
            painter->fillRect(labelRect, QBrush(linearGradient));
 
            painter->setPen(QPen(penColor));
            painter->drawText(labelRect, comboBoxOption->currentText, QTextOption(Qt::AlignCenter));
            break;
        }
    default:
        QProxyStyle::drawControl(element, option, painter, widget);
    }
}
 
void CustomeStyle::drawPrimitive(QStyle::PrimitiveElement element,
                                 const QStyleOption *option,
                                 QPainter *painter,
                                 const QWidget *widget) const
{
    switch (element) {
    default:
        QProxyStyle::drawPrimitive(element, option, painter, widget);
    }
}
 
QSize CustomeStyle::sizeFromContents(QStyle::ContentsType type,
                                     const QStyleOption *option,
                                     const QSize &size,
                                     const QWidget *widget) const
{
    switch (type) {
    default:
        return QProxyStyle::sizeFromContents(type, option, size, widget);
    }
}
 
QRect CustomeStyle::subControlRect(QStyle::ComplexControl cc,
                                   const QStyleOptionComplex *opt,
                                   QStyle::SubControl sc,
                                   const QWidget *widget) const
{
    switch (cc) {
    
    //  sub controls used by ComboBox
 
    case SC_ComboBoxFrame:
        {
            //  An QEditLine is created according to this return value.
            //  In this Qt version, `opt` is an entire widget rectangle.
            QRect labelRect = opt->rect;
            return labelRect.adjusted(1, 1, -(labelRect.width() * 0.2)-1, -1);
        }
 
    //  this value is not used by QStyle!
    case SC_ComboBoxListBoxPopup:   return QRect(0, 0, 0, 0);
    case SC_ComboBoxEditField:      return QRect(0, 0, 0, 0);
    case SC_ComboBoxArrow:          return QRect(0, 0, 0, 0);
 
    default:
        return QProxyStyle::subControlRect(cc, opt, sc, widget);
    }
}
 
QRect CustomeStyle::subElementRect(QStyle::SubElement element,
                                   const QStyleOption *option,
                                   const QWidget *widget) const
{
    return QProxyStyle::subElementRect(element, option, widget);
}           
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "CustomeStyle.h"
 
#include <QStyleFactory>
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 
    auto nativeStyles = QStyleFactory::keys();
    for (auto it = nativeStyles.begin(); it != nativeStyles.end(); ++it) {
        ui->styleComboBox->addItem(*it);
    }
    ui->styleComboBox->addItem("CustomeStyle");
 
    connect(ui->enableEditCheckBox, &QCheckBox::clicked,
            [this](bool checked) {
                ui->styleComboBox->setEditable(checked);
            });
    connect(ui->applyStyleButton, &QPushButton::released,
            [this]() {
                auto styleType = ui->styleComboBox->currentText();
                auto app = static_cast<QApplication*>(QCoreApplication::instance());
                if (styleType == "CustomeStyle")
                    app->setStyle(new CustomeStyle());
                else
                    app->setStyle(QStyleFactory::create(styleType));
            });
 
    ui->styleComboBox->setCurrentIndex(0);
    ui->applyStyleButton->released();
}
 
MainWindow::~MainWindow()
{
    delete ui;
}           

QStyle优点:

统一风格。特定类型的控件效果都统一,如果要多处用到同一种类型的控件,用QStyle会比较方便。

QStyle缺点:

实现涉及Qt GUI控件结构细节,涉及知识面太多太杂。

只有Qt控件使用了QStyle,系统及第三方实现的控件不保证有效。

实现起来太复杂,不如重写QWidget的paintEvent配合其他事件来实现灵活。

Qt QWidget绘制引擎

绘制引擎是我们开发者用的一些常见的接口。光栅化引擎是绘制引擎一部分的实现。

一个小例子:假如要画一条线,需要哪几步

要把画一条线总共需要几个角色(要把大象装冰箱总共分几步)

第一步,需要一个人(画线的方法)

第二步,需要一个笔。

第三步,需要一张纸。

换成Qt来画线的话那就是

第一步,需要一个光栅化引擎(QPaintEngine)

第二步,需要一个笔(QPainter)

第三步,需要一个设备(QPaintDevice)

所以Qt给我们暴露的接口就是这三个

QPaintEngine

QPainter

QPaintDevice

【粉丝福利】Qt开发学习资料包、大厂面试题、技术视频和学习路线图,包括(Qt C++基础,数据库编程,Qt项目实战、Qt框架、QML、Opencv、qt线程等等)有需要的可以进企鹅裙937552610领取哦~

Qt QWidget绘制引擎简介

QPaintEngine,QPainter,QPaintDevice组成了Qt绘制界面的基础。

Qt 的绘画系统可以使用相同的 API 在屏幕和打印设备上绘画,并且主要基于QPainter、QPaintDevice和QPaintEngine类。

QPainter用于执行绘图操作,QPaintDevice是二维空间的抽象,可以使用QPainter进行绘制,QPaintEngine提供了painter用来在不同类型的设备上绘制的接口。QPaintEngine类由QPainter和QPaintDevice内部使用,并且对应用程序程序员隐藏,除非他们创建自己的设备类型。

Qt渲染原理

这种方法的主要好处是所有绘画都遵循相同的绘画管道,从而可以轻松添加对新功能的支持并为不受支持的功能提供默认实现。

三个类的官方说明如下

QPaintEngine

QPaintEngine类为QPainter提供了如何在指定绘图设备上(译者注:一般为QPaintDevice的派生)绘制的一些抽象的方法。

Qt为不同的painter后端提供了一些预设实现的QPaintEngine

译者注:提供一个更加好理解的说法。QPainter的Qt实现一般默认调用的是QPaintEngine的方法。

现在QPaintEngine主要提供的是Qt自带的光栅化引擎(QRasterPaintEngine),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

绘画设备是可以使用 QPainter 绘制的二维空间的抽象。它的默认坐标系的原点位于左上角。 X向右增加,Y向下增加。单位是一个像素。

QPaintDevice类是可绘制对象的基类,即QPainter可以在任何QPaintDevice子类上绘制。QPaintDevice的绘图功能由QWidget、QImage、QPixmap、QPicture、QPrinter和QOpenGLPaintDevice 实现。

Qt渲染原理

QWidget类是Qt Widgets模块中用户界面元素的基类。它从窗口系统接收鼠标、键盘和其他事件,并在屏幕上绘制自己的表示。

QImage类提供了一种独立于硬件的图像表示,它针对 I/O 以及直接像素访问和操作而设计和优化。QImage支持多种图像格式,包括单色、8 位、32 位和 alpha 混合图像。

使用QImage作为绘图设备的一个优点是可以以独立于平台的方式保证任何绘图操作的像素准确性。另一个好处是可以在当前 GUI 线程之外的另一个线程中执行绘制。

QPixmap类是一种离屏图像表示,其设计和优化用于在屏幕上显示图像。与QImage不同,像素图中的像素数据是内部的,由底层窗口系统管理,即只能通过QPainter函数或将QPixmap转换为QImage来访问像素。

为了使用QPixmap优化绘图,Qt 提供了QPixmapCache类,该类可用于存储生成成本高昂的临时像素图,而无需使用超过缓存限制的存储空间。

Qt 还提供QBitmap便利类,继承QPixmap。QBitmap保证单色(1 位深度)像素图,主要用于创建自定义QCursor和QBrush对象,构造QRegion对象。

OpenGL 绘制设备

如前所述,Qt 提供的类使得在 Qt 应用程序中使用 OpenGL 变得容易。例如,QOpenGLPaintDevice启用 OpenGL API 以使用QPainter进行渲染。

QPicture类是记录和重放QPainter命令的绘图设备。图片以独立于平台的格式将画家命令序列化到 IO 设备。QPicture也与分辨率无关,即QPicture可以显示在看起来相同的不同设备(例如 svg、pdf、ps、打印机和屏幕)上。

自定义后端

可以通过从QPaintDevice类派生并重新实现虚拟QPaintDevice::paintEngine () 函数来告诉QPainter应该使用哪个绘图引擎在此特定设备上绘制来实现对新后端的支持。为了真正能够在设备上绘图,此绘制引擎必须是通过派生自QPaintEngine类创建的自定义绘制引擎。

QPainter

QPainter 提供了高度优化的功能来完成大多数绘图 GUI 程序所需的工作。 它可以绘制从简单的线条到复杂的形状(如馅饼和弦)的所有内容。 它还可以绘制对齐的文本和像素图。 通常,它在“自然”坐标系中绘制,但它也可以进行视图和世界变换。 QPainter 可以对任何继承 QPaintDevice 类的对象进行操作。

QPainter 的常见用途是在小部件的绘制事件中:构造和自定义(例如设置钢笔或画笔)画家。 然后画。 记得在绘制后销毁 QPainter 对象。

绘制过程

所有的绘制在中间都必然要经过QPaintEngine。QRasterPaintEngine只不过是它的一个派生,

现在QPaintEngine主要提供的是Qt自带的光栅化引擎(QRasterPaintEngine),Qt在他所有支持的平台上,提供了一个功能完备的光栅化引擎。

在Windows, X11 和 macOS平台上,Qt自带的光栅化引擎都是QWidget这个基础类的默认的绘制方法的提供者,亦或是QImage的绘制方法的提供者。当然有一些特殊的绘制设备的绘制引擎不提供对应的绘制方法,这时候就会调用默认的光栅化引擎。而 QPaintEngine 根据所要绘制的内容,来区分绘制逻辑,比方说涂色采用填充 buffer 、统一刷新的方式;字体绘制要调用字体图元相关绘制逻辑等等。

QPaintEngine *QImage::paintEngine() const
 
{
 
if (!d)
 
return 0;
 
if (!d->paintEngine) {
 
QPaintDevice *paintDevice = const_cast<QImage *>(this);
 
QPaintEngine *paintEngine = 0;
 
QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
 
if (platformIntegration)
 
paintEngine = platformIntegration->createImagePaintEngine(paintDevice);
 
d->paintEngine = paintEngine ? paintEngine : new QRasterPaintEngine(paintDevice);
 
}
 
return d->paintEngine;
 
}           

QRasterPaintEngine只是QPaintEngine的派生类。我也说 Windows 平台下默认的 Qt 绘制是使用指令集的。原因就在于默认条件下,绝大部分的QPaintDevice是选择用QRasterPaintEngine

所有的表层绘制都要经过绘制引擎来向下传递绘制信息。这是 Qt 作为一个高级框架的闪光点,在其他的 Qt 模块也有类似发现,比如控件的绘制上。这样看来 Qt 这个框架能给我们的,除了代码逻辑本身,还有设计。

Qt渲染原理

在 Windows 平台 默认的 Qt QWidget绘制,最终到底层是直接调用指令集指令的而不是 Windows API ,

这也是 Qt 的性能的保障。如果想探究指令集部分的使用,需要到源码目录 qtbase\src\gui\painting:

Qt渲染原理

Create the platform theme:

Qt渲染原理

路径 :D:\Qt\5.9.8\Src\qtbase\src\gui\kernel\qguiapplication.cpp

在函数static void init_platform(const QString &pluginArgument, const QString &platformPluginPath, const QString &platformThemeName, int &argc, char **argv)中确定了平台的主题:调试以windows平台为例

// Create the platform theme:
 
    // 1) Fetch the platform name from the environment if present.
    QStringList themeNames;
    if (!platformThemeName.isEmpty())
        themeNames.append(platformThemeName);
 
    // 2) Ask the platform integration for a list of theme names
    themeNames += QGuiApplicationPrivate::platform_integration->themeNames();
    // 3) Look for a theme plugin.
    for (const QString &themeName : qAsConst(themeNames)) {
        QGuiApplicationPrivate::platform_theme = QPlatformThemeFactory::create(themeName, platformPluginPath);
        if (QGuiApplicationPrivate::platform_theme)
            break;
    }
 
    // 4) If no theme plugin was found ask the platform integration to
    // create a theme
    if (!QGuiApplicationPrivate::platform_theme) {
        for (const QString &themeName : qAsConst(themeNames)) {
            QGuiApplicationPrivate::platform_theme = QGuiApplicationPrivate::platform_integration->createPlatformTheme(themeName);
            if (QGuiApplicationPrivate::platform_theme)
                break;
        }
        // No error message; not having a theme plugin is allowed.
    }
 
    // 5) Fall back on the built-in "null" platform theme.
    if (!QGuiApplicationPrivate::platform_theme)
        QGuiApplicationPrivate::platform_theme = new QPlatformTheme;           

1) Fetch the platform name from the environment if present.

首先读取环境变量,确定主题,跳过

2) Ask the platform integration for a list of theme names

themeNames += QGuiApplicationPrivate::platform_integration->themeNames();

QStringList QWindowsIntegration::themeNames() const
{
    return QStringList(QLatin1String(QWindowsTheme::name));
}           

platform_integration 在windows平台上是QWindowsIntegration,返回的themeNames是QWindowsTheme中的name,该name是一个常量:const char *QWindowsTheme::name = "windows";(路径D:\Qt\5.9.8\Src\qtbase\src\plugins\platforms\windows\qwindowstheme.cpp)

3) Look for a theme plugin

查找主题插件,没有找到

4) If no theme plugin was found ask the platform integration to create a theme

Qt渲染原理

windows平台下返回 QWindowsTheme

QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const
{
    if (name == QLatin1String(QWindowsTheme::name))
        return new QWindowsTheme;
    return QPlatformIntegration::createPlatformTheme(name);
}           

5) Fall back on the built-in "null" platform theme.

兜底策略,如果前几步都没有找到一个主题,则使用默认QPlatformTheme

QPushButton的绘制过程

Qt渲染原理

首先会走到 QPushButton::paintEvent 绘制QPushButton

/*!\reimp
*/
void QPushButton::paintEvent(QPaintEvent *)
{
    QStylePainter p(this);
    QStyleOptionButton option;
    initStyleOption(&option);
    p.drawControl(QStyle::CE_PushButton, option);
}           

QStyleOption是风格的设置类,定义了最基本的绘制控件所需的信息。

绘制不同控件时,控件所使用的设置类继承QStyleOption,且OptionType值不同。

绘制按钮的风格设置类QStyleOptionButton继承QStyleOption时,Type = SO_Button表明是要绘制按钮,且添加了一些按钮才有的属性。QStyleOptionButton 类用于描述绘制按钮的参数。

Qt渲染原理

之后QStylePainter::drawControl 会根据当前的平台所创建的不同的style派生类创建当前平台的按钮风格,例如window下

Qt渲染原理

最后QPainter 调用QRasterPaintEngine(Qt自带的光栅化引擎)将图片渲染出来,其中QRasterPaintEngine的渲染过程中调用了指令集,提高了效率:

Qt渲染原理