首页 > 其他分享 >Qt 渲染原理

Qt 渲染原理

时间:2023-08-11 14:23:44浏览次数:28  
标签:const Qt 渲染 QStyle 原理 绘制 QPainter option

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

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

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

设置样式

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

./myapplication -style windows

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

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

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

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

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

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

 1  void MyWidget::paintEvent(QPaintEvent * /* event */)
 2  {
 3      QPainter painter(this);
 4  
 5      QStyleOptionFocusRect option;
 6      option.initFrom(this);
 7      option.backgroundColor = palette().color(QPalette::Background);
 8  
 9      style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter, this);
10  }

  QStyle 从 QStyleOption 获取渲染图形元素所需的所有信息。 小部件作为最后一个参数传递,以防样式需要它来执行特殊效果(例如 macOS 上的动画默认按钮),但这不是强制性的。 事实上,通过正确设置 QPainter,您可以使用 QStyle 在任何绘图设备上绘制,而不仅仅是小部件。
  QStyleOption 为可以绘制的各种类型的图形元素提供了各种子类。 例如,PE_FrameFocusRect 需要一个 QStyleOptionFocusRect 参数。
  为了确保绘图操作尽可能快,QStyleOption 及其子类具有公共数据成员。 有关如何使用它的详细信息,请参阅 QStyleOption 类文档。
  为方便起见,Qt 提供了 QStylePainter 类,它结合了 QStyle、QPainter 和 QWidget。 这使得可以写

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

创建自定义样式

  您可以通过创建自定义样式为您的应用程序创建自定义外观。创建自定义样式有两种方法。在静态方法中,您可以选择现有的 QStyle 类,对其进行子类化,然后重新实现虚函数以提供自定义行为,或者从头开始创建整个 QStyle 类。在动态方法中,您可以在运行时修改系统样式的行为。下面介绍静态方法。 QProxyStyle 中描述了动态方法。
  静态方法的第一步是选择 Qt 提供的样式之一,您将从中构建您的自定义样式。您对 QStyle 类的选择将取决于哪种风格最类似于您想要的风格。您可以用作基础的最通用类是 QCommonStyle(不是 QStyle)。这是因为 Qt 要求它的样式是 QCommonStyles。
  根据要更改的基本样式的哪些部分,您必须重新实现用于绘制界面这些部分的函数。为了说明这一点,我们将修改 QWindowsStyle 绘制的旋转框箭头的外观。箭头是由 drawPrimitive() 函数绘制的原始元素,因此我们需要重新实现该函数。我们需要以下类声明:

 1  class CustomStyle : public QProxyStyle
 2  {
 3      Q_OBJECT
 4  
 5  public:
 6      CustomStyle();
 7      ~CustomStyle() {}
 8  
 9      void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
10                         QPainter *painter, const QWidget *widget) const override;
11  };

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

 1  void CustomStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
 2                                  QPainter *painter, const QWidget *widget) const
 3  {
 4      if (element == PE_IndicatorSpinUp || element == PE_IndicatorSpinDown) {
 5          QPolygon points(3);
 6          int x = option->rect.x();
 7          int y = option->rect.y();
 8          int w = option->rect.width() / 2;
 9          int h = option->rect.height() / 2;
10          x += (option->rect.width() - w) / 2;
11          y += (option->rect.height() - h) / 2;
12  
13          if (element == PE_IndicatorSpinUp) {
14              points[0] = QPoint(x, y + h);
15              points[1] = QPoint(x + w, y + h);
16              points[2] = QPoint(x + w / 2, y);
17          } else { // PE_SpinBoxDown
18              points[0] = QPoint(x, y);
19              points[1] = QPoint(x + w, y);
20              points[2] = QPoint(x + w / 2, y + h);
21          }
22  
23          if (option->state & State_Enabled) {
24              painter->setPen(option->palette.mid().color());
25              painter->setBrush(option->palette.buttonText());
26          } else {
27              painter->setPen(option->palette.buttonText().color());
28              painter->setBrush(option->palette.mid());
29          }
30          painter->drawPolygon(points);
31      } else {
32      QProxyStyle::drawPrimitive(element, option, painter, widget);
33      }
34  }

  请注意,我们不使用 widget 参数,只是将它传递给 QWindowStyle::drawPrimitive() 函数。 如前所述,关于要绘制什么以及应该如何绘制的信息由 QStyleOption 对象指定,因此无需询问小部件。
