Files
scuc-qt-course/tools/wiki_data/bodies/58954548.json
T
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
2026-06-30 14:16:55 +08:00

1 line
15 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"id":"58954548","type":"page","status":"current","title":"10 绘图和绘图设备","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:25.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954548/version/1"},"_expandable":{"content":"/rest/api/content/58954548"}},"body":{"storage":{"value":"<h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"4125e90b-de61-43b8-b5dd-266ac0590908\"><ac:parameter ac:name=\"\">_Toc466556801</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"9ccc451d-e7f5-48cb-b04e-c3126a0b66dc\"><ac:parameter ac:name=\"\">_Toc471842034</ac:parameter></ac:structured-macro>10.1 QPainter</h1><p>Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainterQPainterDevice和QPaintEngine三个类。<br /><strong>QPainter</strong>用来执行绘制的操作;<strong>QPaintDevice</strong>是一个二维空间的抽象,这个二维空间允许QPainter在其上面进行绘制,也就是QPainter工作的空间;<strong>QPaintEngine</strong>提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心QPaintEngine这个类的。我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。<br />下图给出了这三个类之间的层次结构:<br /><ac:image ac:height=\"65\" ac:width=\"498\"><ri:attachment ri:filename=\"worddav2e6e34382213c9ec52cd6ac2f60c719b.png\" /></ac:image><br />上面的示意图告诉我们,<span style=\"color: rgb(255,0,0);\"><strong>Qt 的绘图系统实际上是,使用QPainter在QPainterDevice上进行绘制,它们之间使用QPaintEngine进行通讯(也就是翻译QPainter的指令)。</strong></span><br />下面我们通过一个实例来介绍QPainter的使用:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"338a66ef-283b-408e-a21c-d44b3aab276c\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class PaintedWidget : public QWidget\n{\nQ_OBJECT\npublic:\nPaintedWidget(QWidget *parent = 0);\nprotected:\nvoid paintEvent(QPaintEvent *);\n}]]></ac:plain-text-body></ac:structured-macro><p><br />注意我们重写了QWidget的paintEvent()函数。接下来就是PaintedWidget的源代码:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"cff83f18-2959-4a13-8765-b4e3db2c22ea\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[PaintedWidget::PaintedWidget(QWidget *parent) :\nQWidget(parent)\n{\nresize(800, 600);\nsetWindowTitle(tr(\"Paint Demo\"));\n}\nvoid PaintedWidget::paintEvent(QPaintEvent *)\n{\nQPainter painter(this);\npainter.drawLine(80, 100, 650, 500);\npainter.setPen(Qt::red);\npainter.drawRect(10, 10, 100, 400);\npainter.setPen(QPen(Qt::green, 5));\npainter.setBrush(Qt::blue);\npainter.drawEllipse(50, 150, 400, 200);\n}]]></ac:plain-text-body></ac:structured-macro><p><br />在构造函数中,我们仅仅设置了窗口的大小和标题。而paintEvent()函数则是绘制的代码。首先,我们在栈上创建了一个QPainter对象,也就是说,每次运行paintEvent()函数的时候,都会重建这个QPainter对象。注意,这一点可能会引发某些细节问题:由于我们每次重建QPainter,因此第一次运行时所设置的画笔颜色、状态等,第二次再进入这个函数时就会全部丢失。有时候我们希望保存画笔状态,就必须自己保存数据,否则的话则需要将QPainter作为类的成员变量。 <br class=\"atl-forced-newline\" />QPainter接收一个QPaintDevice指针作为参数。QPaintDevice有很多子类,比如QImage,以及QWidget。注意回忆一下,QPaintDevice可以理解成要在哪里去绘制,而现在我们希望画在这个组件,因此传入的是 this 指针。 <br class=\"atl-forced-newline\" />QPainter有很多以 draw 开头的函数,用于各种图形的绘制,比如这里的drawLine()drawRect()以及drawEllipse()等。当绘制轮廓线时,使用QPainter的pen()属性。比如,我们调用了painter.setPen(Qt::red)将 pen 设置为红色,则下面绘制的矩形具有红色的轮廓线。接下来,我们将 pen 修改为绿色,5 像素宽(painter.setPen(QPen(Qt::green, 5))),又设置了画刷为蓝色。这时候再调用 draw 函数,则是具有绿色 5 像素宽轮廓线、蓝色填充的椭圆。</p><h1><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"8e7e9e40-0a65-40ba-b6ff-047aba255a20\"><ac:parameter ac:name=\"\">_Toc466556802</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"e22df54b-3ae9-40e7-afd9-dd7b4233b33c\"><ac:parameter ac:name=\"\">_Toc471842035</ac:parameter></ac:structured-macro>10.2 绘图设备</h1><p><span style=\"color: rgb(255,0,0);\"><strong>绘图设备是指继承QPainterDevice的子类。</strong></span>Qt提供了很多这样的类,例如QPixmap、QBitmap、QImage和 QPicture。其中,</p><ul><li>QPixmap专门为图像在屏幕上的显示做了优化</li><li>QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。</li><li>QImage专门为图像的像素级访问做了优化。</li><li>QPicture则可以记录和重现QPainter的各条命令。</li></ul><h2><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"3abd59b7-9f72-43cb-a0de-d9d3644b9191\"><ac:parameter ac:name=\"\">_Toc466556803</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"0a01a079-2567-4ddf-abef-f80585a76cb5\"><ac:parameter ac:name=\"\">_Toc471842036</ac:parameter></ac:structured-macro>10.2.1 QPixmap、QBitmap、QImage</h2><p>QPixmap继承了QPaintDevice,因此,你可以使用QPainter直接在上面绘制图形。QPixmap也可以接受一个字符串作为一个文件的路径来显示这个文件,比如你想在程序之中打开png、jpeg之类的文件,就可以使用 QPixmap。使用QPainter的drawPixmap()函数可以把这个文件绘制到一个QLabel、QPushButton或者其他的设备上面。<span style=\"color: rgb(255,0,0);\"><strong>QPixmap是针对屏幕进行特殊优化的,因此,它与实际的底层显示设备息息相关。</strong></span>注意,这里说的显示设备并不是硬件,而是操作系统提供的原生的绘图引擎。所以,在不同的操作系统平台下,QPixmap的显示可能会有所差别。<br /><span style=\"color: rgb(255,0,0);\"><strong>QBitmap继承自QPixmap,因此具有QPixmap的所有特性,提供单色图像</strong></span>。QBitmap的色深始终为1. 色深这个概念来自计算机图形学,是指用于表现颜色的二进制的位数。我们知道,计算机里面的数据都是使用二进制表示的。为了表示一种颜色,我们也会使用二进制。比如我们要表示8种颜色,需要用3个二进制位,这时我们就说色深是3. 因此,所谓色深为1,也就是使用1个二进制位表示颜色。1个位只有两种状态:0和1,因此它所表示的颜色就有两种,黑和白。所以说,<span style=\"color: rgb(255,0,0);\"><strong>QBitmap实际上是只有黑白两色的图像数据。</strong></span><br /><span style=\"color: rgb(255,0,0);\"><strong>由于QBitmap色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷。</strong></span><br />下面我们来看同一个图像文件在QPixmap和QBitmap下的不同表现:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"af13ac6c-2401-4175-ab36-975257ed5dce\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void PaintWidget::paintEvent(QPaintEvent *)\n{\nQPixmap pixmap(\":/Image/butterfly.png\");\nQPixmap pixmap1(\":/Image/butterfly1.png\");\nQBitmap bitmap(\":/Image/butterfly.png\");\nQBitmap bitmap1(\":/Image/butterfly1.png\");\nQPainter painter(this);\npainter.drawPixmap(0, 0, pixmap);\npainter.drawPixmap(200, 0, pixmap1);\npainter.drawPixmap(0, 130, bitmap);\npainter.drawPixmap(200, 130, bitmap1);\n}]]></ac:plain-text-body></ac:structured-macro><p><br /><ac:image ac:height=\"301\" ac:width=\"381\"><ri:attachment ri:filename=\"worddav8e804b3356a3b26dc7b2cb75be6c1faa.png\" /></ac:image><br />这里我们给出了两张png图片。butterfly1.png是没有透明色的纯白背景,而butterfly.png是具有透明色的背景。我们分别使用QPixmap和QBitmap来加载它们。注意看它们的区别:白色的背景在QBitmap中消失了,而透明色在QBitmap中转换成了黑色;其他颜色则是使用点的疏密程度来体现的。 <br class=\"atl-forced-newline\" />QPixmap使用底层平台的绘制系统进行绘制,无法提供像素级别的操作,而<span style=\"color: rgb(255,0,0);\"><strong>QImage则是使用独立于硬件的绘制系统,实际上是自己绘制自己,因此提供了像素级别的操作,并且能够在不同系统之上提供一个一致的显示形式。</strong></span><br />我们声明了一个QImage对象,大小是300 x 300,颜色模式是RGB32,即使用32位数值表示一个颜色的RGB值,也就是说每种颜色使用8位。然后我们对每个像素进行颜色赋值,从而构成了这个图像。我们可以把QImage想象成一个RGB颜色的二维数组,记录了每一像素的颜色。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"ff5fc102-326b-469d-aee8-429c8ceda2a9\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void PaintWidget::paintEvent(QPaintEvent *)\n{\nQPainter painter(this);\nQImage image(300, 300, QImage::Format_RGB32);\nQRgb value;\n//将图片背景填充为白色\nimage.fill(Qt::white);\n//改变指定区域的像素点的值\nfor(int i=50; i<100; ++i)\n{\nfor(int j=50; j<100; ++j)\n{\nvalue = qRgb(255, 0, 0); // 红色\nimage.setPixel(i, j, value);\n}\n}\n//将图片绘制到窗口中\npainter.drawImage(QPoint(0, 0), image);\n}]]></ac:plain-text-body></ac:structured-macro><p><br /><ac:image ac:height=\"308\" ac:width=\"313\"><ri:attachment ri:filename=\"worddav8fd0cc23d4e6270a33aa00a319de0ec8.png\" /></ac:image><br />QImage与QPixmap的区别</p><ul><li><ul><li>QPixmap主要是用于绘图,针对屏幕显示而最佳化设计,QImage主要是为图像I/O、图片访问和像素修改而设计的</li><li>QPixmap依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果,QImage使用Qt自身的绘图引擎,可在不同平台上具有相同的显示效果</li><li>由于QImage是独立于硬件的,也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI线程中处理,使用这一方式可以很大幅度提高UI响应速度。</li><li>QImage可通过setPixpel()和pixel()等方法直接存取指定的像素。</li></ul></li></ul><p>QImage与QPixmap之间的转换:</p><ul><li>QImage转QPixmap</li></ul><p>使用QPixmap的静态成员函数: fromImage()<br />QPixmapfromImage(const QImage &amp; image, <br />Qt::ImageConversionFlags flags = Qt::AutoColor)</p><ul><li>QPixmap转QImage:</li></ul><p>使用QPixmap类的成员函数: toImage()<br />QImage toImage() const</p><h2><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"fd270f80-9a1d-4522-a1ea-df1b1f2f7aea\"><ac:parameter ac:name=\"\">_Toc466556804</ac:parameter></ac:structured-macro><ac:structured-macro ac:name=\"anchor\" ac:schema-version=\"1\" ac:macro-id=\"a7294771-dd0a-483d-9951-52302db0d002\"><ac:parameter ac:name=\"\">_Toc471842037</ac:parameter></ac:structured-macro>10.2.2 QPicture</h2><p>最后一个需要说明的是QPicture。<span style=\"color: rgb(255,0,0);\"><strong>这是一个可以记录和重现QPainter命令的绘图设备。</strong></span> <span style=\"color: rgb(255,0,0);\"><strong>QPicture将QPainter的命令序列化到一个IO设备,保存为一个平台独立的文件格式。</strong></span>这种格式有时候会是&quot;元文件(meta- files)&quot;。Qt的这种格式是二进制的,不同于某些本地的元文件,Qt的pictures文件没有内容上的限制,只要是能够被QPainter绘制的元素,不论是字体还是pixmap,或者是变换,都可以保存进一个picture中。<br /><span style=\"color: rgb(255,0,0);\"><strong>QPicture是平台无关的</strong></span>,因此它可以使用在多种设备之上,比如svg、pdf、ps、打印机或者屏幕。回忆下我们这里所说的QPaintDevice,实际上是说可以有QPainter绘制的对象。QPicture使用系统的分辨率,并且可以调整 QPainter来消除不同设备之间的显示差异。<br />如果我们要记录下QPainter的命令,首先要使用QPainter::begin()函数,将QPicture实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用QPainter::end()命令终止。代码示例如下:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"f37be6c7-90ea-41d3-932c-f63bd1bfb7eb\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void PaintWidget::paintEvent(QPaintEvent *)\n{\nQPicture pic;\nQPainter painter;\n//将图像绘制到QPicture中,并保存到文件\npainter.begin(&pic);\npainter.drawEllipse(20, 20, 100, 50);\npainter.fillRect(20, 100, 100, 100, Qt::red);\npainter.end();\npic.save(\"D:\ndrawing.pic\");\n//将保存的绘图动作重新绘制到设备上\npic.load(\"D:\ndrawing.pic\");\npainter.begin(this);\npainter.drawPicture(200, 200, pic);\npainter.end();\n}]]></ac:plain-text-body></ac:structured-macro>","representation":"storage","_expandable":{"content":"/rest/api/content/58954548"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":62},"_links":{"webui":"/pages/viewpage.action?pageId=58954548","edit":"/pages/resumedraft.action?draftId=58954548","tinyui":"/x/NJODAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954548"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954548/child","restrictions":"/rest/api/content/58954548/restriction/byOperation","history":"/rest/api/content/58954548/history","ancestors":"","descendants":"/rest/api/content/58954548/descendant","space":"/rest/api/space/2CT"}}