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

QT绘画系统

绘画系统

继承图

QWidget继承自QPaintDevice

Qt的绘图系统(Painting System) 基于QPainter,QPainterDevice和QPaintEngine三个类

  • QPainter用来执行绘制的操作,所以称为绘制器;
  • QPaintDevice是一个二维空间的抽象,这个二维空间允许QPainter在其上面进行绘制,也就是QPainter工作的空间,所以将QPaintDevice称为绘图设备;
  • QPaintEngine提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口,所以称为绘图引擎 。

QPaintEngine类应用于QPainter和QPaintDevice之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心QPaintEngine这个类的。我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。这三者之间的关系如下图:

官网的描述

构造函数

update()

1、执行时机

update函数只是将一个paintEvent事件添加到事件队列中,等待稍后执行,它不会立即执行paintEvent。

repaint函数会立即执行paintEvent,而不会等待事件队列的处理。

2、重绘区域

update函数会合并多个重绘请求,只在最后执行一次paintEvent,可以减少不必要的重绘操作,提高性能。

repaint函数会立即执行paintEvent,不会进行任何合并操作,这意味着每次调用都会触发一次paintEvent。

3、同步性

update是异步的,它只是将重绘事件添加到事件队列,不会立即执行。

repaint是同步的,它会立即执行paintEvent,阻塞直到重绘完成。

QPen

Join Style

Cap Style

PenStyle

SetDashPattern

坐标变换(Coordinate Transformation)

1. 平移(Translate):移动坐标系原点

  • 功能:将当前坐标系的原点(0,0)移动到指定点 (dx, dy),后续绘图的坐标均基于新原点。
  • 函数void QPainter::translate(qreal dx, qreal dy)
  • 示例:先平移再画矩形,等效于直接画 QRect(10+50, 10+50, 50, 50)
    void MyWidget::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setPen(Qt::red);// 1. 平移:将原点移到 (50, 50)painter.translate(50, 50);// 2. 绘制矩形:此时 (10,10) 是平移后的坐标,对应原坐标系 (60,60)painter.drawRect(10, 10, 50, 50); 
    }
    

2. 旋转(Rotate):绕原点旋转坐标系

  • 功能:将当前坐标系绕原点顺时针旋转指定角度(单位:度),注意旋转方向(顺时针为正,逆时针为负)。
  • 函数void QPainter::rotate(qreal angle)
  • 关键注意点:旋转的中心点默认是当前坐标系的原点,若需绕图形自身中心旋转,需先平移到图形中心,旋转后再平移回原位(见下文 “经典场景”)。
  • 示例:旋转坐标系后画矩形,矩形会随坐标系旋转。
    void MyWidget::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setPen(Qt::blue);// 1. 先平移到 (100, 100)(避免旋转后矩形超出窗口)painter.translate(100, 100);// 2. 旋转 45 度(顺时针)painter.rotate(45);// 3. 绘制矩形:基于旋转后的坐标系,矩形会倾斜 45 度painter.drawRect(-25, -25, 50, 50); // 中心在原点,避免偏移
    }
    

3. 缩放(Scale):拉伸 / 压缩坐标系

  • 功能:按比例缩放 X 轴和 Y 轴,值大于 1 为放大,小于 1 为缩小,负值为反向(如 X 轴负缩放会导致图形水平翻转)。
  • 函数void QPainter::scale(qreal sx, qreal sy)sx:X 轴缩放因子,sy:Y 轴缩放因子)
  • 示例:缩放后绘制文本,文本会被拉伸或缩小。
    void MyWidget::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setPen(Qt::green);// 1. 缩放:X轴放大2倍,Y轴放大1.5倍painter.scale(2.0, 1.5);// 2. 绘制文本:文本会横向拉伸2倍,纵向拉伸1.5倍painter.drawText(10, 30, "Scaled Text");
    }
    

4. 剪切(Shear):倾斜坐标系

  • 功能:使坐标系沿 X 轴或 Y 轴倾斜(剪切变换),产生 “斜切” 效果,常用于绘制平行四边形、梯形等。
  • 函数void QPainter::shear(qreal sh, qreal sv)sh:X 轴剪切因子,sv:Y 轴剪切因子)
  • 示例:剪切后画矩形,矩形会变成平行四边形。
    void MyWidget::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setPen(Qt::purple);// 1. 剪切:X轴倾斜0.5(沿Y轴方向偏移),Y轴不倾斜painter.shear(0.5, 0.0);// 2. 绘制矩形:矩形沿X轴倾斜,变成平行四边形painter.drawRect(50, 50, 80, 40);
    }