如果您需要使用 widget 参数来获取附加信息,请在使用前小心确保它不为 0 并且类型正确。 例如:

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

  在实现自定义样式时,您不能仅仅因为枚举值称为 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”可能可用。 请注意,键不区分大小写。

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

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

  1. Windows
  2. WindowsXp
  3. WindowsVista
  4. Fusion

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

Demo

 以QComboBox为例:

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

 

一个完整的控件由一种或多种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

 1 #ifndef CUSTOMESTYLE_H
 2 #define CUSTOMESTYLE_H
 3  
 4 #include <QProxyStyle>
 5  
 6 class CustomeStyle : public QProxyStyle
 7 {
 8 public:
 9     virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;
10     virtual void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
11     virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;
12  
13     //  not used in our demo
14     virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &size, const QWidget *widget) const;
15     virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;
16     virtual QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const;
17  
18 private:
19     void drawArrowArea(const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
20  
21 private:
22     static bool m_arrowAreaHovered;
23 };
24  
25 #endif // CUSTOMESTYLE_H
  1 #include "CustomeStyle.h"
  2  
  3 #include <QStyleOptionComboBox>
  4 #include <QPainter>
  5 #include <QLinearGradient>
  6 #include <QRadialGradient>
  7 #include <QDebug>
  8  
  9 bool CustomeStyle::m_arrowAreaHovered = false;
 10  
 11 void CustomeStyle::drawComplexControl(QStyle::ComplexControl control,
 12                                       const QStyleOptionComplex *option,
 13                                       QPainter *painter,
 14                                       const QWidget *widget) const
 15 {
 16     switch (control) {
 17     case CC_ComboBox:
 18         {
 19             drawArrowArea(option, painter, widget);
 20             drawControl(CE_ComboBoxLabel, option, painter, widget);
 21             break;
 22         }
 23     default:
 24         QProxyStyle::drawComplexControl(control, option, painter, widget);
 25     }
 26 }
 27  
 28 void CustomeStyle::drawArrowArea(const QStyleOptionComplex *option,
 29                                  QPainter *painter,
 30                                  const QWidget *widget) const
 31 {
 32     QRect arrowBoxRect = option->rect;
 33     arrowBoxRect.adjust(option->rect.width() * 0.8, 0, 0, 0);
 34  
 35     auto arrowAreaColor = Qt::darkCyan;
 36     m_arrowAreaHovered = arrowBoxRect.contains(widget->mapFromGlobal(QCursor::pos()));
 37     if (option->state & State_MouseOver && m_arrowAreaHovered)
 38         arrowAreaColor = Qt::cyan;
 39     else if (option->state & State_On && m_arrowAreaHovered)
 40         arrowAreaColor = Qt::darkMagenta;
 41  
 42     QRadialGradient gradient(arrowBoxRect.center(),
 43                              arrowBoxRect.width());
 44     gradient.setColorAt(0.0, Qt::gray);
 45     gradient.setColorAt(1.0, arrowAreaColor);
 46     painter->fillRect(arrowBoxRect, QBrush(gradient));
 47  
 48  
 49     auto arrowDownOption = *option;
 50     auto adjustPixel = arrowBoxRect.width() * 0.2;
 51     arrowDownOption.rect = arrowBoxRect.adjusted(adjustPixel,
 52                                                  adjustPixel,
 53                                                  -adjustPixel,
 54                                                  -adjustPixel);
 55     drawPrimitive(PE_IndicatorArrowDown, &arrowDownOption, painter, widget);
 56 }
 57  
 58 void CustomeStyle::drawControl(QStyle::ControlElement element,
 59                                const QStyleOption *option,
 60                                QPainter *painter,
 61                                const QWidget *widget) const
 62 {
 63     switch (element) {
 64     case CE_ComboBoxLabel:
 65         {
 66             auto comboBoxOption = qstyleoption_cast<const QStyleOptionComboBox*>(option);
 67             if (comboBoxOption == nullptr)
 68                 return;
 69  
 70             QColor gradientColors[] = {
 71                 Qt::yellow,
 72                 Qt::green,
 73                 Qt::blue,
 74                 Qt::red
 75             };
 76             QColor penColor = Qt::white;
 77             if (option->state & State_MouseOver && !m_arrowAreaHovered) {
 78                 for (auto& color : gradientColors)
 79                     color.setAlpha(80);
 80                 penColor.setAlpha(80);
 81             } else if (option->state & State_On && !m_arrowAreaHovered) {
 82                 for (auto& color : gradientColors)
 83                     color = color.darker(300);
 84                 penColor = penColor.darker(300);
 85             }
 86  
 87             QRect labelRect = comboBoxOption->rect;
 88             labelRect.adjust(0, 0, -(labelRect.width() * 0.2), 0);
 89  
 90             QLinearGradient linearGradient(labelRect.topLeft(), labelRect.bottomRight());
 91             for (int i = 0; i < 4; ++i) {
 92                 linearGradient.setColorAt(0.25 *i, gradientColors[i]);
 93             }
 94  
 95             painter->fillRect(labelRect, QBrush(linearGradient));
 96  
 97             painter->setPen(QPen(penColor));
 98             painter->drawText(labelRect, comboBoxOption->currentText, QTextOption(Qt::AlignCenter));
 99             break;
100         }
101     default:
102         QProxyStyle::drawControl(element, option, painter, widget);
103     }
104 }
105  
106 void CustomeStyle::drawPrimitive(QStyle::PrimitiveElement element,
107                                  const QStyleOption *option,
108                                  QPainter *painter,
109                                  const QWidget *widget) const
110 {
111     switch (element) {
112     default:
113         QProxyStyle::drawPrimitive(element, option, painter, widget);
114     }
115 }
116  
117 QSize CustomeStyle::sizeFromContents(QStyle::ContentsType type,
118                                      const QStyleOption *option,
119                                      const QSize &size,
120                                      const QWidget *widget) const
121 {
122     switch (type) {
123     default:
124         return QProxyStyle::sizeFromContents(type, option, size, widget);
125     }
126 }
127  
128 QRect CustomeStyle::subControlRect(QStyle::ComplexControl cc,
129                                    const QStyleOptionComplex *opt,
130                                    QStyle::SubControl sc,
131                                    const QWidget *widget) const
132 {
133     switch (cc) {
134     
135     //  sub controls used by ComboBox
136  
137     case SC_ComboBoxFrame:
138         {
139             //  An QEditLine is created according to this return value.
140             //  In this Qt version, `opt` is an entire widget rectangle.
141             QRect labelRect = opt->rect;
142             return labelRect.adjusted(1, 1, -(labelRect.width() * 0.2)-1, -1);
143         }
144  
145     //  this value is not used by QStyle!
146     case SC_ComboBoxListBoxPopup:   return QRect(0, 0, 0, 0);
147     case SC_ComboBoxEditField:      return QRect(0, 0, 0, 0);
148     case SC_ComboBoxArrow:          return QRect(0, 0, 0, 0);
149  
150     default:
151         return QProxyStyle::subControlRect(cc, opt, sc, widget);
152     }
153 }
154  
155 QRect CustomeStyle::subElementRect(QStyle::SubElement element,
156                                    const QStyleOption *option,
157                                    const QWidget *widget) const
158 {
159     return QProxyStyle::subElementRect(element, option, widget);
160 }
 1 #include "mainwindow.h"
 2 #include "ui_mainwindow.h"
 3 #include "CustomeStyle.h"
 4  
 5 #include <QStyleFactory>
 6  
 7 MainWindow::MainWindow(QWidget *parent) :
 8     QMainWindow(parent),
 9     ui(new Ui::MainWindow)
