当前位置: 首页 > news >正文

【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 - “画刷”或“油漆桶”

当画家画好一个封闭图形的边框后(比如一个方框的内部),这个工具就上场了。它负责填充这个图形内部的区域。

  • 它最简单的形式就是一桶单一颜色的油漆,比如把方框里面涂成纯黄色。

  • 它也可以很高级,比如像一把能做出渐变效果的刷子,让颜色从蓝色平滑地过渡到绿色。

  • 它甚至能像一块印花滚筒,用预设的图案或者一张图片来填充区域,比如填充出木纹或者网格的效果。

简单说:它管的是所有“图形内部”怎么填充才好看。

把它们串起来:一个完整的工作流程

现在,我们把它们放到一起,看看是怎么配合的:

  1. 你先给画家(QPainter)找好一块画布(QPaintDevice),比如一个空白的窗口。

  2. 然后,你递给他一支精心挑选的画笔(QPen),告诉他:“用这支2毫米粗的蓝色虚线笔画轮廓。”

  3. 接着,你再递给他一个画刷(QBrush),告诉他:“用这个能画出红到黄渐变的刷子来填充内部。”

  4. 最后,你给画家下命令:“好了,现在在这个画布上,给我画一个圆角矩形。”

  5. 画家听令,用你给的蓝色虚线笔勾出轮廓,再用渐变刷子把里面填满,一个漂亮的图形就出现在窗口上了。


二. 注意事项

一个关键的注意事项是:与绘制相关的操作通常不建议放在 QWidget 的构造函数中执行。

这是因为在构造阶段,控件尚未完成初始化和显示,此时进行绘制是无效的。

Qt 为此专门提供了一个 paintEvent 事件处理函数,我们应在此函数中执行所有绘制逻辑。

与之对应的是 QPaintEvent 事件,它在以下几种典型情况下会被触发,从而引起重绘:

  1. 控件首次创建完成并显示时
    比如在 QWidget 上绘制内容,必须在控件构造完成并显示之后才会生效。paintEvent 会在首次显示时被调用,确保绘制内容正确呈现。

  2. 控件被遮挡后重新显露时
    如果控件之前被其他窗口或元素遮挡,当遮挡物移开时,系统会触发重绘,以保证之前被遮挡部分的内容能够正确恢复显示。

  3. 窗口从最小化状态还原时
    窗口最小化后再恢复显示,其内容需要重新绘制,此时也会触发 paintEvent

  4. 控件大小发生变化时
    当控件尺寸改变(如用户拖拽调整窗口大小),通常需要重新绘制以适应新的尺寸,此时也会自动调用 paintEvent

  5. 主动在代码中调用 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 &center, int rx, int ry)

函数功能:以给定的中心点为中心,以rx和ry为半径,绘制一个椭圆。

这个函数需要你提供三个信息(参数)来告诉“画家”椭圆应该画在哪里、画成多大:

const QPoint &center

  • 作用:指定椭圆的中心点。
  • 类型: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::SolidLineQt::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) 进行旋转。
  • 最后,在以新原点为中心的坐标系下绘制你的图形。

运行一下

http://www.dtcms.com/a/453093.html

相关文章:

  • Java 集合框架全解析:从数据结构到源码实战
  • 北京商地网站建设公司photoshop设计一个精美的网站主页
  • 【MYSQL】统计用户旅行距离的SQL解决方案:排序规则与稳定性全解析
  • 基于单片机的罐体压力控制器设计与实现
  • C# datagridview读取XML数据和保存到XML的例子
  • OPENPPP2 静态隧道链路迁移平滑(UDP/IP)
  • 使用Unity引擎开发Rokid主机应用的模型交互操作
  • 数据中台的数据源与数据处理流程
  • Oracle数据库impdp/expdp
  • Java学习之旅第第二季-10:包装类
  • 微信网站与响应式网站有哪些如何建立营销型网站
  • PanguHA,一款Windows双机热备工具
  • PostgreSQL 从入门到精通:Windows 环境下安装与使用指南
  • ChatMemory连续对话保存和持久化
  • 从Excel到AI:机器学习如何重塑数据分析,以及MLquick的破局之道
  • 子网站怎么做做网站的公司那家好。
  • PyTorchTensorFlow
  • 廊坊网站排名优化报价wordpress如何写个插件
  • 什么是DDoS攻击?DDoS攻击介绍
  • 类与对象 --1
  • C++异常处理全面解析:从基础到应用
  • Linux 命令:tree
  • Altium Designer元器件NAME从竖向改为横向
  • 天津网站建设费用佛山企业网站建设策划
  • 吴恩达机器学习课程(PyTorch适配)学习笔记:1.2 优化算法实践
  • 服务端之NestJS接口响应message编写规范详解、写给前后端都舒服的接口、API提示信息标准化
  • 【开题答辩全过程】以 安康毛绒玩具展示及销售平台为例,包含答辩的问题和答案
  • H7-TOOL的I2C控制器主机模式的时钟扩展功能支持
  • Keil 单片机笔记1
  • 一个人做运营网站仿站网站开发