save()和restore

Anti-aliased Painting(抗锯齿绘画)

四种绘图设备介绍

Qt提供了4个类来处理图像:QImage、QPixmap、QBitmap、QPicture,为了对这几个类加以区分,分别称QImage为图像、QPixmap为像素图、QBitmap为位图、QPicture为图片,由于它们都是QPaintDevice类的子类(直接或间接),因此他们都是绘制设备,可以直接在其上进行图形绘制。

Qt各个处理图像类的区别及作用 :

  • QImage类提供了一个与硬件无关的图像表示方法,可以直接访问和操控像素,也就是说该类可修改或编辑图像的像素。该类还可以用于进行I/O处理,并对I/O处理操作进行了优化 ;

  • QPixmap类主要用于在屏幕上显示图像,QPixmap中的像素数据是由底层窗口系统进行管理的,该类不能直接访问和操控像素,只能通过QPainter的相应函数或把QPixmap转换为QImage来访问和操控像素。QPixmap可通过标签(QLabel 类)或QAbstractButton 的子类(icon 属性)显示在屏幕上 ;

  • QBitmap是QPixmap的子类,用于处理颜色深度为1的图像,即只能显示黑白两种颜色;

  • QPicture用来记录并重演QPainter命令,QPicture与分辨率无关,可在不同设备上显示。该类使用一个与平台无关的格式(.pic 格式)把绘图命令序列化到 I/O 设备,所有可绘制在QWidget部件或QPixmap上的内容都可以保存在QPicture中,该类的主要作用是把一个绘制图备上使用QPainter绘制的所有图形保存在QPicture之中,然后再把这些图形重新绘制在其他绘图设备上 。

双缓冲机制

在 Qt 绘图系统中,双缓冲机制(Double Buffering) 是一种用于解决绘图闪烁问题的技术,尤其适用于频繁刷新或复杂图形绘制的场景。它通过在内存中预先完成所有绘制操作,再一次性将结果显示到屏幕上,避免了用户看到 “半成品” 绘图过程导致的视觉闪烁。

一、为什么需要双缓冲?

单缓冲机制中,绘图操作直接在屏幕(显示设备)上进行:

  • 当绘制复杂图形(如大量线条、文本、图像)或频繁刷新时,用户会看到 “逐步绘制” 的过程(例如先画一部分,再画另一部分)。
  • 若同时存在擦除旧内容(如eraseRect())和绘制新内容的操作,屏幕会短暂显示空白或不完整画面,产生明显 “闪烁”。

双缓冲通过引入内存缓冲区解决这一问题,核心思路是:“先在内存中画好,再一次性展示”。

二、双缓冲的基本原理

双缓冲包含两个 “缓冲区”:

  1. 后台缓冲区(Off-screen Buffer):一块内存中的绘图区域(如QPixmap),所有绘图操作先在这块区域完成,用户看不到这个过程。
  2. 前台缓冲区(On-screen Buffer):屏幕上的显示区域,当后台缓冲区绘制完成后,将其内容一次性复制到前台缓冲区,用户看到的是完整的画面。

整个过程对用户来说是 “瞬时” 的,因此不会感知到中间的绘制步骤,从而消除闪烁。

三、Qt 中的双缓冲实现

Qt 对双缓冲提供了自动支持手动实现两种方式,大多数情况下无需手动处理,Qt 已内部优化。

1. Qt 的自动双缓冲(推荐)

从 Qt 4 开始,QWidget 及其子类(如 QFrameQPushButton 等)默认启用了双缓冲机制,核心逻辑由 Qt 内部处理:

  • 当重写 paintEvent() 时,QPainter 并非直接绘制到屏幕,而是先绘制到一个临时内存缓冲区(由 Qt 自动创建)。
  • 当 paintEvent() 执行完毕,Qt 会自动将缓冲区内容复制到屏幕,完成显示。

这种机制下,开发者无需手动管理缓冲区,即可避免大部分闪烁问题。

时钟代码

//widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QBrush>
#include <QTimer>
#include <QTime>
#include <QDebug>
#include <QPaintEvent>
#include <QPainter>class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void paintEvent(QPaintEvent *event) override;private:QTimer *m_timer;int m_count = 0;int m_hour;int m_minute;int m_second;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"#include <QDebug>Widget::Widget(QWidget *parent)
: QWidget(parent)
{//创建定时器对象m_timer = new QTimer(this);connect(m_timer, &QTimer::timeout, [&](){m_count++;update();});m_timer->start(1000);//这句话的含义是:获取当前时间,比如:"19:20:58 下午",那么//t = QString("19:20:58 下午");//list1会将t以空格为分隔符,分为("19:20:58", "下午")//list2会将list1[0],也就是QString("19:20:58"),分为("19","20","58")//然后m_hour = 19,m_minute = 20,m_second = 58QString t = QTime::currentTime().toString("h:m:s ap");QStringList list1 = t.split(" ");QStringList list2 = list1[0].split(":");m_hour = list2[0].toUInt();m_minute = list2[1].toUInt();m_second = list2[2].toUInt();//此处使用打印可以得出结果qDebug() << "t = " << t<< "list1 = " << list1<< "list2 = " << list2<< "m_hour = " << m_hour<< "m_minute = " << m_minute<< "m_second = " << m_second;
}Widget::~Widget()
{
}void Widget::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);//绘制圆QPainter pat(this);QPen pen(QColor("skyblue"));pen.setWidth(3);QBrush b("pink");pat.setPen(pen);pat.setBrush(b);//移动画家的位置,然后画圆pat.translate(this->width()/2, this->height()/2);pat.drawEllipse(QPoint(0, 0), 200, 200);//绘制刻度pen.setColor(QColor("black"));pat.setPen(pen);for (int i = 0; i < 60; ++i){pat.rotate(6);pat.drawLine(QPoint(200, 0), QPoint(195, 0));}pen.setWidth(5);pat.setPen(pen);for(int i = 0; i < 12; ++i){pat.rotate(30);pat.drawLine(QPoint(200, 0),QPoint(190, 0));pat.drawText(QPoint(-5, -165), QString("%1").arg(i + 1));}//制作时针pen.setWidth(6);pen.setColor("blue");pat.setPen(pen);pat.rotate(m_hour * 30 + 6 * m_second/60/12+ 30 * m_minute/60 + 6 * m_count/60/12);pat.drawLine(QPoint(0, -50), QPoint(0, 5));//制作分针QPainter patMinute(this);patMinute.translate(this->width()/2, this->height()/2);pen.setWidth(6);pen.setColor("green");patMinute.setPen(pen);patMinute.rotate(6 * m_count/60 + 6 * m_minute + 6 * m_second/60);patMinute.drawLine(QPoint(0, -100), QPoint(0, 2));//制作秒针QPainter patSecond(this);patSecond.translate(this->width()/2, this->height()/2);pen.setWidth(6);pen.setColor("black");patSecond.setPen(pen);patSecond.rotate(6 * m_count + 6 * m_second);patSecond.drawLine(QPoint(0, -100), QPoint(0, 2));
}

测试结果

时钟代码2