10 {
11     ui->setupUi(this);
12  
13     auto nativeStyles = QStyleFactory::keys();
14     for (auto it = nativeStyles.begin(); it != nativeStyles.end(); ++it) {
15         ui->styleComboBox->addItem(*it);
16     }
17     ui->styleComboBox->addItem("CustomeStyle");
18  
19     connect(ui->enableEditCheckBox, &QCheckBox::clicked,
20             [this](bool checked) {
21                 ui->styleComboBox->setEditable(checked);
22             });
23     connect(ui->applyStyleButton, &QPushButton::released,
24             [this]() {
25                 auto styleType = ui->styleComboBox->currentText();
26                 auto app = static_cast<QApplication*>(QCoreApplication::instance());
27                 if (styleType == "CustomeStyle")
28                     app->setStyle(new CustomeStyle());
29                 else
30                     app->setStyle(QStyleFactory::create(styleType));
31             });
32  
33     ui->styleComboBox->setCurrentIndex(0);
34     ui->applyStyleButton->released();
35 }
36  
37 MainWindow::~MainWindow()
38 {
39     delete ui;
40 }

QStyle优点:

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

QStyle缺点:

  实现涉及Qt GUI控件结构细节,涉及知识面太多太杂。
  只有Qt控件使用了QStyle,系统及第三方实现的控件不保证有效。
  实现起来太复杂,不如重写QWidget的paintEvent配合其他事件来实现灵活。

