百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

小谈一下Qt的绘制引擎(qt开发绘图软件)

toyiye 2024-08-21 01:41 21 浏览 0 评论

小谈一下Qt的绘制引擎 - QWidget

#Qt

先谈一个疑问?如何设计一个优秀的绘制引擎。

注意下这里,我说的是绘制引擎,而不是光栅化引擎。

这有本质的区别。光栅化引擎只是绘制渲染引擎的一部分。

绘制引擎是我们开发者用的一些常见的接口。光栅化引擎我认为是绘制引擎一部分的实现,所以这里只讲外层的东西。逃)

个人认为,Qt是把C++ OOP的特性用到滚瓜烂熟的框架-封装,继承,多态。

那么目前Qt6的图形架构是这个样子的。

你会发现,Qt Widget这套,基本上都已经跟qml这套脱离了,所以Qt6最新的RHI架构对3D做了很好的支持。

今天主要讲一下QWidget这套的绘制流程,后面会简单说下优缺点。

废话不多说,先举个栗子吧。

举个

假如要画一条线,需要哪几步

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

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

第二步,需要一个笔。

第三步,需要一张纸。

换成Qt来画线的话那就是

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

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

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

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

  • QPaintEngine
  • QPainter
  • QPaintDevice

Qt的绘制引擎简介

Qt官方简介

QPaintEngineQPainterQPaintDevice组成了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


说下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)
{
//此处精简代码
    xxxxxxxx
    if (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继承自QPaintEngineExQPaintEngineEx继承自QPaintEngine

void QRasterPaintEngine::drawLines(const QLine *lines, int lineCount)
{
#ifdef QT_DEBUG_DRAW
    qDebug() << " - QRasterPaintEngine::drawLines(QLine*)" << lineCount;
#endif
    Q_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

内部枚举使用变量。

扩展用法 - 自定义类型

举个很简单的栗子

比如有一个图片类型,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.cpp

Qt的PDF引擎很好的实现了自己的QPaintEngine,跟QPaintDevice,要不然怎么能够支持导出pdf呢!

个人的一些看法

说句心里话,Qt Widget这套渲染引擎的设计已经是比较过时了。(当然只是相对来说,毕竟Qt诞生的太早了。

比如最诟病的QWidget的渲染机制。我比较诟病的大概有这么几点。

QPaintDevice & QPaintEngine没有分开

由QPaintDevice创建QPaintEngine

在Qt源码里,QPaintEngine的构造是在QPaintDevice创建的。 简单的说,绘制设备必须要跟光栅化引擎必须是同一个类型的后端。

比如OpenGL - QOpenGLWidget

这样就很尴尬,QPaintEngine很难换一个后端渲染引擎,必须得跟着QPaintDevice一起变。 这算是一个痛点吧。

多线程支持

现在QWidget对GPU的支持 & 动画效果并不是太好。

基于上面QWidget的这套设计,实际上,痛点就已经很明显了,即多线程支持。

看下RHI的一些简单的架构设计,对于多线程的支持

这才是现代的绘制引擎的设计,但是目前我也没有好好研究过qml,毕竟没写过。先给自己一个Flag吧,后面准备好好研究下这套引擎。

就大概讲了下这套渲染引擎吧。后面估计会好好研究下这套渲染引擎,逃)

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码