//analogclock.h
#ifndef ANALOGCLOCK_H
#define ANALOGCLOCK_H#include <QWidget>class AnalogClock : public QWidget
{Q_OBJECTpublic:AnalogClock(QWidget *parent = nullptr);protected:void paintEvent(QPaintEvent *event) override;
};#endif
//analogclock.cpp
#include "analogclock.h"#include <QPainter>
#include <QTime>
#include <QTimer>// 构造函数:初始化时钟
AnalogClock::AnalogClock(QWidget *parent): QWidget(parent)
{// 创建定时器,用于每秒更新一次时钟QTimer *timer = new QTimer(this);// 连接定时器超时信号到更新函数,实现每秒刷新connect(timer, &QTimer::timeout, this, QOverload<>::of(&AnalogClock::update));timer->start(1000); // 定时器间隔设置为1000毫秒(1秒)setWindowTitle(tr("带秒针的模拟时钟")); // 设置窗口标题resize(200, 200); // 设置初始窗口大小
}// 绘图事件:绘制时钟的表盘、时针、分针和秒针
void AnalogClock::paintEvent(QPaintEvent *)
{// 定义时针形状:由3个点组成的多边形static const QPoint hourHand[3] = {QPoint(7, 8),    // 时针底部右侧点QPoint(-7, 8),   // 时针底部左侧点QPoint(0, -40)   // 时针顶端点};// 定义分针形状:由3个点组成的多边形static const QPoint minuteHand[3] = {QPoint(7, 8),    // 分针底部右侧点QPoint(-7, 8),   // 分针底部左侧点QPoint(0, -70)   // 分针顶端点};// 定义秒针形状:由3个点组成的多边形,比时分针更细更长static const QPoint secondHand[3] = {QPoint(3, 6),    // 秒针底部右侧点QPoint(-3, 6),   // 秒针底部左侧点QPoint(0, -80)   // 秒针顶端点,比分针更长};// 设置时针颜色:紫色QColor hourColor(127, 0, 127);// 设置分针颜色:蓝绿色,带透明度QColor minuteColor(0, 127, 127, 191);// 设置秒针颜色:红色,更醒目QColor secondColor(255, 0, 0);// 计算时钟绘制区域的边长(取窗口宽高的最小值,保证时钟为圆形)int side = qMin(width(), height());// 获取当前时间QTime time = QTime::currentTime();// 创建绘图工具QPainter painter(this);// 启用抗锯齿,使绘制的线条更平滑painter.setRenderHint(QPainter::Antialiasing);// 将坐标原点平移到窗口中心(时钟中心)painter.translate(width() / 2, height() / 2);// 缩放坐标系,使绘制内容适应窗口大小(以200x200为基准)painter.scale(side / 200.0, side / 200.0);// ====== 绘制时针 ======painter.setPen(Qt::NoPen); // 时针不需要边框painter.setBrush(hourColor); // 设置时针填充颜色painter.save(); // 保存当前绘图状态// 计算时针旋转角度:每小时30度(360/12),加上分钟的影响painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)));// 绘制时针多边形painter.drawConvexPolygon(hourHand, 3);painter.restore(); // 恢复到之前的绘图状态// 绘制小时刻度painter.setPen(hourColor); // 使用时针颜色绘制刻度for (int i = 0; i < 12; ++i) {// 绘制刻度线(从88到96的短线)painter.drawLine(88, 0, 96, 0);painter.rotate(30.0); // 每30度绘制一个刻度(12个刻度)}// ====== 绘制分针 ======painter.setPen(Qt::NoPen); // 分针不需要边框painter.setBrush(minuteColor); // 设置分针填充颜色painter.save(); // 保存当前绘图状态// 计算分针旋转角度:每分钟6度(360/60),加上秒数的影响painter.rotate(6.0 * (time.minute() + time.second() / 60.0));// 绘制分针多边形painter.drawConvexPolygon(minuteHand, 3);painter.restore(); // 恢复到之前的绘图状态// 绘制分钟刻度painter.setPen(minuteColor); // 使用分针颜色绘制刻度for (int j = 0; j < 60; ++j) {// 每5分钟的刻度不重复绘制(已由小时刻度覆盖)if ((j % 5) != 0)painter.drawLine(92, 0, 96, 0); // 绘制较短的刻度线painter.rotate(6.0); // 每6度绘制一个刻度(60个刻度)}// ====== 绘制秒针 ======painter.setPen(Qt::NoPen); // 秒针不需要边框painter.setBrush(secondColor); // 设置秒针填充颜色painter.save(); // 保存当前绘图状态// 计算秒针旋转角度:每秒6度(360/60)painter.rotate(6.0 * time.second());// 绘制秒针多边形painter.drawConvexPolygon(secondHand, 3);painter.restore(); // 恢复到之前的绘图状态
}

测试结果

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

相关文章:

  • anker 网站谁做的优化网站的步骤
  • 2.配置DNS服务器过程
  • 外国的网站 ftp做网站运营经理的要求
  • 办网站需流程wordpress 主题制作教程
  • 漫谈《数字图像处理》之图像模式识别的核心方法论
  • 雅虎网站收录入口南京开发
  • 外贸网站推广平台哪个好网站自动加水印
  • 少样本学习论文分享:多模态模型和元学习
  • 电动剃须刀MCU控制方案开发知识分享
  • html5的网站设计与实现是做什么网络优化工程师是干什么的
  • 有什么网站用名字做图片大全记事本做网站素材代码
  • 《机器学习与深度学习》入门
  • 六安网站自然排名优化价格网站没有备案时
  • 阿里云做企业网站用php怎么做网站
  • Qt常用控件之QLabel(二)
  • C++笔记(基础)动态内存管理 auto,decltype关键字
  • 申请网站沈阳网站建设招标公司
  • 网站统计 中文域名建站之星极速版
  • python中Pydantic学习笔记
  • 网站域名注册机制网站建设公司包括哪些板块
  • 华为昇腾 CANN 算子仓开源上线 GitCode,加速 AI 开发生态共建
  • 广东建站网站怎么做结算
  • 河南省建设厅陈华平官方网站制作公司主页网站
  • 精美化妆品网站模板博达网站建设怎么建立下载
  • 网站如何做分布式wordpress始终无法登录
  • NAT44性能测试详解:应对IPv4地址枯竭的关键利器
  • 一个网站可以做多少个关键词wordpress会员发布文章
  • 敏感网站用什么浏览器重庆市最新新闻
  • 怎么用自助网站订阅 wordpress
  • 国外优秀论文网站郑州新闻