Qt QWidget绘制引擎

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

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

要把画一条线总共需要几个角色(要把大象装冰箱总共分几步)
第一步,需要一个人(画线的方法)

第二步,需要一个笔。

第三步,需要一张纸。

换成Qt来画线的话那就是

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

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

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

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

  • QPaintEngine
  • QPainter
  • QPaintDevice

Qt QWidget绘制引擎简介
  QPaintEngine,QPainter,QPaintDevice组成了Qt绘制界面的基础。

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

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

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

三个类的官方说明如下

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 实现。

  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 、统一刷新的方式;字体绘制要调用字体图元相关绘制逻辑等等。

 1 QPaintEngine *QImage::paintEngine() const
 2  
 3 {
 4  
 5 if (!d)
 6  
 7 return 0;
 8  
 9 if (!d->paintEngine) {
10  
11 QPaintDevice *paintDevice = const_cast<QImage *>(this);
12  
13 QPaintEngine *paintEngine = 0;
14  
15 QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
16  
17 if (platformIntegration)
18  
19 paintEngine = platformIntegration->createImagePaintEngine(paintDevice);
20  
21 d->paintEngine = paintEngine ? paintEngine : new QRasterPaintEngine(paintDevice);
22  
23 }
24  
25 return d->paintEngine;
26  
27 }

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

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

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

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

Create the platform theme:

路径 :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平台为例

 1     // Create the platform theme:
 2  
 3     // 1) Fetch the platform name from the environment if present.
 4     QStringList themeNames;
 5     if (!platformThemeName.isEmpty())
 6         themeNames.append(platformThemeName);
 7  
 8     // 2) Ask the platform integration for a list of theme names
 9     themeNames += QGuiApplicationPrivate::platform_integration->themeNames();
10     // 3) Look for a theme plugin.
11     for (const QString &themeName : qAsConst(themeNames)) {
12         QGuiApplicationPrivate::platform_theme = QPlatformThemeFactory::create(themeName, platformPluginPath);
13         if (QGuiApplicationPrivate::platform_theme)
14             break;
15     }
16  
17     // 4) If no theme plugin was found ask the platform integration to
18     // create a theme
19     if (!QGuiApplicationPrivate::platform_theme) {
20         for (const QString &themeName : qAsConst(themeNames)) {
21             QGuiApplicationPrivate::platform_theme = QGuiApplicationPrivate::platform_integration->createPlatformTheme(themeName);
22             if (QGuiApplicationPrivate::platform_theme)
23                 break;
24         }
25         // No error message; not having a theme plugin is allowed.
26     }
27  
28     // 5) Fall back on the built-in "null" platform theme.
29     if (!QGuiApplicationPrivate::platform_theme)
30         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();

1 QStringList QWindowsIntegration::themeNames() const
2 {
3     return QStringList(QLatin1String(QWindowsTheme::name));
4 }

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

 windows平台下返回 QWindowsTheme

1 QPlatformTheme *QWindowsIntegration::createPlatformTheme(const QString &name) const
2 {
3     if (name == QLatin1String(QWindowsTheme::name))
4         return new QWindowsTheme;
5     return QPlatformIntegration::createPlatformTheme(name);
6 }

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

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

QPushButton的绘制过程

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

1 /*!\reimp
2 */
3 void QPushButton::paintEvent(QPaintEvent *)
4 {
5     QStylePainter p(this);
6     QStyleOptionButton option;
7     initStyleOption(&option);
8     p.drawControl(QStyle::CE_PushButton, option);
9 }

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

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

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

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

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

标签:const,Qt,渲染,QStyle,原理,绘制,QPainter,option
From: https://www.cnblogs.com/ybqjymy/p/17622853.html

