【Qt】绘图
目录
一. 核心类介绍
二. 注意事项
三.绘制各种形状
3.1.绘制线段
3.2.绘制矩形
3.3.绘制椭圆
3.4.绘制文本
三.设置画笔
四.设置画刷
五.绘制图片
嘿,小伙伴,咱们之前学 Qt 不都是在跟各种控件打交道嘛,什么按钮、文本框之类的。说白了,这些玩意儿本质上都是 Qt 用代码“画”出来的。
因为它们太常用了,Qt 就提前帮咱们都画好了,咱们直接拿来用就行,特别省事儿。
但你在实际做项目的时候,肯定会遇到这种情况:哎,Qt 自带的这些控件,好像不够酷啊,或者根本实现不了我想要的那个效果。这时候咋办呢?
别担心,Qt 早就替咱们想好了!它提供了一整套画画的“神笔”——就是绘图 API。用上它,你就能自己动手,丰衣足食,想画啥就画啥,彻底放飞自我,做出各种炫酷的自定义控件和效果。
不过咱也得实话实说,在大部分日常开发里,Qt 现成的控件已经完全够用了。所以你也不用急着把所有绘图 API 都啃下来,等真需要的时候,再来学这部分也完全来得及!
一. 核心类介绍
绘画涉及到几个核心的类
你可以把它们想象成一个画家在画室里工作的完整场景:
1. QPainter - “画家”本人
这家伙就是整个绘图过程的核心执行者,是那个真正动手画画的人。
-
他什么都会画:画直线、画方框、画圆圈、写字、贴图片……十八般武艺样样精通。
-
他干活需要听你指挥,你让他画什么,他就画什么。
-
但是,他不能凭空作画,他必须得有个地方可以画,比如一块画布或者一面墙。而且他需要你给他提供合适的工具,比如一支笔和一桶颜料。
简单说:他就是那个负责“动手画”的人。
2. QPaintDevice - “画板”或“画布”
这就是画家作画的载体,是那个实实在在的、能让他下笔的表面。
-
它定义了“画在哪里”这个问题。
-
它可以是各种各样的东西:比如你电脑上的一个窗口、一张内存里的图片,或者是一块专门用来做3D渲染的特殊区域。
-
无论画家多么厉害,他都得有一块这样的“画板”才能施展才华。没有画板,画家就无用武之地。
简单说:它就是画家作画的“地盘”或“场地”。
3. QPen - “画笔”
这就是画家手里拿的那支笔,它专门负责决定图形轮廓的样子。
-
它决定了线条的颜色(是用黑色墨水还是红色彩笔?)。
-
它决定了线条的粗细(是细细的签字笔还是粗粗的马克笔?)。
-
它还决定了线条的样式(是画一条实线,还是一串虚线或者点线?)。
-
它甚至能决定线条拐角和末尾的形状(是方方正正的,还是圆润的?)。
简单说:它管的是所有“线条和边框”长什么样。
4. QBrush - “画刷”或“油漆桶”
当画家画好一个封闭图形的边框后(比如一个方框的内部),这个工具就上场了。它负责填充这个图形内部的区域。
-
它最简单的形式就是一桶单一颜色的油漆,比如把方框里面涂成纯黄色。
-
它也可以很高级,比如像一把能做出渐变效果的刷子,让颜色从蓝色平滑地过渡到绿色。
-
它甚至能像一块印花滚筒,用预设的图案或者一张图片来填充区域,比如填充出木纹或者网格的效果。
简单说:它管的是所有“图形内部”怎么填充才好看。
把它们串起来:一个完整的工作流程
现在,我们把它们放到一起,看看是怎么配合的:
-
你先给画家(QPainter)找好一块画布(QPaintDevice),比如一个空白的窗口。
-
然后,你递给他一支精心挑选的画笔(QPen),告诉他:“用这支2毫米粗的蓝色虚线笔画轮廓。”
-
接着,你再递给他一个画刷(QBrush),告诉他:“用这个能画出红到黄渐变的刷子来填充内部。”
-
最后,你给画家下命令:“好了,现在在这个画布上,给我画一个圆角矩形。”
-
画家听令,用你给的蓝色虚线笔勾出轮廓,再用渐变刷子把里面填满,一个漂亮的图形就出现在窗口上了。
二. 注意事项
一个关键的注意事项是:与绘制相关的操作通常不建议放在 QWidget
的构造函数中执行。
这是因为在构造阶段,控件尚未完成初始化和显示,此时进行绘制是无效的。
Qt 为此专门提供了一个 paintEvent
事件处理函数,我们应在此函数中执行所有绘制逻辑。
与之对应的是 QPaintEvent
事件,它在以下几种典型情况下会被触发,从而引起重绘:
-
控件首次创建完成并显示时
比如在QWidget
上绘制内容,必须在控件构造完成并显示之后才会生效。paintEvent
会在首次显示时被调用,确保绘制内容正确呈现。 -
控件被遮挡后重新显露时
如果控件之前被其他窗口或元素遮挡,当遮挡物移开时,系统会触发重绘,以保证之前被遮挡部分的内容能够正确恢复显示。 -
窗口从最小化状态还原时
窗口最小化后再恢复显示,其内容需要重新绘制,此时也会触发paintEvent
。 -
控件大小发生变化时
当控件尺寸改变(如用户拖拽调整窗口大小),通常需要重新绘制以适应新的尺寸,此时也会自动调用paintEvent
。 -
主动在代码中调用 repaint() 或 update() 时
我们可以在代码中主动调用repaint()
(立即重绘)或update()
(异步调度重绘)来触发paintEvent
,从而实现手动刷新界面。
因此,将绘制代码统一放置在 paintEvent
中,能够确保界面在各种情况下都能正确、及时地更新显示内容,避免出现内容丢失或显示异常的问题。
两位“重绘助手”:repaint() 和 update()
它们都是 QWidget 的成员函数,作用都是最终触发 paintEvent,但脾气完全不同:
- repaint() - “急性子”
行为:它一被调用,立即、强制地产生一个绘制事件,并且会阻塞当前正在执行的代码,直到 paintEvent 函数执行完毕,画面画好了,它才返回。
优点:响应快,立竿见影。
缺点:因为会阻塞,如果重绘很复杂,可能会让界面卡顿一下。频繁调用它效率很低。
什么时候用:很少用。除非在某些对实时性要求极高、不能有丝毫延迟的场合(比如动画)。
- update() - “聪明人”(最常用!)
行为:它一被调用,并不是立刻去画,而是给 Qt 系统“发个消息”:“喂,我这儿需要重画一下,你方便的时候处理一下。” 然后它就立刻返回了,不会阻塞你的代码。
优点:Qt 会把多个连续的 update() 请求合并成一次 paintEvent 调用,效率非常高,避免了不必要的重复绘制,不会造成界面卡顿。
缺点:有极短的延迟,不是立刻执行。
什么时候用:几乎任何时候你需要主动触发重绘,都应该用它。比如你改变了一个数据(比如进度值),然后调用 update(),请求界面更新来反映这个新数据。
三.绘制各种形状
3.1.绘制线段
这两个函数的功能是完全一样的:在屏幕上画一条直线。它们唯一的区别在于传递参数的方式不同。
函数一:使用 QPoint 对象
void drawLine(const QPoint &p1, const QPoint &p2);
参数解释:
p1:一个 QPoint 对象,表示线条的起点坐标。
p2:一个 QPoint 对象,表示线条的终点坐标。
什么是 QPoint?
QPoint 是 Qt 中的一个类,专门用来表示一个二维平面上的点,它包含两个属性:x 坐标 和 y 坐标。
如何使用这个函数?
你需要先创建两个 QPoint 对象,然后把它们传递给函数。
示例:
// 创建起点坐标 (100, 50)
QPoint startPoint(100, 50);
// 创建终点坐标 (400, 200)
QPoint endPoint(400, 200);// 调用函数,从 startPoint 到 endPoint 画一条线
drawLine(startPoint, endPoint);
函数二:直接使用坐标值
void drawLine(int x1, int y1, int x2, int y2);
参数解释:
x1, y1:两个整数,分别表示起点坐标的 X 轴位置 和 Y 轴位置。
x2, y2:两个整数,分别表示终点坐标的 X 轴位置 和 Y 轴位置。
如何使用这个函数?
你直接传入四个整数,分别代表起点和终点的 X, Y 坐标。
示例:
// 调用函数,从 (100, 50) 到 (400, 200) 画一条线
drawLine(100, 50, 400, 200);
话不多说,我们直接看例子
这段代码 QPainter painter(this);
在栈上创建了一个 QPainter 对象。由于它是局部自动变量,其生命周期随作用域结束而自动释放,因此我们无需手动管理它的内存。
需要特别说明的是,此处的 this
指针并非用于指定父对象,而是用于指定绘制设备——它表明我们将在这个窗口部件(即当前对象)上进行绘制操作。这种初始化方式是 Qt 中常见的绘图设备指定方式,确保了绘图操作能够正确作用于目标设备。
3.2.绘制矩形
void QPainter::drawRect(int x,int y, int width, int height);
- x: 矩形左上角的x坐标(相对于绘图设备的坐标系统)
- y: 矩形左上角的y坐标
- width: 矩形的宽度(以像素为单位,可以是负数,表示向左或向上扩展)
- height: 矩形的高度(同样可以是负数)
3.3.绘制椭圆
void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry)
函数功能:以给定的中心点为中心,以rx和ry为半径,绘制一个椭圆。
这个函数需要你提供三个信息(参数)来告诉“画家”椭圆应该画在哪里、画成多大:
const QPoint ¢er
- 作用:指定椭圆的中心点。
- 类型:QPoint 是一个类,用来表示一个二维平面上的点,它包含 x(横坐标)和 y(纵坐标)信息。
- 解读:你通过这个参数告诉画家:“请把椭圆的中心点放在这个位置。” 例如 QPoint(100, 50) 表示中心点在 x=100, y=50 的位置。
int rx
- 作用:指定椭圆在水平方向(X轴)的半径。
- 解读:这个值决定了椭圆有多“宽”。rx 就是从中心点向左右两边延伸的距离。例如,如果 rx 是 50,那么椭圆在水平方向的总宽度就是 100。
int ry
- 作用:指定椭圆在垂直方向(Y轴)的半径。
- 解读:这个值决定了椭圆有多“高”。ry 就是从中心点向上下两边延伸的距离。例如,如果 ry 是 30,那么椭圆在垂直方向的总高度就是 60。
注意:在Qt的坐标系统中,原点(0,0)位于左上角,x轴向右增长,y轴向下增长。
示例:如果中心点坐标为QPoint(100,100),rx为50,ry为30,那么将绘制一个水平方向从50到150(中心100±50),垂直方向从70到130(中心100±30)的椭圆。
另外,如果rx和ry相等,则绘制的是一个圆。
3.4.绘制文本
QPainter类中不仅提供了绘制图形的功能,还可以使⽤QPainter::drawText()函数来绘制⽂字,也可 以使⽤QPainter::setFont() 设置字体等信息。
void QPainter::drawText(int x, int y, const QString &text)
void QPainter::drawText(const QPointF &position, const QString &text)
-
(x, y)
/position
: 这是文字基线的起点坐标。-
关键理解:什么是基线? 想象一下英语练习本的四线三格。基线就是那第三条主线,大部分字母(如 ‘a’, ‘x’, ‘c’)的底部都坐落在基线上。而像 ‘y’, ‘g’, ‘p’ 这样的字母的尾巴会延伸到基线以下(称为下行部分)。所以,这个坐标点并不是文字矩形的左上角。
-
-
-
text
: 要绘制的字符串。
示例: 如果你在 (10, 20)
处绘制文字 “Hello”,那么 “H” 的左下角大约就在 (10, 20)
这个位置。
三.设置画笔
QPainter 在进⾏绘制时,默认会使⽤系统预定义的画笔。
此外,开发⼈员也可以根据需求⾃定义画笔属性。
在 Qt 中,QPen 类⽤于控制 QPainter 绘制图形、线条和轮廓的⽅式。
通过 QPen,可以灵活设置画笔的线宽、颜⾊、样式以及画刷等属性。
⼀般来说,画笔的颜⾊可以在创建 QPen 对象时通过构造函数直接指定,该颜⾊由 QColor 类进⾏定义。
⽽画笔的宽度可以使⽤ setWidth() ⽅法进⾏调整,该值表⽰绘制线条的像素宽度。
画笔的样式则可以调⽤ setStyle() ⽅法来设置,例如实线、虚线、点线等 Qt::PenStyle 枚举类型所定义的⻛格。
此外,还可以通过 setBrush() ⽅法为画笔设置画刷,从⽽实现更丰富的填充效果,例如渐变或纹理。
常⽤的 QPen 设置⽅法包括:
-
设置画笔颜⾊:
QPen::QPen(const QColor &color)
该构造函数可在初始化时直接传⼊ QColor 对象,以设定画笔颜⾊。 -
设置画笔宽度:
void QPen::setWidth(int width)
此⽅法⽤于指定画笔的线宽,单位为像素。 -
设置画笔样式:
void QPen::setStyle(Qt::PenStyle style)
通过该⽅法可设置线条的绘制样式,如Qt::SolidLine
、Qt::DashLine
等。
通过灵活组合上述属性,可以满⾜不同场景下的绘制需求,实现丰富多样的图形外观。
画笔的⻛格我们可以去官方文档那里寻找一下
话不多说,我们直接看例子
四.设置画刷
在Qt中,画刷是使⽤QBrush类来描述,画刷⼤多⽤于填充。
QBrush定义了QPainter的填充模式, 具有样式、颜⾊、渐变以及纹理等属性。
画刷的格式中定义了填充的样式,使⽤Qt::BrushStyle枚举,默认值是Qt::NoBrush,也就是不进⾏ 任何填充。
可以通过Qt助⼿查找画刷的格式。
设置画刷主要通过void QPen::setBrush(constQBrush&brush)⽅法,其参数为画刷的格式
如下图⽰:
不是很明显啊,但是我们仔细就能看出来啊。
我们换一种填充方式
怎么样?是不是很好看了。
五.绘制图片
Qt 提供了四个常用的图像处理类,分别是 QImage、QPixmap、QBitmap 和 QPicture,它们都属于 Qt 绘图设备的重要组成部分,各自在特定场景中发挥着重要作用:
-
QImage 主要设计用于图像数据的输入输出(I/O)操作。该类针对读写操作进行了深度优化,并支持直接访问和操作像素,非常适合对图像进行处理和转换;
-
QPixmap 专注于在屏幕上高效显示图像。它底层依赖于系统的图形资源,在绘制到屏幕时具有较好的性能表现,因此常用于界面中显示图片;
-
QBitmap 是 QPixmap 的一个子类,专门用于处理颜色深度为 1 的图像,即只支持黑白两色的掩码或图案,常见于定制光标、画笔样式等场景;
-
QPicture 则是一个用于记录和回放 QPainter 绘图指令的类,它不存储像素数据,而是保存绘图命令,便于跨会话重用绘图过程。
在本节中,我们将重点介绍在实际开发中最常用的 QPixmap 类,详细讲解其基本用法和典型应用场景。
QPainter::drawPixmap这个函数有多个重载版本(即参数列表不同),我们讲解最常用的几个。
形式1:指定位置 (x, y)
void QPainter::drawPixmap(int x, int y, const QPixmap &pixmap);
-
x
:图片左上角在画布上的X坐标。 -
y
:图片左上角在画布上的Y坐标。 -
pixmap
:要绘制的图片本身。
我们先创建一个项目,然后我们准备一些图片资源啊
接下来我们创建qrc文件
接下来我们就编写代码
缩放图片
形式2:指定位置和大小(缩放)
void QPainter::drawPixmap(int x, int y, int width, int height, const QPixmap &pixmap);
这个版本比上一个多了两个参数:
-
width
:你希望图片在画布上显示的宽度。 -
height
:你希望图片在画布上显示的高度。
通俗解释:
“画家,请把你手里的 pixmap
这张图片,拉伸或压缩到 width 乘 height
这么大,然后把它的左上角对准画布的 (x, y)
这个点,贴上去。”
重要特性:
如果指定的 width
和 height
与图片原始尺寸不同,图片会被自动缩放以适应你指定的大小。
我们来写一个项目
旋转图片
这里的旋转本质是QPainter对象进行了旋转,绘制出来的内容也就进行了旋转
1. 作用
你可以把 QPainter 想象成一个画家,他面前有一张画布(你的窗口或部件)。画布上有一个看不见的坐标系。
- 默认情况下,这个坐标系的原点(0,0)在画布的左上角,X轴向右,Y轴向下。
- 当你调用 painter.rotate(angle) 后,就相当于画家本人(或者说他手中的画笔坐标系) 绕着原点(0,0)顺时针旋转了 angle 度。
- 旋转之后,画家再执行任何绘图指令,比如 painter.drawRect(0, 0, 100, 50),这个矩形就会出现在旋转后的新坐标系里,从而看起来像是被“转”了一个角度。
核心要点: 它旋转的是整个坐标系,而不是单个图形。一旦旋转,后续的所有绘制都会受影响。
2. 参数
QPainter::rotate 只有一个参数:
angle (类型是 qreal,即 double)
- 含义:旋转的角度。
- 单位:度。
- 方向:顺时针 为正方向。也就是说,传入一个正数,坐标系会顺时针旋转。
我们来修改代码
运行一下
什么都没有!
旋转中心始终是原点 (0, 0)
这是最容易困惑的地方。旋转永远是绕着(0,0)点发生的。如果你的图形不在(0,0)附近,旋转时它就会像行星一样“绕”着原点转一个大圈,而不是在原地旋转。
那我们应该咋办呢?
解决方案: 结合使用 QPainter::translate。
如果你想绕着一个特定点(比如一个矩形的中心)旋转,你需要:
- 先用 translate(dx, dy) 将坐标系原点平移到你想要的旋转中心。
- 然后调用 rotate(angle) 进行旋转。
- 最后,在以新原点为中心的坐标系下绘制你的图形。
运行一下