相关文章

  • Qt 一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
    概述Qt提供了多种渲染引擎,包括栅格引擎(Raster)、OpenGL和本地绘图系统。选择哪种引擎主要取决于你的应用程序需求和你的硬件环境。栅格引擎(Raster):这是Qt的软件渲染引擎,它在CPU上执行所有的绘图操作。栅格引擎在所有平台上都提供了一致的视觉效果,但可能会比硬件加速的引......
  • 异步线程变量传递必知必会---InheritableThreadLocal及底层原理分析
    InheritableThreadLocal简介笑傲菌:多线程热知识(一):ThreadLocal简介及底层原理3赞同·0评论文章上一篇文章我们聊到了ThreadLocal的作用机理,但是在文章的末尾,我提到了一个问题,ThreadLocal无法实现异步线程变量的传递。什么意思呢?以下面的代码为例子:@SneakyThrowspublicBo......
  • Android View绘制原理-RenderPipeline
    在上一篇关于帧绘制的原理中,做好了EGLSuface切换,同步好了UI的更新,为需要进行GPU绘制的RenderNode创好了SKSurface,最后通过ANativeWindow为下一帧调用了dequeueBuffer。所有的资源和数据都准备好了,从而可以进行绘制,这个任务将由RenderPipeline来完成。我们先不考虑Fence的逻辑,直接接......
  • Qt 渲染
    1)Qt::AA_UseDesktopOpenGL使用显卡的openGL库,且要求支持openGL2.1及以上的版本。因此很多老旧设备是不满足版本要求的(windows默认的驱动版本只支持openGL1.1)。(依赖硬件,即硬件加速渲染)2)Qt::AA_UseOpenGLES使用Angle库来将DirectX11或者DirectX9的接口转成OpenGLES2.0的API,从......
  • 【Hystrix技术指南】(7)故障切换的运作流程原理分析(含源码)
    推荐超值课程:点击获取背景介绍目前对于一些非核心操作,如增减库存后保存操作日志发送异步消息时(具体业务流程),一旦出现MQ服务异常时,会导致接口响应超时,因此可以考虑对非核心操作引入服务降级、服务隔离。Hystrix说明官方文档Hystrix是Netflix开源的一个容灾框架,解决当外部依......
  • QT网络通信
    QT进行TCP网络通信:1.TCP是C/S模式的需要建立连接的网络通信架构,建立连接需要进行三次握手,断开连接需要四次握手。2.服务器模式:需要先创建监听套接字,通过bind函数进行ip和端口绑定,通过listen函数监听客户端的连接请求,通过accept函数处理客户端的连接请求并返回通信套接字进行通信......
  • §2. 数集 ▪ 确界原理
    §2.数集▪ 确界原理掌握区间和邻域的概念。掌握有界集和无界集的定义,能够证明一个数集是否是有界集(例1)。掌握上(下)确界的定义,能够计算一个给定数集的上(下)确界(例2、例5)。确界原理。重点习题:习题2、4、5、6,习题2、5、6的结论需要背下来。  上确界的另一种定义:S是中的......
  • 计算机组成原理 第一章
    电信号:高低电平0和1高低电平的穿插使用就变成了类似于01001001等等的信息传递在我们存储信息的存储器上我们能看到上面有很多的金属针脚,其实那些针脚就是用来接收高低电平的。提出问题数字、文字、图像如何使用二进制表示?CPU如何对二进制数进行加减乘除?如何存储这些二进制......
  • 《C++ GUI Qt4编程》第2章——创建对话框——深入介绍信号和槽
    槽和普通的C++成员函数几乎是一样的——可以是虚函数;可以被重载;可以是公有的、保护的或者私有的,并且也可以被其他C++成员函数直接调用,它们的参数可以是任意类型。唯一不同的是:槽还可以和信号连接在一起,在这种情况下,每当发射这个信号的时候,就会自动调用这个槽。connect(sender,SI......
  • QT鼠标图标显示与隐藏
    1.如果只希望在某个widget中不显示鼠标指针,那么在该widget的构造函数中调用QWidget::setCursor(QCursor(Qt::BlankCursor))2.如果希望整个应用程序不显示鼠标指针,那么在实例化Application后调用QApplication::setOverrideCursor(Qt::BlankCursor);3.可以在任一控件......